From eb24e7ff6011e55902ba7c43729b8d42fa4486df Mon Sep 17 00:00:00 2001 From: "Emmanuele (WolfSolver)" Date: Fri, 20 Dec 2024 17:19:21 +0100 Subject: [PATCH 1/3] First test to handle generic crash --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 10 +++ .../ex/common/MmxBaseFragmentActivity.java | 3 + .../manager/ex/errorhandle/AuthActivity.java | 70 +++++++++++++++ .../errorhandle/LoggingExceptionHandler.java | 42 +++++++++ .../LoggingExceptionHandlerDetail.java | 70 +++++++++++++++ .../ex/errorhandle/MyExceptionHandler.java | 54 +++++++++++ .../money/manager/ex/home/MainActivity.java | 10 +++ app/src/main/res/layout/activity_auth.xml | 90 +++++++++++++++++++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 9 ++ 11 files changed, 362 insertions(+) create mode 100644 app/src/main/java/com/money/manager/ex/errorhandle/AuthActivity.java create mode 100644 app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandler.java create mode 100644 app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandlerDetail.java create mode 100644 app/src/main/java/com/money/manager/ex/errorhandle/MyExceptionHandler.java create mode 100644 app/src/main/res/layout/activity_auth.xml diff --git a/app/build.gradle b/app/build.gradle index d6d3a2b8f1..e8911adb0a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -204,6 +204,7 @@ dependencies { } // Parceler implementation 'org.parceler:parceler-api:1.1.13' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' annotationProcessor 'org.parceler:parceler:1.1.13' // Dagger 2 implementation 'com.google.dagger:dagger:2.53.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 673a95d4cd..4201790b24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -432,6 +432,16 @@ android:resource="@dimen/app_minimumsize_h" /> + + + + + + diff --git a/app/src/main/java/com/money/manager/ex/common/MmxBaseFragmentActivity.java b/app/src/main/java/com/money/manager/ex/common/MmxBaseFragmentActivity.java index 976dc57a0d..a2b1fe6415 100644 --- a/app/src/main/java/com/money/manager/ex/common/MmxBaseFragmentActivity.java +++ b/app/src/main/java/com/money/manager/ex/common/MmxBaseFragmentActivity.java @@ -34,6 +34,7 @@ import com.money.manager.ex.R; import com.money.manager.ex.core.Core; import com.money.manager.ex.core.UIHelper; +import com.money.manager.ex.errorhandle.MyExceptionHandler; import com.money.manager.ex.log.ErrorRaisedEvent; import com.money.manager.ex.settings.AppSettings; @@ -71,6 +72,8 @@ protected void onCreate(Bundle savedInstanceState) { this.compositeSubscription = new CompositeSubscription(); super.onCreate(savedInstanceState); + new MyExceptionHandler(MmxBaseFragmentActivity.this); + // Initialize the ActivityResultLauncher openDocumentLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { diff --git a/app/src/main/java/com/money/manager/ex/errorhandle/AuthActivity.java b/app/src/main/java/com/money/manager/ex/errorhandle/AuthActivity.java new file mode 100644 index 0000000000..eb9087ffd3 --- /dev/null +++ b/app/src/main/java/com/money/manager/ex/errorhandle/AuthActivity.java @@ -0,0 +1,70 @@ +package com.money.manager.ex.errorhandle; + + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import com.money.manager.ex.R; +import com.money.manager.ex.common.MmxBaseFragmentActivity; + +import org.w3c.dom.Text; + +public class AuthActivity extends AppCompatActivity { + Intent intent = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (intent != null) return; + + setContentView(R.layout.activity_auth); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + handleIntent(); + } + + void handleIntent() { + if (intent != null) return; + + intent = getIntent(); + if (intent == null) return; + if ( ! getIntent().getAction().equals("HANDLE_ERROR") ) { + return; + } + + String source = intent.getStringExtra("ERROR"); + String report = intent.getStringExtra(Intent.EXTRA_TEXT); + + TextView reportUI = findViewById(R.id.editTextReport); + reportUI.setText(report); + + Button openIssue = findViewById(R.id.buttonOpenIssue); + openIssue.setOnClickListener(v -> { + String body = "[Put here your description]\n" + report; + String uri = Uri.parse("https://github.com/moneymanagerex/android-money-manager-ex/issues/new") + .buildUpon() + .appendQueryParameter("label", "bug") +// .appendQueryParameter("title", "Your title here") + .appendQueryParameter("body", body) + .build().toString(); + Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); + this.startActivity(myIntent); + }); + + Button cancel = findViewById(R.id.buttonQuit); + cancel.setOnClickListener(v -> { + this.finish(); + + // make sure we die, otherwise the app will hang ... + android.os.Process.killProcess(android.os.Process.myPid()); + // sometimes on older android version killProcess wasn't enough -- strategy pattern should be considered here + System.exit(0); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandler.java b/app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandler.java new file mode 100644 index 0000000000..eb171b8c48 --- /dev/null +++ b/app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandler.java @@ -0,0 +1,42 @@ +package com.money.manager.ex.errorhandle; + + +import android.content.Context; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Application based Exception Handler, basically a Singleton. + * + * As this exception handler is attached in the Android Application, + * we can't start activities here but we could easily log the errors + * and send them later ... . + */ +public class LoggingExceptionHandler implements Thread.UncaughtExceptionHandler { + private final static String TAG = LoggingExceptionHandler.class.getSimpleName(); + + private final Context context; + private final Thread.UncaughtExceptionHandler rootHandler; + + public LoggingExceptionHandler(Context context) { + this.context = context; + // we should store the current exception handler -- to invoke it for all not handled exceptions ... + rootHandler = Thread.getDefaultUncaughtExceptionHandler(); + // we replace the exception handler now with us -- we will properly dispatch the exceptions ... + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(final Thread thread, final Throwable ex) { + rootHandler.uncaughtException(thread, ex); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandlerDetail.java b/app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandlerDetail.java new file mode 100644 index 0000000000..82db62f491 --- /dev/null +++ b/app/src/main/java/com/money/manager/ex/errorhandle/LoggingExceptionHandlerDetail.java @@ -0,0 +1,70 @@ +package com.money.manager.ex.errorhandle; + +import android.content.Intent; +import android.os.Build; +import android.util.Log; + +public class LoggingExceptionHandlerDetail { + + public static String generateReport(final Thread t, final Throwable e) { + StackTraceElement[] arr = e.getStackTrace(); + final StringBuffer report = new StringBuffer(e.toString()); + final String lineSeperator = "-------------------------------\n\n"; + report.append("\n\n"); + report.append("--------- Stack trace ---------\n\n"); + for (int i = 0; i < arr.length; i++) { + report.append( " "); + report.append(arr[i].toString()); + report.append("\n"); + } + report.append(lineSeperator); + // If the exception was thrown in a background thread inside + // AsyncTask, then the actual exception can be found with getCause + report.append("--------- Cause ---------\n\n"); + Throwable cause = e.getCause(); + if (cause != null) { + report.append(cause.toString()); + report.append("\n\n"); + arr = cause.getStackTrace(); + for (int i = 0; i < arr.length; i++) { + report.append(" "); + report.append(arr[i].toString()); + report.append("\n"); + } + } + // Getting the Device brand,model and sdk verion details. + report.append(lineSeperator); + report.append("--------- Device ---------\n\n"); + report.append("Brand: "); + report.append(Build.BRAND); + report.append("\n"); + report.append("Device: "); + report.append(Build.DEVICE); + report.append("\n"); + report.append("Model: "); + report.append(Build.MODEL); + report.append("\n"); + report.append("Id: "); + report.append(Build.ID); + report.append("\n"); + report.append("Product: "); + report.append(Build.PRODUCT); + report.append("\n"); + report.append(lineSeperator); + report.append("--------- Firmware ---------\n\n"); + report.append("SDK: "); + report.append(Build.VERSION.SDK); + report.append("\n"); + report.append("Release: "); + report.append(Build.VERSION.RELEASE); + report.append("\n"); + report.append("Incremental: "); + report.append(Build.VERSION.INCREMENTAL); + report.append("\n"); + report.append(lineSeperator); + +// Log.e("Report ::", report.toString()); + + return report.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/money/manager/ex/errorhandle/MyExceptionHandler.java b/app/src/main/java/com/money/manager/ex/errorhandle/MyExceptionHandler.java new file mode 100644 index 0000000000..1ad5a6c639 --- /dev/null +++ b/app/src/main/java/com/money/manager/ex/errorhandle/MyExceptionHandler.java @@ -0,0 +1,54 @@ +package com.money.manager.ex.errorhandle; + + +import static androidx.core.content.ContextCompat.startActivity; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.util.Log; +import android.widget.Toast; + +import com.money.manager.ex.Constants; + +import timber.log.Timber; + +/** + * Activity based Exception handler ... + */ +public class MyExceptionHandler implements Thread.UncaughtExceptionHandler { + + public static final String EXTRA_MY_EXCEPTION_HANDLER = "EXTRA_MY_EXCEPTION_HANDLER"; + private final Activity context; + private final Thread.UncaughtExceptionHandler rootHandler; + + public MyExceptionHandler(Activity context) { + this.context = context; + // we should store the current exception handler -- to invoke it for all not handled exceptions ... + rootHandler = Thread.getDefaultUncaughtExceptionHandler(); + // we replace the exception handler now with us -- we will properly dispatch the exceptions ... + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(final Thread thread, final Throwable ex) { + // note we can't just open in Android an dialog etc. we have to use Intents here + // http://stackoverflow.com/questions/13416879/show-a-dialog-in-thread-setdefaultuncaughtexceptionhandler + + Intent registerActivity = new Intent(context, AuthActivity.class); + registerActivity.setAction("HANDLE_ERROR"); + registerActivity.putExtra("ERROR", MyExceptionHandler.class.getName()); + registerActivity.putExtra(Intent.EXTRA_TEXT, LoggingExceptionHandlerDetail.generateReport(thread,ex)); + registerActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + registerActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + context.startActivity(registerActivity); + + // make sure we die, otherwise the app will hang ... + android.os.Process.killProcess(android.os.Process.myPid()); + // sometimes on older android version killProcess wasn't enough -- strategy pattern should be considered here + System.exit(0); + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/money/manager/ex/home/MainActivity.java b/app/src/main/java/com/money/manager/ex/home/MainActivity.java index 8dc995e158..802a1b930c 100644 --- a/app/src/main/java/com/money/manager/ex/home/MainActivity.java +++ b/app/src/main/java/com/money/manager/ex/home/MainActivity.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.text.Html; import android.text.TextUtils; +import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; @@ -54,6 +55,9 @@ import com.money.manager.ex.Constants; import com.money.manager.ex.HelpActivity; import com.money.manager.ex.MmexApplication; +import com.money.manager.ex.errorhandle.AuthActivity; +import com.money.manager.ex.errorhandle.LoggingExceptionHandler; +import com.money.manager.ex.errorhandle.MyExceptionHandler; import com.money.manager.ex.nestedcategory.NestedCategoryListFragment; import com.money.manager.ex.passcode.PasscodeActivity; import com.money.manager.ex.R; @@ -172,6 +176,12 @@ public static void setRestartActivity(boolean mRestart) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + new LoggingExceptionHandler(this); + + // test + int test = 0; + if (test == 1) throw new IllegalStateException("This state is not allowed."); + if ( test == 2) {test = 0; test = 1 / test;}; MmexApplication.getApp().iocComponent.inject(this); diff --git a/app/src/main/res/layout/activity_auth.xml b/app/src/main/res/layout/activity_auth.xml new file mode 100644 index 0000000000..93d18fe2a7 --- /dev/null +++ b/app/src/main/res/layout/activity_auth.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + +