Quellcode durchsuchen

Move exception handler to MainApp

* move exception handler to MainApp
* avoid recursive call to exception handler
* launch exception handler in separate process

Signed-off-by: Chris Narkiewicz <hello@ezaquarii.com>
Chris Narkiewicz vor 5 Jahren
Ursprung
Commit
9d5566f384

+ 5 - 1
src/main/AndroidManifest.xml

@@ -321,7 +321,11 @@
         <activity android:name=".ui.activity.LogHistoryActivity"/>
 
         <activity android:name="com.nextcloud.client.errorhandling.ShowErrorActivity"
-            android:theme="@style/Theme.ownCloud.Toolbar"/>
+            android:theme="@style/Theme.ownCloud.Toolbar"
+            android:process=":crash"
+            android:excludeFromRecents="true"
+            android:finishOnTaskLaunch="true"
+            android:launchMode="singleInstance" />
         <activity android:name=".ui.activity.UploadListActivity" />
         <activity
             android:name=".ui.trashbin.TrashbinActivity"

+ 1 - 1
src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java

@@ -21,7 +21,7 @@ package com.nextcloud.client.appinfo;
 
 import com.owncloud.android.BuildConfig;
 
-public class AppInfoImpl implements AppInfo {
+class AppInfoImpl implements AppInfo {
 
     @Override
     public String getFormattedVersionCode() {

+ 32 - 23
src/main/java/com/nextcloud/client/errorhandling/ExceptionHandler.kt

@@ -3,9 +3,13 @@
  *
  *   @author LukeOwncloud
  *   @author AndyScherzinger
+ *   @author Tobias Kaminsky
+ *   @author Chris Narkiewicz
+ *
  *   Copyright (C) 2016 ownCloud Inc.
  *   Copyright (C) 2016 LukeOwncloud
  *   Copyright (C) 2019 Andy Scherzinger
+ *   Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -21,43 +25,48 @@
  */
 package com.nextcloud.client.errorhandling
 
-import android.app.Activity
+import android.content.Context
 import android.content.Intent
 import android.os.Build
-import android.util.Log
-import com.nextcloud.client.appinfo.AppInfoImpl
+import com.nextcloud.client.appinfo.AppInfo
 import com.owncloud.android.BuildConfig
 import com.owncloud.android.R
 import java.io.PrintWriter
 import java.io.StringWriter
-import kotlin.system.exitProcess
+import javax.inject.Provider
+
+class ExceptionHandler(
+    private val context: Context,
+    private val appInfoProvider: Provider<AppInfo?>,
+    private val defaultExceptionHandler: Thread.UncaughtExceptionHandler
+) : Thread.UncaughtExceptionHandler {
 
-class ExceptionHandler(private val context: Activity) : Thread.UncaughtExceptionHandler {
     companion object {
-        private val TAG = ExceptionHandler::class.java.simpleName
-        private val LINE_SEPARATOR = "\n"
-        private val STATUS = 1000
+        private const val LINE_SEPARATOR = "\n"
     }
 
     override fun uncaughtException(thread: Thread, exception: Throwable) {
-        Log.e(TAG, "ExceptionHandler caught UncaughtException", exception)
-        val stackTrace = StringWriter()
-        exception.printStackTrace(PrintWriter(stackTrace))
-
-        val errorReport = generateErrorReport(stackTrace.toString())
 
-        Log.e(TAG, "An exception was thrown and handled by ExceptionHandler:", exception)
-
-        val intent = Intent(context, ShowErrorActivity::class.java)
-        intent.putExtra(ShowErrorActivity.EXTRA_ERROR_TEXT, errorReport)
-        context.startActivity(intent)
-
-        android.os.Process.killProcess(android.os.Process.myPid())
-        exitProcess(STATUS)
+        @Suppress("TooGenericExceptionCaught") // this is exactly what we want here
+        try {
+            val stackTrace = StringWriter()
+            exception.printStackTrace(PrintWriter(stackTrace))
+            val errorReport = generateErrorReport(stackTrace.toString())
+            val intent = Intent(context, ShowErrorActivity::class.java)
+            intent.putExtra(ShowErrorActivity.EXTRA_ERROR_TEXT, errorReport)
+            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            context.startActivity(intent)
+            // Pass exception to OS for graceful handling - OS will report it via ADB
+            // and close all activities and services.
+            defaultExceptionHandler.uncaughtException(thread, exception)
+        } catch (fatalException: Exception) {
+            // do not recurse into custom handler if exception is thrown during
+            // exception handling. Pass this ultimate fatal exception to OS
+            defaultExceptionHandler.uncaughtException(thread, fatalException)
+        }
     }
 
     private fun generateErrorReport(stackTrace: String): String {
-        val appInfo = AppInfoImpl()
         val buildNumber = context.resources.getString(R.string.buildNumber)
 
         var buildNumberString = ""
@@ -73,7 +82,7 @@ class ExceptionHandler(private val context: Activity) : Thread.UncaughtException
             BuildConfig.APPLICATION_ID +
             LINE_SEPARATOR +
             "Version: " +
-            appInfo.formattedVersionCode +
+            (appInfoProvider.get()?.formattedVersionCode ?: "not available") +
             buildNumberString +
             LINE_SEPARATOR +
             "Build flavor: " +

+ 2 - 7
src/main/java/com/nextcloud/client/errorhandling/ShowErrorActivity.kt

@@ -51,10 +51,7 @@ class ShowErrorActivity : AppCompatActivity() {
         val snackbar = DisplayUtils.createSnackbar(
             error_page_container,
             R.string.error_report_issue_text, Snackbar.LENGTH_INDEFINITE)
-            .setAction(R.string.error_report_issue_action
-            ) { v ->
-                reportIssue()
-            }
+            .setAction(R.string.error_report_issue_action) { reportIssue() }
 
         val primaryColor = ThemeUtils.primaryColor(this)
         val fontColor = ThemeUtils.fontColor(this)
@@ -98,9 +95,7 @@ class ShowErrorActivity : AppCompatActivity() {
 
     override fun onOptionsItemSelected(item: MenuItem?): Boolean {
         return when (item?.itemId) {
-            R.id.error_share -> {
-                onClickedShare(); true
-            }
+            R.id.error_share -> { onClickedShare(); true }
             else -> super.onOptionsItemSelected(item)
         }
     }

+ 35 - 1
src/main/java/com/owncloud/android/MainApp.java

@@ -25,6 +25,8 @@ import android.Manifest;
 import android.accounts.Account;
 import android.annotation.SuppressLint;
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Application;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.ContentResolver;
@@ -46,6 +48,7 @@ import com.nextcloud.client.appinfo.AppInfo;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.di.ActivityInjector;
 import com.nextcloud.client.di.DaggerAppComponent;
+import com.nextcloud.client.errorhandling.ExceptionHandler;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.onboarding.OnboardingService;
 import com.nextcloud.client.preferences.AppPreferences;
@@ -166,9 +169,41 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         return powerManagementService;
     }
 
+    private String getAppProcessName() {
+        String processName = "";
+        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+            ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
+            final int ownPid = android.os.Process.myPid();
+            final List<ActivityManager.RunningAppProcessInfo> processes = manager.getRunningAppProcesses();
+            if (processes != null) {
+                for (ActivityManager.RunningAppProcessInfo info : processes) {
+                    if (info.pid == ownPid) {
+                        processName = info.processName;
+                        break;
+                    }
+                }
+            }
+        } else {
+            processName = Application.getProcessName();
+        }
+        return processName;
+    }
+
     @Override
     protected void attachBaseContext(Context base) {
         super.attachBaseContext(base);
+
+        // we don't want to handle crashes occuring inside crash reporter activity/process;
+        // let the platform deal with those
+        final boolean isCrashReportingProcess = getAppProcessName().endsWith(":crash");
+        if (!isCrashReportingProcess) {
+            Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler();
+            final ExceptionHandler crashReporter = new ExceptionHandler(this,
+                                                                        () -> appInfo,
+                                                                        defaultPlatformHandler);
+            Thread.setDefaultUncaughtExceptionHandler(crashReporter);
+        }
+
         initGlobalContext(this);
         DaggerAppComponent.builder()
             .application(this)
@@ -180,7 +215,6 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
     @Override
     public void onCreate() {
         super.onCreate();
-
         registerActivityLifecycleCallbacks(new ActivityInjector());
 
         Thread t = new Thread(() -> {

+ 0 - 9
src/main/java/com/owncloud/android/ui/activity/BaseActivity.java

@@ -11,7 +11,6 @@ import android.os.Handler;
 
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.errorhandling.ExceptionHandler;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
@@ -20,7 +19,6 @@ import com.owncloud.android.lib.resources.status.OCCapability;
 
 import javax.inject.Inject;
 
-import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
 /**
@@ -60,13 +58,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
         return accountManager;
     }
 
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
-    }
-
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);