diff --git a/.gitignore b/.gitignore index acb6bc11..35b2c5b7 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,5 @@ hs_err_pid* obj/ .externalNativeBuild **/.cxx + +backtrace-library/src/main/jniLibs/** diff --git a/backtrace-library/src/main/cpp/backends/backend.cpp b/backtrace-library/src/main/cpp/backends/backend.cpp index 0e8e4bf0..140301cf 100644 --- a/backtrace-library/src/main/cpp/backends/backend.cpp +++ b/backtrace-library/src/main/cpp/backends/backend.cpp @@ -82,4 +82,32 @@ void Disable() { "Disable not supported on this backend"); #endif } + +bool EnableCrashLoopDetection() { +#ifdef CRASHPAD_BACKEND + return EnableCrashLoopDetectionCrashpad(); +#elif BREAKPAD_BACKEND + __android_log_print(ANDROID_LOG_ERROR, "Backtrace-Android", "EnableCrashLoopDetection not supported on this backend"); + return false; +#endif +} + +bool IsSafeModeRequired() { +#ifdef CRASHPAD_BACKEND + return IsSafeModeRequiredCrashpad(); +#elif BREAKPAD_BACKEND + __android_log_print(ANDROID_LOG_ERROR, "Backtrace-Android", "IsSafeModeRequired not supported on this backend"); + return false; +#endif +} + +int ConsecutiveCrashesCount() { +#ifdef CRASHPAD_BACKEND + return ConsecutiveCrashesCountCrashpad(); +#elif BREAKPAD_BACKEND + __android_log_print(ANDROID_LOG_ERROR, "Backtrace-Android", "ConsecutiveCrashesCount not supported on this backend"); + return 0; +#endif +} + } \ No newline at end of file diff --git a/backtrace-library/src/main/cpp/backends/crashpad-backend.cpp b/backtrace-library/src/main/cpp/backends/crashpad-backend.cpp index 08cff7b1..741aa947 100644 --- a/backtrace-library/src/main/cpp/backends/crashpad-backend.cpp +++ b/backtrace-library/src/main/cpp/backends/crashpad-backend.cpp @@ -11,6 +11,7 @@ extern std::atomic_bool disabled; static crashpad::CrashpadClient *client; static std::unique_ptr database; +static int consecutive_crashes_count = 0; bool InitializeCrashpad(jstring url, jstring database_path, jstring handler_path, @@ -121,8 +122,11 @@ bool InitializeCrashpad(jstring url, // Start crash handler client = new crashpad::CrashpadClient(); - initialized = client->StartHandlerAtCrash(handler, db, db, backtraceUrl, attributes, - arguments); + // Get consecutive crashes count BEFORE any handler started, + // as it writes extra line into CSV, what leads to getting 0 for each next ConsecutiveCrashesCount call + consecutive_crashes_count = crashpad::CrashpadClient::ConsecutiveCrashesCount(db); + + initialized = client->StartHandlerAtCrash(handler, db, db, backtraceUrl, attributes, arguments); env->ReleaseStringUTFChars(url, backtraceUrl); env->ReleaseStringUTFChars(handler_path, handlerPath); @@ -226,3 +230,19 @@ void ReEnableCrashpad() { disabled = false; } } + +bool EnableCrashLoopDetectionCrashpad() { + if (client != nullptr) { + return client->EnableCrashLoopDetection(); + } else { + return false; + } +} + +bool IsSafeModeRequiredCrashpad() { + return consecutive_crashes_count >= 5; +} + +int ConsecutiveCrashesCountCrashpad() { + return consecutive_crashes_count; +} diff --git a/backtrace-library/src/main/cpp/backtrace-native.cpp b/backtrace-library/src/main/cpp/backtrace-native.cpp index eff31880..f9020250 100644 --- a/backtrace-library/src/main/cpp/backtrace-native.cpp +++ b/backtrace-library/src/main/cpp/backtrace-native.cpp @@ -135,4 +135,19 @@ Java_backtraceio_library_BacktraceDatabase_disable(JNIEnv *env, jobject thiz) { Disable(); } +JNIEXPORT jboolean JNICALL +Java_backtraceio_library_BacktraceDatabase_EnableCrashLoopDetection(JNIEnv *env, jclass clazz) { + return EnableCrashLoopDetection(); +} + +JNIEXPORT jboolean JNICALL +Java_backtraceio_library_BacktraceDatabase_IsSafeModeRequired(JNIEnv *env, jclass clazz) { + return IsSafeModeRequired(); +} + +JNIEXPORT int JNICALL +Java_backtraceio_library_BacktraceDatabase_ConsecutiveCrashesCount(JNIEnv *env, jclass clazz) { + return ConsecutiveCrashesCount(); +} + } diff --git a/backtrace-library/src/main/cpp/crashpad b/backtrace-library/src/main/cpp/crashpad index 3ce0e54c..3f6a78bb 160000 --- a/backtrace-library/src/main/cpp/crashpad +++ b/backtrace-library/src/main/cpp/crashpad @@ -1 +1 @@ -Subproject commit 3ce0e54c6e3341725c5ab1b1a3443835e08b8058 +Subproject commit 3f6a78bb13120d61ad7b76af2824aa712db4e80c diff --git a/backtrace-library/src/main/cpp/include/backend.h b/backtrace-library/src/main/cpp/include/backend.h index 3b0543e4..27916fd2 100644 --- a/backtrace-library/src/main/cpp/include/backend.h +++ b/backtrace-library/src/main/cpp/include/backend.h @@ -19,6 +19,12 @@ void DumpWithoutCrash(jstring message, jboolean set_main_thread_as_faulting_thre void AddAttribute(jstring key, jstring value); void Disable(); + +bool EnableCrashLoopDetection(); + +bool IsSafeModeRequired(); + +int ConsecutiveCrashesCount(); } #endif //BACKTRACE_ANDROID_BACKEND_H diff --git a/backtrace-library/src/main/cpp/include/crashpad-backend.h b/backtrace-library/src/main/cpp/include/crashpad-backend.h index 8a5c27ee..83340cd4 100644 --- a/backtrace-library/src/main/cpp/include/crashpad-backend.h +++ b/backtrace-library/src/main/cpp/include/crashpad-backend.h @@ -28,4 +28,8 @@ void DisableCrashpad(); void ReEnableCrashpad(); +bool EnableCrashLoopDetectionCrashpad(); +bool IsSafeModeRequiredCrashpad(); +int ConsecutiveCrashesCountCrashpad(); + #endif //BACKTRACE_ANDROID_CRASHPAD_BACKEND_H diff --git a/backtrace-library/src/main/java/backtraceio/library/BacktraceClient.java b/backtrace-library/src/main/java/backtraceio/library/BacktraceClient.java index 0256678e..6e9a360a 100644 --- a/backtrace-library/src/main/java/backtraceio/library/BacktraceClient.java +++ b/backtrace-library/src/main/java/backtraceio/library/BacktraceClient.java @@ -24,6 +24,28 @@ public class BacktraceClient extends BacktraceBase { */ private BacktraceANRWatchdog anrWatchdog; + /** + * Enables Crash loop detection to verify if the application is in the crash loop. + */ + public static boolean EnableCrashLoopDetection() { + return BacktraceDatabase.EnableCrashLoopDetection(); + } + + /** + * Determine if application requires a safe mode to launch. + */ + public static boolean IsSafeModeRequired() { + return BacktraceDatabase.IsSafeModeRequired(); + } + + /** + * Returns the number of consecutive application crashes + */ + public static int ConsecutiveCrashesCount() { + return BacktraceDatabase.ConsecutiveCrashesCount(); + } + + /** * Initializing Backtrace client instance with BacktraceCredentials * diff --git a/backtrace-library/src/main/java/backtraceio/library/BacktraceDatabase.java b/backtrace-library/src/main/java/backtraceio/library/BacktraceDatabase.java index 3a8bdc1f..530e400a 100644 --- a/backtrace-library/src/main/java/backtraceio/library/BacktraceDatabase.java +++ b/backtrace-library/src/main/java/backtraceio/library/BacktraceDatabase.java @@ -464,4 +464,8 @@ private boolean validateDatabaseSize() { public long getDatabaseSize() { return backtraceDatabaseContext.getDatabaseSize(); } + + public static native boolean EnableCrashLoopDetection(); + public static native boolean IsSafeModeRequired(); + public static native int ConsecutiveCrashesCount(); } diff --git a/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java b/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java index a9eaae11..026c678e 100644 --- a/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java +++ b/example-app/src/main/java/backtraceio/backtraceio/MainActivity.java @@ -1,8 +1,12 @@ package backtraceio.backtraceio; +import static backtraceio.backtraceio.BuildConfig.BACKTRACE_SUBMISSION_URL; + import android.content.Context; import android.os.Bundle; + import androidx.appcompat.app.AppCompatActivity; + import android.system.ErrnoException; import android.system.Os; import android.util.Log; @@ -27,6 +31,7 @@ import backtraceio.library.enums.BacktraceBreadcrumbType; import backtraceio.library.enums.database.RetryBehavior; import backtraceio.library.enums.database.RetryOrder; +import backtraceio.library.models.BacktraceData; import backtraceio.library.models.BacktraceExceptionHandler; import backtraceio.library.models.BacktraceMetricsSettings; import backtraceio.library.models.database.BacktraceDatabaseSettings; @@ -41,6 +46,7 @@ public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); + System.loadLibrary("backtrace-native"); } @Override @@ -48,11 +54,25 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + // Crash Loop Detector example + BacktraceClient.EnableCrashLoopDetection(); + boolean isCLSafeModeReq = BacktraceClient.IsSafeModeRequired(); + int crashesCount = BacktraceClient.ConsecutiveCrashesCount(); + Log.i("BacktraceAndroid", String.format("ConsecutiveCrashesCount: %d", crashesCount)); + // Set this value in your local.properties if (BuildConfig.BACKTRACE_SUBMISSION_URL != null) { backtraceClient = initializeBacktrace(BuildConfig.BACKTRACE_SUBMISSION_URL); } + + View viewBackground = findViewById(R.id.viewBackground); + if (viewBackground != null) { + viewBackground.setBackgroundColor(isCLSafeModeReq + ? getResources().getColor(R.color.colorAccent) + : getResources().getColor(R.color.colorWhite)); + } + symlinkAndWriteFile(); } @@ -89,7 +109,7 @@ private BacktraceClient initializeBacktrace(final String submissionUrl) { put("custom.attribute", "My Custom Attribute"); }}; - List attachments = new ArrayList(){{ + List attachments = new ArrayList() {{ add(context.getFilesDir() + "/" + "myCustomFile.txt"); }}; @@ -113,29 +133,28 @@ private BacktraceClient initializeBacktrace(final String submissionUrl) { public native void cppCrash(); public native boolean registerNativeBreadcrumbs(BacktraceBase backtraceBase); + public native boolean addNativeBreadcrumb(); + public native boolean addNativeBreadcrumbUserError(); + public native void cleanupNativeBreadcrumbHandler(); private List equippedItems; - public List getWarriorArmor() - { + public List getWarriorArmor() { return new ArrayList(Arrays.asList("Tough Boots", "Strong Sword", "Sturdy Shield", "Magic Wand")); } - int findEquipmentIndex(List armor, String equipment) - { + int findEquipmentIndex(List armor, String equipment) { return armor.indexOf(equipment); } - void removeEquipment(List armor, int index) - { + void removeEquipment(List armor, int index) { armor.remove(index); } - void equipItem(List armor, int index) - { + void equipItem(List armor, int index) { equippedItems.add(armor.get(index)); } @@ -154,7 +173,7 @@ public void handledException(View view) { public void getSaveData() throws IOException { // I know for sure this file is there (spoiler alert, it's not) - File mySaveData = new File("mySave.sav"); + File mySaveData = new File("mySave.sav"); FileReader mySaveDataReader = new FileReader(mySaveData); char[] saveDataBuffer = new char[255]; mySaveDataReader.read(saveDataBuffer); @@ -202,7 +221,7 @@ private void writeMyCustomFile(String filePath) { outputStreamWriter.write(fileData); outputStreamWriter.close(); } catch (IOException e) { - Log.e("BacktraceAndroid", "File write failed due to: " + e.toString()); + Log.e("BacktraceAndroid", "File write failed due to: " + e.toString()); } } diff --git a/example-app/src/main/res/layout/activity_main.xml b/example-app/src/main/res/layout/activity_main.xml index a12d0567..33933cb8 100644 --- a/example-app/src/main/res/layout/activity_main.xml +++ b/example-app/src/main/res/layout/activity_main.xml @@ -1,11 +1,17 @@ - +