summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgunsch <gunsch@chromium.org>2014-10-08 21:37:06 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-09 04:37:32 +0000
commit168c44ce90012516dcfa91ac80a6d0a200513531 (patch)
tree52b91d2ca80fdd9c7e2e858928a69505d456a4e9
parentfbba679b43d6df314321543541dfce39789593b4 (diff)
downloadchromium_src-168c44ce90012516dcfa91ac80a6d0a200513531.zip
chromium_src-168c44ce90012516dcfa91ac80a6d0a200513531.tar.gz
chromium_src-168c44ce90012516dcfa91ac80a6d0a200513531.tar.bz2
Chromecast: adds crash handling for Android build.
Cast for Android TV behavior includes the following: * On crash, immediately attempt to upload crash dump + logs from current process, then delete the dump file. * On startup, attempt to upload any crash dump files that are still left on the device. R=thestig@chromium.org,byungchul@chromium.org BUG=400876 Review URL: https://codereview.chromium.org/620673003 Cr-Commit-Position: refs/heads/master@{#298767}
-rw-r--r--chromecast/android/DEPS4
-rw-r--r--chromecast/android/cast_jni_registrar.cc2
-rw-r--r--chromecast/android/chromecast_config_android.h4
-rw-r--r--chromecast/android/chromecast_config_android_stub.cc15
-rw-r--r--chromecast/chromecast.gyp12
-rw-r--r--chromecast/common/global_descriptors.h1
-rw-r--r--chromecast/crash/DEPS4
-rw-r--r--chromecast/crash/android/DEPS3
-rw-r--r--chromecast/crash/android/cast_crash_reporter_client_android.cc72
-rw-r--r--chromecast/crash/android/cast_crash_reporter_client_android.h35
-rw-r--r--chromecast/crash/android/crash_handler.cc145
-rw-r--r--chromecast/crash/android/crash_handler.h72
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java32
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java223
-rw-r--r--chromecast/shell/app/DEPS1
-rw-r--r--chromecast/shell/app/cast_main_delegate.cc15
-rw-r--r--chromecast/shell/browser/DEPS1
-rw-r--r--chromecast/shell/browser/cast_browser_main_parts.cc12
-rw-r--r--chromecast/shell/browser/cast_browser_process.cc12
-rw-r--r--chromecast/shell/browser/cast_browser_process.h6
-rw-r--r--chromecast/shell/browser/cast_content_browser_client.cc18
21 files changed, 688 insertions, 1 deletions
diff --git a/chromecast/android/DEPS b/chromecast/android/DEPS
index d441934..62e5a66 100644
--- a/chromecast/android/DEPS
+++ b/chromecast/android/DEPS
@@ -1,4 +1,6 @@
include_rules = [
- # Include for JNI.
+ # Includes for JNI.
+ "+chromecast/android",
+ "+chromecast/crash/android",
"+chromecast/shell/browser/android",
]
diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc
index de919c6..81735d8 100644
--- a/chromecast/android/cast_jni_registrar.cc
+++ b/chromecast/android/cast_jni_registrar.cc
@@ -6,6 +6,7 @@
#include "base/android/jni_android.h"
#include "base/android/jni_registrar.h"
+#include "chromecast/crash/android/crash_handler.h"
#include "chromecast/shell/browser/android/cast_window_android.h"
#include "chromecast/shell/browser/android/cast_window_manager.h"
#include "chromecast/shell/browser/android/external_video_surface_container_impl.h"
@@ -20,6 +21,7 @@ static base::android::RegistrationMethod kMethods[] = {
{ "CastWindowManager", shell::RegisterCastWindowManager },
{ "ExternalVideoSurfaceContainer",
shell::RegisterExternalVideoSurfaceContainer },
+ { "CrashHandler", CrashHandler::RegisterCastCrashJni },
};
} // namespace
diff --git a/chromecast/android/chromecast_config_android.h b/chromecast/android/chromecast_config_android.h
index 627259e..e09d657 100644
--- a/chromecast/android/chromecast_config_android.h
+++ b/chromecast/android/chromecast_config_android.h
@@ -18,6 +18,10 @@ class ChromecastConfigAndroid {
public:
static ChromecastConfigAndroid* GetInstance();
+ // Returns whether or not the user has allowed sending usage stats and
+ // crash reports.
+ bool CanSendUsageStats();
+
// Registers a handler to be notified when SendUsageStats is changed.
void SetSendUsageStatsChangedCallback(
const base::Callback<void(bool)>& callback);
diff --git a/chromecast/android/chromecast_config_android_stub.cc b/chromecast/android/chromecast_config_android_stub.cc
new file mode 100644
index 0000000..dc8bdca
--- /dev/null
+++ b/chromecast/android/chromecast_config_android_stub.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/chromecast_config_android.h"
+
+namespace chromecast {
+namespace android {
+
+bool ChromecastConfigAndroid::CanSendUsageStats() {
+ return false;
+}
+
+} // namespace android
+} // namespace chromecast
diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp
index cdb1ede..7ca308a 100644
--- a/chromecast/chromecast.gyp
+++ b/chromecast/chromecast.gyp
@@ -327,18 +327,28 @@
'cast_shell_pak',
'cast_version_header',
'../base/base.gyp:base',
+ '../breakpad/breakpad.gyp:breakpad_client',
+ '../components/components.gyp:breakpad_host',
+ '../components/components.gyp:crash_component',
'../content/content.gyp:content_app_browser',
'../content/content.gyp:content',
'../skia/skia.gyp:skia',
'../ui/gfx/gfx.gyp:gfx',
'../ui/gl/gl.gyp:gl',
],
+ 'include_dirs': [
+ '../breakpad/src',
+ ],
'sources': [
'android/cast_jni_registrar.cc',
'android/cast_jni_registrar.h',
'android/chromecast_config_android.cc',
'android/chromecast_config_android.h',
'android/platform_jni_loader.h',
+ 'crash/android/cast_crash_reporter_client_android.cc',
+ 'crash/android/cast_crash_reporter_client_android.h',
+ 'crash/android/crash_handler.cc',
+ 'crash/android/crash_handler.h',
'shell/app/android/cast_jni_loader.cc',
'shell/browser/android/cast_window_android.cc',
'shell/browser/android/cast_window_android.h',
@@ -354,6 +364,7 @@
],
}, {
'sources': [
+ 'android/chromecast_config_android_stub.cc',
'android/platform_jni_loader_stub.cc',
],
}]
@@ -407,6 +418,7 @@
'target_name': 'cast_jni_headers',
'type': 'none',
'sources': [
+ 'shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java',
'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java',
'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java',
'shell/android/apk/src/org/chromium/chromecast/shell/ExternalVideoSurfaceContainer.java',
diff --git a/chromecast/common/global_descriptors.h b/chromecast/common/global_descriptors.h
index cbbbb2c..b380158 100644
--- a/chromecast/common/global_descriptors.h
+++ b/chromecast/common/global_descriptors.h
@@ -15,6 +15,7 @@ enum {
kDummyValue = kContentIPCDescriptorMax + 1,
#if defined(OS_ANDROID)
kAndroidPakDescriptor,
+ kAndroidMinidumpDescriptor,
#endif // defined(OS_ANDROID)
};
diff --git a/chromecast/crash/DEPS b/chromecast/crash/DEPS
new file mode 100644
index 0000000..4389c51
--- /dev/null
+++ b/chromecast/crash/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+breakpad",
+ "+components/crash",
+]
diff --git a/chromecast/crash/android/DEPS b/chromecast/crash/android/DEPS
new file mode 100644
index 0000000..5021862
--- /dev/null
+++ b/chromecast/crash/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+chromecast/android",
+]
diff --git a/chromecast/crash/android/cast_crash_reporter_client_android.cc b/chromecast/crash/android/cast_crash_reporter_client_android.cc
new file mode 100644
index 0000000..2137fae
--- /dev/null
+++ b/chromecast/crash/android/cast_crash_reporter_client_android.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/crash/android/cast_crash_reporter_client_android.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "chromecast/android/chromecast_config_android.h"
+#include "chromecast/common/global_descriptors.h"
+#include "chromecast/common/version.h"
+#include "content/public/common/content_switches.h"
+
+namespace chromecast {
+
+CastCrashReporterClientAndroid::CastCrashReporterClientAndroid() {
+}
+
+CastCrashReporterClientAndroid::~CastCrashReporterClientAndroid() {
+}
+
+void CastCrashReporterClientAndroid::GetProductNameAndVersion(
+ const char** product_name,
+ const char** version) {
+ *product_name = "media_shell";
+ *version = PRODUCT_VERSION
+#if CAST_IS_DEBUG_BUILD
+ ".debug"
+#endif
+ "." CAST_BUILD_REVISION;
+}
+
+base::FilePath CastCrashReporterClientAndroid::GetReporterLogFilename() {
+ return base::FilePath(FILE_PATH_LITERAL("uploads.log"));
+}
+
+bool CastCrashReporterClientAndroid::GetCrashDumpLocation(
+ base::FilePath* crash_dir) {
+ base::FilePath crash_dir_local;
+ if (!PathService::Get(base::DIR_ANDROID_APP_DATA, &crash_dir_local)) {
+ return false;
+ }
+ crash_dir_local = crash_dir_local.Append("crashes");
+
+ if (!base::DirectoryExists(crash_dir_local)) {
+ if (!base::CreateDirectory(crash_dir_local)) {
+ return false;
+ }
+ }
+
+ // Provide value to crash_dir once directory is known to be a valid path.
+ *crash_dir = crash_dir_local;
+ return true;
+}
+
+bool CastCrashReporterClientAndroid::GetCollectStatsConsent() {
+ return android::ChromecastConfigAndroid::GetInstance()->CanSendUsageStats();
+}
+
+int CastCrashReporterClientAndroid::GetAndroidMinidumpDescriptor() {
+ return kAndroidMinidumpDescriptor;
+}
+
+bool CastCrashReporterClientAndroid::EnableBreakpadForProcess(
+ const std::string& process_type) {
+ return process_type == switches::kRendererProcess ||
+ process_type == switches::kGpuProcess;
+}
+
+} // namespace chromecast
diff --git a/chromecast/crash/android/cast_crash_reporter_client_android.h b/chromecast/crash/android/cast_crash_reporter_client_android.h
new file mode 100644
index 0000000..4524ae7
--- /dev/null
+++ b/chromecast/crash/android/cast_crash_reporter_client_android.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_
+#define CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_
+
+#include "base/compiler_specific.h"
+#include "components/crash/app/crash_reporter_client.h"
+
+namespace chromecast {
+
+class CastCrashReporterClientAndroid
+ : public crash_reporter::CrashReporterClient {
+ public:
+ CastCrashReporterClientAndroid();
+ virtual ~CastCrashReporterClientAndroid();
+
+ // crash_reporter::CrashReporterClient implementation:
+ virtual void GetProductNameAndVersion(const char** product_name,
+ const char** version) override;
+ virtual base::FilePath GetReporterLogFilename() override;
+ virtual bool GetCrashDumpLocation(base::FilePath* crash_dir) override;
+ virtual int GetAndroidMinidumpDescriptor() override;
+ virtual bool GetCollectStatsConsent() override;
+ virtual bool EnableBreakpadForProcess(
+ const std::string& process_type) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CastCrashReporterClientAndroid);
+};
+
+} // namespace chromecast
+
+#endif // CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_
diff --git a/chromecast/crash/android/crash_handler.cc b/chromecast/crash/android/crash_handler.cc
new file mode 100644
index 0000000..a73e313
--- /dev/null
+++ b/chromecast/crash/android/crash_handler.cc
@@ -0,0 +1,145 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/crash/android/crash_handler.h"
+
+#include <jni.h>
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "breakpad/src/client/linux/handler/exception_handler.h"
+#include "breakpad/src/client/linux/handler/minidump_descriptor.h"
+#include "chromecast/common/version.h"
+#include "chromecast/crash/android/cast_crash_reporter_client_android.h"
+#include "components/crash/app/breakpad_linux.h"
+#include "components/crash/app/crash_reporter_client.h"
+#include "content/public/common/content_switches.h"
+#include "jni/CastCrashHandler_jni.h"
+
+namespace {
+
+chromecast::CrashHandler* g_crash_handler = NULL;
+
+// ExceptionHandler requires a HandlerCallback as a function pointer. This
+// function exists to proxy into the global CrashHandler instance.
+bool HandleCrash(const void* /* crash_context */,
+ size_t /* crash_context_size */,
+ void* /* context */) {
+ DCHECK(g_crash_handler);
+ if (g_crash_handler->CanUploadCrashDump()) {
+ g_crash_handler->AttemptUploadCrashDump();
+ } else {
+ g_crash_handler->RemoveCrashDumps();
+ }
+
+ // Let the exception continue to propagate up to the system.
+ return false;
+}
+
+} // namespace
+
+namespace chromecast {
+
+// static
+void CrashHandler::Initialize(const std::string& process_type,
+ const base::FilePath& log_file_path) {
+ DCHECK(!g_crash_handler);
+ g_crash_handler = new CrashHandler(log_file_path);
+ g_crash_handler->Initialize(process_type);
+}
+
+// static
+bool CrashHandler::GetCrashDumpLocation(base::FilePath* crash_dir) {
+ DCHECK(g_crash_handler);
+ return g_crash_handler->crash_reporter_client_->
+ GetCrashDumpLocation(crash_dir);
+}
+
+// static
+bool CrashHandler::RegisterCastCrashJni(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+CrashHandler::CrashHandler(const base::FilePath& log_file_path)
+ : log_file_path_(log_file_path),
+ crash_reporter_client_(new CastCrashReporterClientAndroid) {
+ if (!crash_reporter_client_->GetCrashDumpLocation(&crash_dump_path_)) {
+ LOG(ERROR) << "Could not get crash dump location";
+ }
+ SetCrashReporterClient(crash_reporter_client_.get());
+}
+
+CrashHandler::~CrashHandler() {
+ DCHECK(g_crash_handler);
+ g_crash_handler = NULL;
+}
+
+void CrashHandler::Initialize(const std::string& process_type) {
+ if (process_type != switches::kZygoteProcess) {
+ if (process_type.empty()) {
+ // ExceptionHandlers are called on crash in reverse order of
+ // instantiation. This ExceptionHandler will attempt to upload crashes
+ // and the log file written out by the main process.
+
+ // Dummy MinidumpDescriptor just to start up another ExceptionHandler.
+ google_breakpad::MinidumpDescriptor dummy(crash_dump_path_.value());
+ crash_uploader_.reset(new google_breakpad::ExceptionHandler(
+ dummy, NULL, NULL, NULL, true, -1));
+ crash_uploader_->set_crash_handler(&::HandleCrash);
+
+ breakpad::InitCrashReporter(process_type);
+ } else {
+ breakpad::InitNonBrowserCrashReporterForAndroid(process_type);
+ }
+ }
+
+ UploadCrashDumpsAsync();
+}
+
+bool CrashHandler::CanUploadCrashDump() {
+ DCHECK(crash_reporter_client_);
+ return crash_reporter_client_->GetCollectStatsConsent();
+}
+
+void CrashHandler::AttemptUploadCrashDump() {
+ VLOG(1) << "Attempting to upload current process crash";
+ JNIEnv* env = base::android::AttachCurrentThread();
+ // Crash dump location
+ base::android::ScopedJavaLocalRef<jstring> crash_dump_path_java =
+ base::android::ConvertUTF8ToJavaString(env,
+ crash_dump_path_.value());
+ // Current log file location
+ base::android::ScopedJavaLocalRef<jstring> log_file_path_java =
+ base::android::ConvertUTF8ToJavaString(env, log_file_path_.value());
+ Java_CastCrashHandler_uploadCurrentProcessDumpSync(
+ env,
+ crash_dump_path_java.obj(),
+ log_file_path_java.obj(),
+ CAST_IS_DEBUG_BUILD);
+}
+
+void CrashHandler::UploadCrashDumpsAsync() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jstring> crash_dump_path_java =
+ base::android::ConvertUTF8ToJavaString(env,
+ crash_dump_path_.value());
+ Java_CastCrashHandler_uploadCrashDumpsAsync(env,
+ crash_dump_path_java.obj(),
+ CAST_IS_DEBUG_BUILD);
+}
+
+void CrashHandler::RemoveCrashDumps() {
+ VLOG(1) << "Removing crash dumps instead of uploading";
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jstring> crash_dump_path_java =
+ base::android::ConvertUTF8ToJavaString(env,
+ crash_dump_path_.value());
+ Java_CastCrashHandler_removeCrashDumpsSync(
+ env, crash_dump_path_java.obj(), CAST_IS_DEBUG_BUILD);
+}
+
+} // namespace chromecast
diff --git a/chromecast/crash/android/crash_handler.h b/chromecast/crash/android/crash_handler.h
new file mode 100644
index 0000000..246c5bb
--- /dev/null
+++ b/chromecast/crash/android/crash_handler.h
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_
+#define CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace google_breakpad {
+class ExceptionHandler;
+}
+
+namespace chromecast {
+class CastCrashReporterClientAndroid;
+
+class CrashHandler {
+ public:
+ // Initializes the crash handler for attempting to upload crash dumps with
+ // the current process's log file.
+ // Must not be called more than once.
+ static void Initialize(const std::string& process_type,
+ const base::FilePath& log_file_path);
+
+ // Returns the directory location for crash dumps.
+ static bool GetCrashDumpLocation(base::FilePath* crash_dir);
+
+ // Registers JNI methods for this module.
+ static bool RegisterCastCrashJni(JNIEnv* env);
+
+ // Returns whether or not the user has allowed for uploading crash dumps.
+ bool CanUploadCrashDump();
+
+ // Callback with which to create a breakpad::ExceptionHandler that will
+ // attempt synchronously uploading crash dumps and logs at crash time.
+ void AttemptUploadCrashDump();
+
+ // Callback for breakpad::ExceptionHandler to delete crash dumps created by
+ // the Chrome crash component. Chrome's crash component does not query
+ // for user consent after initializing breakpad.
+ void RemoveCrashDumps();
+
+ private:
+ CrashHandler(const base::FilePath& log_file_path);
+ ~CrashHandler();
+
+ void Initialize(const std::string& process_type);
+
+ // Starts a background thread to look for any past crash dumps and upload them
+ // to the crash server.
+ void UploadCrashDumpsAsync();
+
+ // Path to the current process's log file.
+ base::FilePath log_file_path_;
+
+ // Location to which crash dumps should be written.
+ base::FilePath crash_dump_path_;
+
+ scoped_ptr<CastCrashReporterClientAndroid> crash_reporter_client_;
+ scoped_ptr<google_breakpad::ExceptionHandler> crash_uploader_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrashHandler);
+};
+
+} // namespace chromecast
+
+#endif // CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java
new file mode 100644
index 0000000..20fed19
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashHandler.java
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * JNI wrapper class for accessing CastCrashHandler.
+ */
+@JNINamespace("chromecast")
+public final class CastCrashHandler {
+
+ @CalledByNative
+ public static void removeCrashDumpsSync(String crashDumpPath, boolean isDebugBuild) {
+ new CastCrashUploader(crashDumpPath, isDebugBuild).removeCrashDumpsSync();
+ }
+
+ @CalledByNative
+ public static void uploadCurrentProcessDumpSync(String crashDumpPath, String logFilePath,
+ boolean isDebugBuild) {
+ new CastCrashUploader(crashDumpPath, isDebugBuild).
+ uploadCurrentProcessDumpSync(logFilePath);
+ }
+
+ @CalledByNative
+ public static void uploadCrashDumpsAsync(String crashDumpPath, boolean isDebugBuild) {
+ new CastCrashUploader(crashDumpPath, isDebugBuild).uploadRecentCrashesAsync();
+ }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java
new file mode 100644
index 0000000..bc1631c
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastCrashUploader.java
@@ -0,0 +1,223 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.net.http.AndroidHttpClient;
+import android.util.Log;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.InputStreamEntity;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.SequenceInputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Crash crashdump uploader. Scans the crash dump location provided by CastCrashReporterClient
+ * for dump files, attempting to upload all crash dumps to the crash server.
+ *
+ * Uploading is intended to happen in a background thread, and this method will likely be called
+ * on startup, looking for crash dumps from previous runs, since Chromium's crash code
+ * explicitly blocks any post-dump hooks or uploading for Android builds.
+ */
+public final class CastCrashUploader {
+ private static final String TAG = "CastCrashUploader";
+ private static final String CRASH_REPORT_HOST = "clients2.google.com";
+ private static final String CAST_SHELL_USER_AGENT = android.os.Build.MODEL + "/CastShell";
+
+ private final ExecutorService mExecutorService;
+ private final String mCrashDumpPath;
+ private final String mCrashReportUploadUrl;
+
+ public CastCrashUploader(String crashDumpPath, boolean isDebugBuild) {
+ this.mCrashDumpPath = crashDumpPath;
+ mCrashReportUploadUrl = isDebugBuild ?
+ "http://clients2.google.com/cr/staging_report" :
+ "http://clients2.google.com/cr/report";
+ mExecutorService = Executors.newFixedThreadPool(1);
+ }
+
+ public void removeCrashDumpsSync() {
+ mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ File crashDumpDirectory = new File(mCrashDumpPath);
+ for (File potentialDump : crashDumpDirectory.listFiles()) {
+ if (potentialDump.getName().matches(".*\\.dmp\\d*")) {
+ potentialDump.delete();
+ }
+ }
+ }
+ });
+ waitForTasksToFinish();
+ }
+
+ /**
+ * Synchronously uploads the crash dump from the current process, along with an attached
+ * log file.
+ * @param logFilePath Full path to the log file for the current process.
+ */
+ public void uploadCurrentProcessDumpSync(String logFilePath) {
+ int pid = android.os.Process.myPid();
+ Log.d(TAG, "Immediately attempting a crash upload with logs, looking for: .dmp" + pid);
+
+ queueAllCrashDumpUpload(".*\\.dmp" + pid, new File(logFilePath));
+ waitForTasksToFinish();
+ }
+
+ private void waitForTasksToFinish() {
+ try {
+ mExecutorService.shutdown();
+ boolean finished = mExecutorService.awaitTermination(60, TimeUnit.SECONDS);
+ if (!finished) {
+ Log.d(TAG, "Crash dump handling did not finish executing in time. Exiting.");
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted waiting for asynchronous execution", e);
+ }
+ }
+
+ /**
+ * Scans the given location for crash dump files and queues background
+ * uploads of each file.
+ */
+ public void uploadRecentCrashesAsync() {
+ mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ // Multipart dump filename has format "[random string].dmp[pid]", e.g.
+ // 20597a65-b822-008e-31f8fc8e-02bb45c0.dmp18169
+ queueAllCrashDumpUpload(".*\\.dmp\\d+", null);
+ }
+ });
+ }
+
+ /**
+ * Searches for files matching the given regex in the crash dump folder, queueing each
+ * one for upload.
+ * @param dumpFileRegex Regex to test against the name of each file in the crash dump folder.
+ * @param logFile Log file to include, if any.
+ */
+ private void queueAllCrashDumpUpload(String dumpFileRegex, File logFile) {
+ if (mCrashDumpPath == null) return;
+ final AndroidHttpClient httpClient = AndroidHttpClient.newInstance(CAST_SHELL_USER_AGENT);
+ File crashDumpDirectory = new File(mCrashDumpPath);
+ for (File potentialDump : crashDumpDirectory.listFiles()) {
+ if (potentialDump.getName().matches(dumpFileRegex)) {
+ queueCrashDumpUpload(httpClient, potentialDump, logFile);
+ }
+ }
+
+ // When finished uploading all of the queued dumps, release httpClient
+ mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ httpClient.close();
+ }
+ });
+ }
+
+ /**
+ * Enqueues a background task to upload a single crash dump file.
+ */
+ private void queueCrashDumpUpload(final AndroidHttpClient httpClient, final File dumpFile,
+ final File logFile) {
+ mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Uploading dump crash log: " + dumpFile.getName());
+
+ try {
+ // Dump file is already in multipart MIME format and has a boundary throughout.
+ // Scrape the first line, remove two dashes, call that the "boundary" and add it
+ // to the content-type.
+ FileInputStream dumpFileStream = new FileInputStream(dumpFile);
+ String dumpFirstLine = getFirstLine(dumpFileStream);
+ String mimeBoundary = dumpFirstLine.substring(2);
+
+ InputStream uploadCrashDumpStream = new FileInputStream(dumpFile);
+ InputStream logFileStream = null;
+
+ if (logFile != null) {
+ Log.d(TAG, "Including log file: " + logFile.getName());
+ StringBuffer logHeader = new StringBuffer();
+ logHeader.append(dumpFirstLine);
+ logHeader.append("\n");
+ logHeader.append(
+ "Content-Disposition: form-data; name=\"log\"; filename=\"log\"\n");
+ logHeader.append("Content-Type: text/plain\n\n");
+ InputStream logHeaderStream =
+ new ByteArrayInputStream(logHeader.toString().getBytes());
+ logFileStream = new FileInputStream(logFile);
+
+ // Upload: prepend the log file for uploading
+ uploadCrashDumpStream = new SequenceInputStream(
+ new SequenceInputStream(logHeaderStream, logFileStream),
+ uploadCrashDumpStream);
+ }
+
+ InputStreamEntity entity = new InputStreamEntity(uploadCrashDumpStream, -1);
+ entity.setContentType("multipart/form-data; boundary=" + mimeBoundary);
+
+ HttpPost uploadRequest = new HttpPost(mCrashReportUploadUrl);
+ uploadRequest.setEntity(entity);
+ HttpResponse response =
+ httpClient.execute(new HttpHost(CRASH_REPORT_HOST), uploadRequest);
+
+ // Expect a report ID as the entire response
+ String responseLine = getFirstLine(response.getEntity().getContent());
+
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ Log.e(TAG, "Failed response (" + statusCode + "): " + responseLine);
+ return;
+ }
+
+ Log.d(TAG, "Successfully uploaded as report ID: " + responseLine);
+
+ // Delete the file so we don't re-upload it next time.
+ dumpFileStream.close();
+ dumpFile.delete();
+ } catch (IOException e) {
+ Log.e(TAG, "File I/O error trying to upload crash dump", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets the first line from an input stream, opening and closing readers to do so.
+ * We're targeting Java 6, so this is still tedious to do.
+ * @return First line of the input stream.
+ * @throws IOException
+ */
+ private String getFirstLine(InputStream inputStream) throws IOException {
+ InputStreamReader streamReader = null;
+ BufferedReader reader = null;
+ try {
+ streamReader = new InputStreamReader(inputStream);
+ reader = new BufferedReader(streamReader);
+ return reader.readLine();
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ if (streamReader != null) {
+ streamReader.close();
+ }
+ }
+ }
+}
diff --git a/chromecast/shell/app/DEPS b/chromecast/shell/app/DEPS
index efc610d..3dad089 100644
--- a/chromecast/shell/app/DEPS
+++ b/chromecast/shell/app/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+components/crash",
"+content/public/app",
"+content/public/browser",
]
diff --git a/chromecast/shell/app/cast_main_delegate.cc b/chromecast/shell/app/cast_main_delegate.cc
index 719cc2b..e3dbfc1 100644
--- a/chromecast/shell/app/cast_main_delegate.cc
+++ b/chromecast/shell/app/cast_main_delegate.cc
@@ -4,6 +4,7 @@
#include "chromecast/shell/app/cast_main_delegate.h"
+#include "base/command_line.h"
#include "base/cpu.h"
#include "base/logging.h"
#include "base/path_service.h"
@@ -17,6 +18,10 @@
#include "content/public/common/content_switches.h"
#include "ui/base/resource/resource_bundle.h"
+#if defined(OS_ANDROID)
+#include "chromecast/crash/android/crash_handler.h"
+#endif // defined(OS_ANDROID)
+
namespace chromecast {
namespace shell {
@@ -56,6 +61,16 @@ void CastMainDelegate::PreSandboxStartup() {
base::CPU cpu_info;
#endif
+ const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
+ std::string process_type =
+ command_line->GetSwitchValueASCII(switches::kProcessType);
+
+#if defined(OS_ANDROID)
+ base::FilePath log_file;
+ PathService::Get(FILE_CAST_ANDROID_LOG, &log_file);
+ chromecast::CrashHandler::Initialize(process_type, log_file);
+#endif // defined(OS_ANDROID)
+
InitializeResourceBundle();
}
diff --git a/chromecast/shell/browser/DEPS b/chromecast/shell/browser/DEPS
index 78e621d..b023f30 100644
--- a/chromecast/shell/browser/DEPS
+++ b/chromecast/shell/browser/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+components/crash",
"+content/public/browser",
"+media/base",
]
diff --git a/chromecast/shell/browser/cast_browser_main_parts.cc b/chromecast/shell/browser/cast_browser_main_parts.cc
index 19b8b56..ec3e2e9 100644
--- a/chromecast/shell/browser/cast_browser_main_parts.cc
+++ b/chromecast/shell/browser/cast_browser_main_parts.cc
@@ -23,6 +23,8 @@
#include "media/base/media_switches.h"
#if defined(OS_ANDROID)
+#include "chromecast/crash/android/crash_handler.h"
+#include "components/crash/browser/crash_dump_manager_android.h"
#include "net/android/network_change_notifier_factory_android.h"
#endif // defined(OS_ANDROID)
@@ -109,6 +111,16 @@ void CastBrowserMainParts::PreMainMessageLoopRun() {
content::BrowserThread::GetBlockingPool(),
ChromecastConfig::GetInstance()->pref_service(),
cast_browser_process_->browser_context()->GetRequestContext()));
+
+#if defined(OS_ANDROID)
+ base::FilePath crash_dumps_dir;
+ if (!chromecast::CrashHandler::GetCrashDumpLocation(&crash_dumps_dir)) {
+ LOG(ERROR) << "Could not find crash dump location.";
+ }
+ cast_browser_process_->SetCrashDumpManager(
+ new breakpad::CrashDumpManager(crash_dumps_dir));
+#endif
+
cast_browser_process_->SetRemoteDebuggingServer(new RemoteDebuggingServer());
InitializeWebUI();
diff --git a/chromecast/shell/browser/cast_browser_process.cc b/chromecast/shell/browser/cast_browser_process.cc
index 70802aa..f89e695 100644
--- a/chromecast/shell/browser/cast_browser_process.cc
+++ b/chromecast/shell/browser/cast_browser_process.cc
@@ -10,6 +10,10 @@
#include "chromecast/shell/browser/cast_browser_context.h"
#include "chromecast/shell/browser/devtools/remote_debugging_server.h"
+#if defined(OS_ANDROID)
+#include "components/crash/browser/crash_dump_manager_android.h"
+#endif // defined(OS_ANDROID)
+
namespace chromecast {
namespace shell {
@@ -56,5 +60,13 @@ void CastBrowserProcess::SetMetricsServiceClient(
metrics_service_client_.reset(metrics_service_client);
}
+#if defined(OS_ANDROID)
+void CastBrowserProcess::SetCrashDumpManager(
+ breakpad::CrashDumpManager* crash_dump_manager) {
+ DCHECK(!crash_dump_manager_);
+ crash_dump_manager_.reset(crash_dump_manager);
+}
+#endif // defined(OS_ANDROID)
+
} // namespace shell
} // namespace chromecast
diff --git a/chromecast/shell/browser/cast_browser_process.h b/chromecast/shell/browser/cast_browser_process.h
index 65830c2a..ba625ac 100644
--- a/chromecast/shell/browser/cast_browser_process.h
+++ b/chromecast/shell/browser/cast_browser_process.h
@@ -39,6 +39,9 @@ class CastBrowserProcess {
void SetRemoteDebuggingServer(RemoteDebuggingServer* remote_debugging_server);
void SetMetricsServiceClient(
metrics::CastMetricsServiceClient* metrics_service_client);
+#if defined(OS_ANDROID)
+ void SetCrashDumpManager(breakpad::CrashDumpManager* crash_dump_manager);
+#endif // defined(OS_ANDROID)
CastBrowserContext* browser_context() const { return browser_context_.get(); }
CastService* cast_service() const { return cast_service_.get(); }
@@ -50,6 +53,9 @@ class CastBrowserProcess {
scoped_ptr<CastBrowserContext> browser_context_;
scoped_ptr<metrics::CastMetricsServiceClient> metrics_service_client_;
scoped_ptr<RemoteDebuggingServer> remote_debugging_server_;
+#if defined(OS_ANDROID)
+ scoped_ptr<breakpad::CrashDumpManager> crash_dump_manager_;
+#endif // defined(OS_ANDROID)
// Note: CastService must be destroyed before others.
scoped_ptr<CastService> cast_service_;
diff --git a/chromecast/shell/browser/cast_content_browser_client.cc b/chromecast/shell/browser/cast_content_browser_client.cc
index 546aa80..ad1ea3f 100644
--- a/chromecast/shell/browser/cast_content_browser_client.cc
+++ b/chromecast/shell/browser/cast_content_browser_client.cc
@@ -17,6 +17,7 @@
#include "chromecast/shell/browser/devtools/cast_dev_tools_delegate.h"
#include "chromecast/shell/browser/geolocation/cast_access_token_store.h"
#include "chromecast/shell/browser/url_request_context_factory.h"
+#include "components/crash/app/breakpad_linux.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/certificate_request_result_type.h"
#include "content/public/browser/render_process_host.h"
@@ -30,6 +31,10 @@
#include "chromecast/shell/browser/android/external_video_surface_container_impl.h"
#endif // defined(OS_ANDROID)
+#if defined(OS_ANDROID)
+#include "components/crash/browser/crash_dump_manager_android.h"
+#endif // defined(OS_ANDROID)
+
namespace chromecast {
namespace shell {
@@ -228,6 +233,19 @@ void CastContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
mappings->Transfer(
kAndroidPakDescriptor,
base::ScopedFD(pak_with_flags.TakePlatformFile()));
+
+ if (breakpad::IsCrashReporterEnabled()) {
+ base::File minidump_file(
+ breakpad::CrashDumpManager::GetInstance()->CreateMinidumpFile(
+ child_process_id));
+ if (!minidump_file.IsValid()) {
+ LOG(ERROR) << "Failed to create file for minidump, crash reporting will "
+ << "be disabled for this process.";
+ } else {
+ mappings->Transfer(kAndroidMinidumpDescriptor,
+ base::ScopedFD(minidump_file.TakePlatformFile()));
+ }
+ }
#endif // defined(OS_ANDROID)
}