diff --git a/README.md b/README.md
index 64db861..8edc455 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,22 @@
-# CrashX
+
+
+# CrashX [](https://android-arsenal.com/api?level=15) [](https://snyk.io/test/github/TutorialsAndroid/CrashX?targetFile=library%2Fbuild.gradle) [](https://android-arsenal.com/details/1/7581) [](https://opensource.org/licenses/Apache-2.0) [](https://GitHub.com/TutorialsAndroid/crashx)
This library allows launching a crash activity when the app crashes, instead of showing the hated "Unfortunately, X has stopped" dialog.
-[](https://android-arsenal.com/api?level=15)
+**Library Availbale at JitPack.io**
+
+[](https://jitpack.io/#TutorialsAndroid/crashx)
-[](https://snyk.io/test/github/TutorialsAndroid/CrashX?targetFile=library%2Fbuild.gradle)
-**Library Availbale at JitPack.io**
+## And Don't Forget To Follow Me On Instagram
-[](https://jitpack.io/#TutorialsAndroid/CrashX)
+
Follow me on instagram to stay up-to-date https://instagram.com/a.masram444
+
**Sample Screen**
-
+
## How to use
@@ -30,7 +34,7 @@ Add it in your root build.gradle at the end of repositories:
Step 2. Add the dependency
dependencies {
- implementation 'com.github.TutorialsAndroid:CrashX:v0.1'
+ implementation 'com.github.TutorialsAndroid:crashx:v6.0.19'
}
...and you are done!
@@ -148,7 +152,7 @@ restartActivity(Class extends Activity>);
```
> This method sets the activity that must be launched by the error activity when the user presses the button to restart the app.
> If you don't set it (or set it to null), the library will use the first activity on your manifest that has an intent-filter with action
-> `com.kinda.crash.RESTART`, and if there is none, the default launchable activity on your app.
+> `com.developer.crashx.RESTART`, and if there is none, the default launchable activity on your app.
> If no launchable activity can be found and you didn't specify any, the "restart app" button will become a "close app" button,
> even if `showRestartButton` is set to `true`.
>
@@ -156,7 +160,7 @@ restartActivity(Class extends Activity>);
> ```xml
>
>
->
+>
>
> ```
@@ -166,13 +170,13 @@ errorActivity(Class extends Activity>);
> This method allows you to set a custom error activity to be launched, instead of the default one.
> Use it if you need further customization that is not just strings, colors or themes (see below).
> If you don't set it (or set it to null), the library will use the first activity on your manifest that has an intent-filter with action
-> `com.kinda.crash.ERROR`, and if there is none, a default error activity from the library.
+> `com.developer.crashx.ERROR`, and if there is none, a default error activity from the library.
> If you use this, the activity **must** be declared in your `AndroidManifest.xml`, with `process` set to `:error_activity`.
>
> Example:
> ```xml
> android:name="com.kinda.crash.sample.CustomErrorActivity"
+> android:name="com.developer.crashx.sample.CustomErrorActivity"
> android:label="@string/error_title"
> android:process=":error_activity" />
> ```
@@ -181,7 +185,7 @@ errorActivity(Class extends Activity>);
> ```xml
>
>
->
+>
>
> ```
@@ -204,7 +208,7 @@ If you want to specify a specific theme only for the error activity, you can do
```xml
```
diff --git a/build.gradle b/build.gradle
index 5c2f118..000c19f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,16 +1,11 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
buildscript {
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
}
}
@@ -18,6 +13,10 @@ allprojects {
repositories {
google()
jcenter()
+ mavenCentral()
+ maven {
+ url 'https://jitpack.io'
+ }
}
}
diff --git a/gradle.properties b/gradle.properties
index 5d08ba7..21ee4a5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,4 +15,6 @@
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+# org.gradle.parallel=true
+android.enableJetifier=true
+android.useAndroidX=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 905d23b..3cece4b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Nov 13 17:16:27 CET 2017
+#Sat Aug 29 18:42:59 IST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/images/device-2019-03-19-154405.png b/images/device-2019-03-19-154405.png
index b7431b1..d6bac46 100644
Binary files a/images/device-2019-03-19-154405.png and b/images/device-2019-03-19-154405.png differ
diff --git a/images/device-2019-03-19-154421.png b/images/device-2019-03-19-154421.png
deleted file mode 100644
index b6cc52d..0000000
Binary files a/images/device-2019-03-19-154421.png and /dev/null differ
diff --git a/library/build.gradle b/library/build.gradle
index 47b01f8..7732a5b 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -4,14 +4,14 @@ apply plugin: 'com.github.dcendents.android-maven'
group='com.github.TutorialsAndroid'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
resourcePrefix 'crash_'
defaultConfig {
minSdkVersion 15
- targetSdkVersion 28
- versionCode 1
- versionName "1.0-SNAPSHOT"
+ targetSdkVersion 29
+ versionCode 8
+ versionName "5.0.19"
}
buildTypes {
release {
@@ -19,10 +19,20 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
-
- implementation 'com.android.support:appcompat-v7:28.0.0'
+
+ //ANDROIDX JETPACK LIBRARIES
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'com.google.android.material:material:1.2.0'
+ implementation 'androidx.preference:preference:1.1.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
}
\ No newline at end of file
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
index ec8921c..e34625e 100644
--- a/library/src/main/AndroidManifest.xml
+++ b/library/src/main/AndroidManifest.xml
@@ -1,13 +1,13 @@
+ package="com.developer.crashx">
diff --git a/library/src/main/java/com/kinda/crash/CrashActivity.java b/library/src/main/java/com/developer/crashx/CrashActivity.java
similarity index 54%
rename from library/src/main/java/com/kinda/crash/CrashActivity.java
rename to library/src/main/java/com/developer/crashx/CrashActivity.java
index dfab696..bf668eb 100644
--- a/library/src/main/java/com/kinda/crash/CrashActivity.java
+++ b/library/src/main/java/com/developer/crashx/CrashActivity.java
@@ -1,4 +1,4 @@
-package com.kinda.crash;
+package com.developer.crashx;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -11,11 +11,10 @@
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import android.util.Log;
-
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
@@ -27,25 +26,28 @@
import java.util.Deque;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import com.developer.crashx.activity.DefaultErrorActivity;
+import com.developer.crashx.config.CrashConfig;
-import com.kinda.crash.activity.DefaultErrorActivity;
-import com.kinda.crash.config.CrashConfig;
-
+/**
+ * @author tkdco , TutorialsAndroid
+ */
public final class CrashActivity {
private final static String TAG = "CrashActivity";
//Extras passed to the error activity
- private static final String EXTRA_CONFIG = "com.kinda.crash.EXTRA_CONFIG";
- private static final String EXTRA_STACK_TRACE = "com.kinda.crash.EXTRA_STACK_TRACE";
- private static final String EXTRA_ACTIVITY_LOG = "com.kinda.crash.EXTRA_ACTIVITY_LOG";
+ private static final String EXTRA_CONFIG = "com.developer.crashx.EXTRA_CONFIG";
+ private static final String EXTRA_STACK_TRACE = "com.developer.crashx.EXTRA_STACK_TRACE";
+ private static final String EXTRA_ACTIVITY_LOG = "com.developer.crashx.EXTRA_ACTIVITY_LOG";
//General constants
- private static final String INTENT_ACTION_ERROR_ACTIVITY = "com.kinda.crash.ERROR";
- private static final String INTENT_ACTION_RESTART_ACTIVITY = "com.kinda.crash.RESTART";
- private static final String CRASH_HANDLER_PACKAGE_NAME = "com.kinda.crash.";
+ private static final String INTENT_ACTION_ERROR_ACTIVITY = "com.developer.crashx.ERROR";
+ private static final String INTENT_ACTION_RESTART_ACTIVITY = "com.developer.crashx.RESTART";
+ private static final String CRASH_HANDLER_PACKAGE_NAME = "com.developer.crashx.";
private static final String DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os";
private static final int MAX_STACK_TRACE_SIZE = 131071; //128 KB - 1
private static final int MAX_ACTIVITIES_IN_LOG = 50;
@@ -55,19 +57,12 @@ public final class CrashActivity {
private static final String SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp";
//Internal variables
- @SuppressLint("StaticFieldLeak") //This is an application-wide component
private static Application application;
private static CrashConfig config = new CrashConfig();
private static final Deque activityLog = new ArrayDeque<>(MAX_ACTIVITIES_IN_LOG);
private static WeakReference lastActivityCreated = new WeakReference<>(null);
private static boolean isInBackground = true;
-
- /**
- * Installs CrashActivity on the application using the default error activity.
- *
- * @param context Context to use for obtaining the ApplicationContext. Must not be null.
- */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static void install(@Nullable final Context context) {
try {
@@ -86,92 +81,78 @@ public static void install(@Nullable final Context context) {
application = (Application) context.getApplicationContext();
- //We define a default exception handler that does what we want so it can be called from Crashlytics/ACRA
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(Thread thread, final Throwable throwable) {
- if (config.isEnabled()) {
- Log.e(TAG, "App has crashed, executing CrashActivity's UncaughtExceptionHandler", throwable);
+ Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
+ if (config.isEnabled()) {
+ Log.e(TAG, "App has crashed, executing CrashActivity's UncaughtExceptionHandler", throwable);
+
+ if (hasCrashedInTheLastSeconds(application)) {
+ Log.e(TAG, "App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?", throwable);
+ if (oldHandler != null) {
+ oldHandler.uncaughtException(thread, throwable);
+ return;
+ }
+ } else {
+ setLastCrashTimestamp(application, new Date().getTime());
+
+ Class extends Activity> errorActivityClass = config.getErrorActivityClass();
- if (hasCrashedInTheLastSeconds(application)) {
- Log.e(TAG, "App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?", throwable);
+ if (errorActivityClass == null) {
+ errorActivityClass = guessErrorActivityClass(application);
+ }
+
+ if (isStackTraceLikelyConflictive(throwable, errorActivityClass)) {
+ Log.e(TAG, "Your application class or your error activity have crashed, the custom activity will not be launched!");
if (oldHandler != null) {
oldHandler.uncaughtException(thread, throwable);
return;
}
- } else {
- setLastCrashTimestamp(application, new Date().getTime());
+ } else if (config.getBackgroundMode() == CrashConfig.BACKGROUND_MODE_SHOW_CUSTOM || !isInBackground) {
- Class extends Activity> errorActivityClass = config.getErrorActivityClass();
+ final Intent intent = new Intent(application, errorActivityClass);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ throwable.printStackTrace(pw);
+ String stackTraceString = sw.toString();
- if (errorActivityClass == null) {
- errorActivityClass = guessErrorActivityClass(application);
+ if (stackTraceString.length() > MAX_STACK_TRACE_SIZE) {
+ String disclaimer = " [stack trace too large]";
+ stackTraceString = stackTraceString.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;
}
+ intent.putExtra(EXTRA_STACK_TRACE, stackTraceString);
- if (isStackTraceLikelyConflictive(throwable, errorActivityClass)) {
- Log.e(TAG, "Your application class or your error activity have crashed, the custom activity will not be launched!");
- if (oldHandler != null) {
- oldHandler.uncaughtException(thread, throwable);
- return;
- }
- } else if (config.getBackgroundMode() == CrashConfig.BACKGROUND_MODE_SHOW_CUSTOM || !isInBackground) {
-
- final Intent intent = new Intent(application, errorActivityClass);
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- throwable.printStackTrace(pw);
- String stackTraceString = sw.toString();
-
- //Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
- //The limit is 1MB on Android but some devices seem to have it lower.
- //See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
- //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
- if (stackTraceString.length() > MAX_STACK_TRACE_SIZE) {
- String disclaimer = " [stack trace too large]";
- stackTraceString = stackTraceString.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;
- }
- intent.putExtra(EXTRA_STACK_TRACE, stackTraceString);
-
- if (config.isTrackActivities()) {
- StringBuilder activityLogStringBuilder = new StringBuilder();
- while (!activityLog.isEmpty()) {
- activityLogStringBuilder.append(activityLog.poll());
- }
- intent.putExtra(EXTRA_ACTIVITY_LOG, activityLogStringBuilder.toString());
+ if (config.isTrackActivities()) {
+ StringBuilder activityLogStringBuilder = new StringBuilder();
+ while (!activityLog.isEmpty()) {
+ activityLogStringBuilder.append(activityLog.poll());
}
+ intent.putExtra(EXTRA_ACTIVITY_LOG, activityLogStringBuilder.toString());
+ }
- if (config.isShowRestartButton() && config.getRestartActivityClass() == null) {
- //We can set the restartActivityClass because the app will terminate right now,
- //and when relaunched, will be null again by default.
- config.setRestartActivityClass(guessRestartActivityClass(application));
- }
+ if (config.isShowRestartButton() && config.getRestartActivityClass() == null) {
+ config.setRestartActivityClass(guessRestartActivityClass(application));
+ }
- intent.putExtra(EXTRA_CONFIG, config);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- if (config.getEventListener() != null) {
- config.getEventListener().onLaunchErrorActivity();
- }
- application.startActivity(intent);
- } else if (config.getBackgroundMode() == CrashConfig.BACKGROUND_MODE_CRASH) {
- if (oldHandler != null) {
- oldHandler.uncaughtException(thread, throwable);
- return;
- }
- //If it is null (should not be), we let it continue and kill the process or it will be stuck
+ intent.putExtra(EXTRA_CONFIG, config);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ if (config.getEventListener() != null) {
+ config.getEventListener().onLaunchErrorActivity();
+ }
+ application.startActivity(intent);
+ } else if (config.getBackgroundMode() == CrashConfig.BACKGROUND_MODE_CRASH) {
+ if (oldHandler != null) {
+ oldHandler.uncaughtException(thread, throwable);
+ return;
}
- //Else (BACKGROUND_MODE_SILENT): do nothing and let the following code kill the process
- }
- final Activity lastActivity = lastActivityCreated.get();
- if (lastActivity != null) {
- //We finish the activity, this solves a bug which causes infinite recursion.
- //See: https://github.com/ACRA/acra/issues/42
- lastActivity.finish();
- lastActivityCreated.clear();
}
- killCurrentProcess();
- } else if (oldHandler != null) {
- oldHandler.uncaughtException(thread, throwable);
}
+ final Activity lastActivity = lastActivityCreated.get();
+ if (lastActivity != null) {
+ lastActivity.finish();
+ lastActivityCreated.clear();
+ }
+ killCurrentProcess();
+ } else if (oldHandler != null) {
+ oldHandler.uncaughtException(thread, throwable);
}
});
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@@ -181,10 +162,6 @@ public void uncaughtException(Thread thread, final Throwable throwable) {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity.getClass() != config.getErrorActivityClass()) {
- // Copied from ACRA:
- // Ignore activityClass because we want the last
- // application Activity that was started so that we can
- // explicitly kill it off.
lastActivityCreated = new WeakReference<>(activity);
}
if (config.isTrackActivities()) {
@@ -241,26 +218,14 @@ public void onActivityDestroyed(Activity activity) {
}
}
- /**
- * Given an Intent, returns the stack trace extra from it.
- *
- * @param intent The Intent. Must not be null.
- * @return The stacktrace, or null if not provided.
- */
@Nullable
public static String getStackTraceFromIntent(@NonNull Intent intent) {
return intent.getStringExtra(CrashActivity.EXTRA_STACK_TRACE);
}
- /**
- * Given an Intent, returns the config extra from it.
- *
- * @param intent The Intent. Must not be null.
- * @return The config, or null if not provided.
- */
public static CrashConfig getConfigFromIntent(@NonNull Intent intent) {
CrashConfig config = (CrashConfig) intent.getSerializableExtra(CrashActivity.EXTRA_CONFIG);
- if (config.isLogErrorOnRestart()) {
+ if (Objects.requireNonNull(config).isLogErrorOnRestart()) {
String stackTrace = getStackTraceFromIntent(intent);
if (stackTrace != null) {
Log.e(TAG, "The previous app process crashed. This is the stack trace of the crash:\n" + getStackTraceFromIntent(intent));
@@ -270,35 +235,19 @@ public static CrashConfig getConfigFromIntent(@NonNull Intent intent) {
return config;
}
- /**
- * Given an Intent, returns the activity log extra from it.
- *
- * @param intent The Intent. Must not be null.
- * @return The activity log, or null if not provided.
- */
@Nullable
private static String getActivityLogFromIntent(@NonNull Intent intent) {
return intent.getStringExtra(CrashActivity.EXTRA_ACTIVITY_LOG);
}
- /**
- * Given an Intent, returns several error details including the stack trace extra from the intent.
- *
- * @param context A valid context. Must not be null.
- * @param intent The Intent. Must not be null.
- * @return The full error details.
- */
@NonNull
public static String getAllErrorDetailsFromIntent(@NonNull Context context, @NonNull Intent intent) {
- //I don't think that this needs localization because it's a development string...
Date currentDate = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
- //Get build date
String buildDateAsString = getBuildDateAsString(context, dateFormat);
- //Get app version
String versionName = getVersionName(context);
String errorDetails = "";
@@ -308,9 +257,6 @@ public static String getAllErrorDetailsFromIntent(@NonNull Context context, @Non
errorDetails += "Build date: " + buildDateAsString + " \n";
}
errorDetails += "Current date: " + dateFormat.format(currentDate) + " \n";
- //Added a space between line feeds to fix #18.
- //Ideally, we should not use this method at all... It is only formatted this way because of coupling with the default error activity.
- //We should move it to a method that returns a bean, and let anyone format it as they wish.
errorDetails += "Device: " + getDeviceModelName() + " \n \n";
errorDetails += "Stack trace: \n";
errorDetails += getStackTraceFromIntent(intent);
@@ -324,17 +270,6 @@ public static String getAllErrorDetailsFromIntent(@NonNull Context context, @Non
return errorDetails;
}
- /**
- * Given an Intent, restarts the app and launches a startActivity to that intent.
- * The flags NEW_TASK and CLEAR_TASK are set if the Intent does not have them, to ensure
- * the app stack is fully cleared.
- * If an event listener is provided, the restart app event is invoked.
- * Must only be used from your error activity.
- *
- * @param activity The current error activity. Must not be null.
- * @param intent The Intent. Must not be null.
- * @param config The config object as obtained by calling getConfigFromIntent.
- */
private static void restartApplicationWithIntent(@NonNull Activity activity, @NonNull Intent intent, @NonNull CrashConfig config) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
if (intent.getComponent() != null) {
@@ -359,14 +294,6 @@ public static void restartApplication(@NonNull Activity activity, @NonNull Crash
restartApplicationWithIntent(activity, intent, config);
}
- /**
- * Closes the app.
- * If an event listener is provided, the close app event is invoked.
- * Must only be used from your error activity.
- *
- * @param activity The current error activity. Must not be null.
- * @param config The config object as obtained by calling getConfigFromIntent.
- */
public static void closeApplication(@NonNull Activity activity, @NonNull CrashConfig config) {
if (config.getEventListener() != null) {
config.getEventListener().onCloseAppFromErrorActivity();
@@ -375,40 +302,17 @@ public static void closeApplication(@NonNull Activity activity, @NonNull CrashCo
killCurrentProcess();
}
- /// INTERNAL METHODS NOT TO BE USED BY THIRD PARTIES
-
- /**
- * INTERNAL method that returns the current configuration of the library.
- * If you want to check the config, use CrashConfig.Builder.get();
- *
- * @return the current configuration
- */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@NonNull
public static CrashConfig getConfig() {
return config;
}
- /**
- * INTERNAL method that sets the configuration of the library.
- * You must not use this, use CrashConfig.Builder.apply()
- *
- * @param config the configuration to use
- */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static void setConfig(@NonNull CrashConfig config) {
CrashActivity.config = config;
}
- /**
- * INTERNAL method that checks if the stack trace that just crashed is conflictive. This is true in the following scenarios:
- * - The application has crashed while initializing (handleBindApplication is in the stack)
- * - The error activity has crashed (activityClass is in the stack)
- *
- * @param throwable The throwable from which the stack trace will be checked
- * @param activityClass The activity class to launch when the app crashes
- * @return true if this stack trace is conflictive and the activity must not be launched, false otherwise
- */
private static boolean isStackTraceLikelyConflictive(@NonNull Throwable throwable, @NonNull Class extends Activity> activityClass) {
do {
StackTraceElement[] stackTrace = throwable.getStackTrace();
@@ -421,13 +325,6 @@ private static boolean isStackTraceLikelyConflictive(@NonNull Throwable throwabl
return false;
}
- /**
- * INTERNAL method that returns the build date of the current APK as a string, or null if unable to determine it.
- *
- * @param context A valid context. Must not be null.
- * @param dateFormat DateFormat to use to convert from Date to String
- * @return The formatted date, or "Unknown" if unable to determine it.
- */
@Nullable
private static String getBuildDateAsString(@NonNull Context context, @NonNull DateFormat dateFormat) {
long buildDate;
@@ -452,12 +349,6 @@ private static String getBuildDateAsString(@NonNull Context context, @NonNull Da
}
}
- /**
- * INTERNAL method that returns the version name of the current app, or null if unable to determine it.
- *
- * @param context A valid context. Must not be null.
- * @return The version name, or "Unknown if unable to determine it.
- */
@NonNull
private static String getVersionName(Context context) {
try {
@@ -468,12 +359,6 @@ private static String getVersionName(Context context) {
}
}
- /**
- * INTERNAL method that returns the device model name with correct capitalization.
- * Taken from: http://stackoverflow.com/a/12707479/1254846
- *
- * @return The device model name (i.e., "LGE Nexus 5")
- */
@NonNull
private static String getDeviceModelName() {
String manufacturer = Build.MANUFACTURER;
@@ -485,12 +370,6 @@ private static String getDeviceModelName() {
}
}
- /**
- * INTERNAL method that capitalizes the first character of a string
- *
- * @param s The string to capitalize
- * @return The capitalized string
- */
@NonNull
private static String capitalize(@Nullable String s) {
if (s == null || s.length() == 0) {
@@ -504,23 +383,11 @@ private static String capitalize(@Nullable String s) {
}
}
- /**
- * INTERNAL method used to guess which activity must be called from the error activity to restart the app.
- * It will first get activities from the AndroidManifest with intent filter ,
- * if it cannot find them, then it will get the default launcher.
- * If there is no default launcher, this returns null.
- *
- * @param context A valid context. Must not be null.
- * @return The guessed restart activity class, or null if no suitable one is found
- */
@Nullable
private static Class extends Activity> guessRestartActivityClass(@NonNull Context context) {
Class extends Activity> resolvedActivityClass;
-
- //If action is defined, use that
resolvedActivityClass = getRestartActivityClassWithIntentFilter(context);
- //Else, get the default launcher activity
if (resolvedActivityClass == null) {
resolvedActivityClass = getLauncherActivity(context);
}
@@ -528,14 +395,6 @@ private static Class extends Activity> guessRestartActivityClass(@NonNull Cont
return resolvedActivityClass;
}
- /**
- * INTERNAL method used to get the first activity with an intent-filter ,
- * If there is no activity with that intent filter, this returns null.
- *
- * @param context A valid context. Must not be null.
- * @return A valid activity class, or null if no suitable one is found
- */
- @SuppressWarnings("unchecked")
@Nullable
private static Class extends Activity> getRestartActivityClassWithIntentFilter(@NonNull Context context) {
Intent searchedIntent = new Intent().setAction(INTENT_ACTION_RESTART_ACTIVITY).setPackage(context.getPackageName());
@@ -555,14 +414,6 @@ private static Class extends Activity> getRestartActivityClassWithIntentFilter
return null;
}
- /**
- * INTERNAL method used to get the default launcher activity for the app.
- * If there is no launchable activity, this returns null.
- *
- * @param context A valid context. Must not be null.
- * @return A valid activity class, or null if no suitable one is found
- */
- @SuppressWarnings("unchecked")
@Nullable
private static Class extends Activity> getLauncherActivity(@NonNull Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
@@ -578,22 +429,10 @@ private static Class extends Activity> getLauncherActivity(@NonNull Context co
return null;
}
- /**
- * INTERNAL method used to guess which error activity must be called when the app crashes.
- * It will first get activities from the AndroidManifest with intent filter ,
- * if it cannot find them, then it will use the default error activity.
- *
- * @param context A valid context. Must not be null.
- * @return The guessed error activity class, or the default error activity if not found
- */
@NonNull
private static Class extends Activity> guessErrorActivityClass(@NonNull Context context) {
Class extends Activity> resolvedActivityClass;
-
- //If action is defined, use that
resolvedActivityClass = getErrorActivityClassWithIntentFilter(context);
-
- //Else, get the default error activity
if (resolvedActivityClass == null) {
resolvedActivityClass = DefaultErrorActivity.class;
}
@@ -601,14 +440,6 @@ private static Class extends Activity> guessErrorActivityClass(@NonNull Contex
return resolvedActivityClass;
}
- /**
- * INTERNAL method used to get the first activity with an intent-filter ,
- * If there is no activity with that intent filter, this returns null.
- *
- * @param context A valid context. Must not be null.
- * @return A valid activity class, or null if no suitable one is found
- */
- @SuppressWarnings("unchecked")
@Nullable
private static Class extends Activity> getErrorActivityClassWithIntentFilter(@NonNull Context context) {
Intent searchedIntent = new Intent().setAction(INTENT_ACTION_ERROR_ACTIVITY).setPackage(context.getPackageName());
@@ -620,7 +451,6 @@ private static Class extends Activity> getErrorActivityClassWithIntentFilter(@
try {
return (Class extends Activity>) Class.forName(resolveInfo.activityInfo.name);
} catch (ClassNotFoundException e) {
- //Should not happen, print it to the log!
Log.e(TAG, "Failed when resolving the error activity class via intent filter, stack trace follows!", e);
}
}
@@ -628,40 +458,20 @@ private static Class extends Activity> getErrorActivityClassWithIntentFilter(@
return null;
}
- /**
- * INTERNAL method that kills the current process.
- * It is used after restarting or killing the app.
- */
private static void killCurrentProcess() {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
- /**
- * INTERNAL method that stores the last crash timestamp
- *
- * @param timestamp The current timestamp.
- */
@SuppressLint("ApplySharedPref") //This must be done immediately since we are killing the app
private static void setLastCrashTimestamp(@NonNull Context context, long timestamp) {
context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit().putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit();
}
- /**
- * INTERNAL method that gets the last crash timestamp
- *
- * @return The last crash timestamp, or -1 if not set.
- */
private static long getLastCrashTimestamp(@NonNull Context context) {
return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1);
}
- /**
- * INTERNAL method that tells if the app has crashed in the last seconds.
- * This is used to avoid restart loops.
- *
- * @return true if the app has crashed in the last seconds, false otherwise.
- */
private static boolean hasCrashedInTheLastSeconds(@NonNull Context context) {
long lastTimestamp = getLastCrashTimestamp(context);
long currentTimestamp = new Date().getTime();
@@ -669,10 +479,6 @@ private static boolean hasCrashedInTheLastSeconds(@NonNull Context context) {
return (lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < config.getMinTimeBetweenCrashesMs());
}
- /**
- * Interface to be called when events occur, so they can be reported
- * by the app as, for example, Google Analytics events.
- */
public interface EventListener extends Serializable {
void onLaunchErrorActivity();
diff --git a/library/src/main/java/com/developer/crashx/activity/DefaultErrorActivity.java b/library/src/main/java/com/developer/crashx/activity/DefaultErrorActivity.java
new file mode 100644
index 0000000..14d7ef8
--- /dev/null
+++ b/library/src/main/java/com/developer/crashx/activity/DefaultErrorActivity.java
@@ -0,0 +1,105 @@
+package com.developer.crashx.activity;
+
+import android.annotation.SuppressLint;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.developer.crashx.CrashActivity;
+import com.developer.crashx.R;
+import com.developer.crashx.config.CrashConfig;
+
+/**
+ * @author tkdco , TutorialsAndroid
+ */
+public final class DefaultErrorActivity extends AppCompatActivity {
+
+ @SuppressLint("PrivateResource")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+
+ setTheme(R.style.Theme_AppCompat_Light_NoActionBar);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Window w = getWindow();
+ w.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ }
+
+ super.onCreate(savedInstanceState);
+
+ TypedArray a = obtainStyledAttributes(R.styleable.AppCompatTheme);
+ if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
+ setTheme(R.style.Theme_AppCompat_Light_DarkActionBar);
+ }
+ a.recycle();
+
+ setContentView(R.layout.crash_default_error_activity);
+ Button restartButton = findViewById(R.id.crash_error_activity_restart_button);
+
+ final CrashConfig config = CrashActivity.getConfigFromIntent(getIntent());
+
+ if (config == null) {
+ finish();
+ return;
+ }
+
+ if (config.isShowRestartButton() && config.getRestartActivityClass() != null) {
+ restartButton.setText(R.string.customactivityoncrash_error_activity_restart_app);
+ restartButton.setOnClickListener(v -> CrashActivity.restartApplication(DefaultErrorActivity.this, config));
+ } else {
+ restartButton.setOnClickListener(v -> CrashActivity.closeApplication(DefaultErrorActivity.this, config));
+ }
+
+ Button moreInfoButton = findViewById(R.id.crash_error_activity_more_info_button);
+
+ if (config.isShowErrorDetails()) {
+ moreInfoButton.setOnClickListener(v -> {
+ AlertDialog dialog = new AlertDialog.Builder(DefaultErrorActivity.this)
+ .setTitle(R.string.customactivityoncrash_error_activity_error_details_title)
+ .setMessage(CrashActivity.getAllErrorDetailsFromIntent(DefaultErrorActivity.this, getIntent()))
+ .setPositiveButton(R.string.customactivityoncrash_error_activity_error_details_close, null)
+ .setNeutralButton(R.string.customactivityoncrash_error_activity_error_details_copy,
+ (dialog1, which) -> copyErrorToClipboard())
+ .show();
+ TextView textView = dialog.findViewById(android.R.id.message);
+ if (textView != null) {
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.customactivityoncrash_error_activity_error_details_text_size));
+ }
+ });
+ } else {
+ moreInfoButton.setVisibility(View.GONE);
+ }
+
+ Integer defaultErrorActivityDrawableId = config.getErrorDrawable();
+ ImageView errorImageView = findViewById(R.id.crash_error_activity_image);
+
+ if (defaultErrorActivityDrawableId != null) {
+ errorImageView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), defaultErrorActivityDrawableId, getTheme()));
+ }
+ }
+
+ private void copyErrorToClipboard() {
+ String errorInformation = CrashActivity.getAllErrorDetailsFromIntent(DefaultErrorActivity.this, getIntent());
+
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ if (clipboard != null) {
+ ClipData clip = ClipData.newPlainText(getString(R.string.customactivityoncrash_error_activity_error_details_clipboard_label), errorInformation);
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(DefaultErrorActivity.this, R.string.customactivityoncrash_error_activity_error_details_copied, Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/library/src/main/java/com/kinda/crash/config/CrashConfig.java b/library/src/main/java/com/developer/crashx/config/CrashConfig.java
similarity index 64%
rename from library/src/main/java/com/kinda/crash/config/CrashConfig.java
rename to library/src/main/java/com/developer/crashx/config/CrashConfig.java
index cc506c1..273dd9b 100644
--- a/library/src/main/java/com/kinda/crash/config/CrashConfig.java
+++ b/library/src/main/java/com/developer/crashx/config/CrashConfig.java
@@ -1,25 +1,26 @@
-package com.kinda.crash.config;
+package com.developer.crashx.config;
import android.app.Activity;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Modifier;
-import com.kinda.crash.CrashActivity;
+import com.developer.crashx.CrashActivity;
+/**
+ * @author tkdco , TutorialsAndroid
+ */
public class CrashConfig implements Serializable {
@IntDef({BACKGROUND_MODE_CRASH, BACKGROUND_MODE_SHOW_CUSTOM, BACKGROUND_MODE_SILENT})
@Retention(RetentionPolicy.SOURCE)
- private @interface BackgroundMode {
- //I hate empty blocks
- }
+ private @interface BackgroundMode { }
static final int BACKGROUND_MODE_SILENT = 0;
public static final int BACKGROUND_MODE_SHOW_CUSTOM = 1;
@@ -157,136 +158,66 @@ public static Builder create() {
return builder;
}
- /**
- * Defines if the error activity must be launched when the app is on background.
- * BackgroundMode.BACKGROUND_MODE_SHOW_CUSTOM: launch the error activity when the app is in background,
- * BackgroundMode.BACKGROUND_MODE_CRASH: launch the default system error when the app is in background,
- * BackgroundMode.BACKGROUND_MODE_SILENT: crash silently when the app is in background,
- * The default is BackgroundMode.BACKGROUND_MODE_SHOW_CUSTOM (the app will be brought to front when a crash occurs).
- */
@NonNull
public Builder backgroundMode(@BackgroundMode int backgroundMode) {
config.backgroundMode = backgroundMode;
return this;
}
- /**
- * Defines if CrashActivity crash interception mechanism is enabled.
- * Set it to true if you want CrashActivity to intercept crashes,
- * false if you want them to be treated as if the library was not installed.
- * The default is true.
- */
@NonNull
public Builder enabled(boolean enabled) {
config.enabled = enabled;
return this;
}
- /**
- * Defines if the error activity must shown the error details button.
- * Set it to true if you want to show the full stack trace and device info,
- * false if you want it to be hidden.
- * The default is true.
- */
@NonNull
public Builder showErrorDetails(boolean showErrorDetails) {
config.showErrorDetails = showErrorDetails;
return this;
}
- /**
- * Defines if the error activity should show a restart button.
- * Set it to true if you want to show a restart button,
- * false if you want to show a close button.
- * Note that even if restart is enabled but you app does not have any launcher activities,
- * a close button will still be used by the default error activity.
- * The default is true.
- */
@NonNull
public Builder showRestartButton(boolean showRestartButton) {
config.showRestartButton = showRestartButton;
return this;
}
- /**
- * Defines if the stack trace must be logged again once the custom activity is shown.
- * Set it to true if you want to log the stack trace again,
- * false if you don't want the extra logging.
- * This option exists because the default Android Studio logcat view only shows the output
- * of the current process, and since the error activity runs on a new process,
- * you can't see the previous output easily.
- * Internally, it's logged when getConfigFromIntent() is called.
- * The default is true.
- */
@NonNull
public Builder logErrorOnRestart(boolean logErrorOnRestart) {
config.logErrorOnRestart = logErrorOnRestart;
return this;
}
- /**
- * Defines if the activities visited by the user should be tracked
- * so they are reported when an error occurs.
- * The default is false.
- */
@NonNull
public Builder trackActivities(boolean trackActivities) {
config.trackActivities = trackActivities;
return this;
}
- /**
- * Defines the time that must pass between app crashes to determine that we are not
- * in a crash loop. If a crash has occurred less that this time ago,
- * the error activity will not be launched and the system crash screen will be invoked.
- * The default is 3000.
- */
@NonNull
public Builder minTimeBetweenCrashesMs(int minTimeBetweenCrashesMs) {
config.minTimeBetweenCrashesMs = minTimeBetweenCrashesMs;
return this;
}
- /**
- * Defines which drawable to use in the default error activity image.
- * Set this if you want to use an image other than the default one.
- * The default is R.drawable.customactivityoncrash_error_image (a cute upside-down bug).
- */
@NonNull
public Builder errorDrawable(@Nullable @DrawableRes Integer errorDrawable) {
config.errorDrawable = errorDrawable;
return this;
}
- /**
- * Sets the error activity class to launch when a crash occurs.
- * If null, the default error activity will be used.
- */
@NonNull
public Builder errorActivity(@Nullable Class extends Activity> errorActivityClass) {
config.errorActivityClass = errorActivityClass;
return this;
}
- /**
- * Sets the main activity class that the error activity must launch when a crash occurs.
- * If not set or set to null, the default launch activity will be used.
- * If your app has no launch activities and this is not set, the default error activity will close instead.
- */
@NonNull
public Builder restartActivity(@Nullable Class extends Activity> restartActivityClass) {
config.restartActivityClass = restartActivityClass;
return this;
}
- /**
- * Sets an event listener to be called when events occur, so they can be reported
- * by the app as, for example, Google Analytics events.
- * If not set or set to null, no events will be reported.
- *
- * @param eventListener The event listener.
- * @throws IllegalArgumentException if the eventListener is an inner or anonymous class
- */
@NonNull
public Builder eventListener(@Nullable CrashActivity.EventListener eventListener) {
if (eventListener != null && eventListener.getClass().getEnclosingClass() != null && !Modifier.isStatic(eventListener.getClass().getModifiers())) {
diff --git a/library/src/main/java/com/kinda/crash/provider/CrashInitProvider.java b/library/src/main/java/com/developer/crashx/provider/CrashInitProvider.java
similarity index 84%
rename from library/src/main/java/com/kinda/crash/provider/CrashInitProvider.java
rename to library/src/main/java/com/developer/crashx/provider/CrashInitProvider.java
index a97d3e3..313c158 100644
--- a/library/src/main/java/com/kinda/crash/provider/CrashInitProvider.java
+++ b/library/src/main/java/com/developer/crashx/provider/CrashInitProvider.java
@@ -1,14 +1,17 @@
-package com.kinda.crash.provider;
+package com.developer.crashx.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
-import com.kinda.crash.CrashActivity;
+import com.developer.crashx.CrashActivity;
+/**
+ * @author tkdco , TutorialsAndroid
+ */
public class CrashInitProvider extends ContentProvider {
public boolean onCreate() {
diff --git a/library/src/main/java/com/kinda/crash/activity/DefaultErrorActivity.java b/library/src/main/java/com/kinda/crash/activity/DefaultErrorActivity.java
deleted file mode 100644
index 5ff0d3c..0000000
--- a/library/src/main/java/com/kinda/crash/activity/DefaultErrorActivity.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package com.kinda.crash.activity;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.DialogInterface;
-import android.content.res.TypedArray;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
-import android.util.TypedValue;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.kinda.crash.CrashActivity;
-import com.kinda.crash.R;
-import com.kinda.crash.config.CrashConfig;
-
-public final class DefaultErrorActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- //This is needed to avoid a crash if the developer has not specified
- //an app-level theme that extends Theme.AppCompat
- TypedArray a = obtainStyledAttributes(R.styleable.AppCompatTheme);
- if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
- setTheme(R.style.Theme_AppCompat_Light_DarkActionBar);
- }
- a.recycle();
-
- setContentView(R.layout.crash_default_error_activity);
-
- //Close/restart button logic:
- //If a class if set, use restart.
- //Else, use close and just finish the app.
- //It is recommended that you follow this logic if implementing a custom error activity.
- Button restartButton = findViewById(R.id.crash_error_activity_restart_button);
-
- final CrashConfig config = CrashActivity.getConfigFromIntent(getIntent());
-
- if (config == null) {
- //This should never happen - Just finish the activity to avoid a recursive crash.
- finish();
- return;
- }
-
- if (config.isShowRestartButton() && config.getRestartActivityClass() != null) {
- restartButton.setText(R.string.customactivityoncrash_error_activity_restart_app);
- restartButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- CrashActivity.restartApplication(DefaultErrorActivity.this, config);
- }
- });
- } else {
- restartButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- CrashActivity.closeApplication(DefaultErrorActivity.this, config);
- }
- });
- }
-
- Button moreInfoButton = findViewById(R.id.crash_error_activity_more_info_button);
-
- if (config.isShowErrorDetails()) {
- moreInfoButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //We retrieve all the error data and show it
-
- AlertDialog dialog = new AlertDialog.Builder(DefaultErrorActivity.this)
- .setTitle(R.string.customactivityoncrash_error_activity_error_details_title)
- .setMessage(CrashActivity.getAllErrorDetailsFromIntent(DefaultErrorActivity.this, getIntent()))
- .setPositiveButton(R.string.customactivityoncrash_error_activity_error_details_close, null)
- .setNeutralButton(R.string.customactivityoncrash_error_activity_error_details_copy,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- copyErrorToClipboard();
- }
- })
- .show();
- TextView textView = dialog.findViewById(android.R.id.message);
- if (textView != null) {
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.customactivityoncrash_error_activity_error_details_text_size));
- }
- }
- });
- } else {
- moreInfoButton.setVisibility(View.GONE);
- }
-
- Integer defaultErrorActivityDrawableId = config.getErrorDrawable();
- ImageView errorImageView = findViewById(R.id.crash_error_activity_image);
-
- if (defaultErrorActivityDrawableId != null) {
- errorImageView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), defaultErrorActivityDrawableId, getTheme()));
- }
- }
-
- private void copyErrorToClipboard() {
- String errorInformation = CrashActivity.getAllErrorDetailsFromIntent(DefaultErrorActivity.this, getIntent());
-
- ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
-
- //Are there any devices without clipboard...?
- if (clipboard != null) {
- ClipData clip = ClipData.newPlainText(getString(R.string.customactivityoncrash_error_activity_error_details_clipboard_label), errorInformation);
- clipboard.setPrimaryClip(clip);
- Toast.makeText(DefaultErrorActivity.this, R.string.customactivityoncrash_error_activity_error_details_copied, Toast.LENGTH_SHORT).show();
- }
- }
-}
diff --git a/library/src/main/res/drawable-hdpi/buggy.png b/library/src/main/res/drawable-hdpi/buggy.png
deleted file mode 100644
index f2b4958..0000000
Binary files a/library/src/main/res/drawable-hdpi/buggy.png and /dev/null differ
diff --git a/library/src/main/res/drawable/crash_ic_bug_report.xml b/library/src/main/res/drawable/crash_ic_bug_report.xml
new file mode 100644
index 0000000..37292e0
--- /dev/null
+++ b/library/src/main/res/drawable/crash_ic_bug_report.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/library/src/main/res/drawable/crash_ic_close.xml b/library/src/main/res/drawable/crash_ic_close.xml
new file mode 100644
index 0000000..0c8775c
--- /dev/null
+++ b/library/src/main/res/drawable/crash_ic_close.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/library/src/main/res/drawable/crash_ic_error.xml b/library/src/main/res/drawable/crash_ic_error.xml
new file mode 100644
index 0000000..93db179
--- /dev/null
+++ b/library/src/main/res/drawable/crash_ic_error.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/library/src/main/res/layout/crash_default_error_activity.xml b/library/src/main/res/layout/crash_default_error_activity.xml
index 107c11f..84ecc8d 100644
--- a/library/src/main/res/layout/crash_default_error_activity.xml
+++ b/library/src/main/res/layout/crash_default_error_activity.xml
@@ -1,31 +1,37 @@
+ android:background="#D50000">
-
+ android:layout_centerInParent="true"
+ app:cardBackgroundColor="#FFFFFF"
+ app:cardCornerRadius="30dp"
+ app:cardElevation="0dp"
+ app:cardPreventCornerOverlap="true"
+ app:cardUseCompatPadding="true">
+ android:paddingBottom="@dimen/customactivityoncrash_activity_vertical_margin">
+ android:scaleType="fitXY"
+ android:src="@drawable/crash_ic_bug_report" />
+ android:textColor="@android:color/white"
+ android:textStyle="bold"
+ android:background="#D50000"
+ android:text="@string/customactivityoncrash_error_activity_close_app"
+ android:drawableBottom="@drawable/crash_ic_close" />
+ android:layout_marginTop="@dimen/customactivityoncrash_activity_vertical_margin"
+ android:textColor="@android:color/white"
+ android:textStyle="bold"
+ android:background="#D50000"
+ android:drawableBottom="@drawable/crash_ic_error"/>
-
+
\ No newline at end of file
diff --git a/sample/build.gradle b/sample/build.gradle
index a7e822c..d3ad962 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
- applicationId "com.kinda.crash.sample"
+ applicationId "com.developer.crashx.crash"
minSdkVersion 15
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
+ targetSdkVersion 29
+ versionCode 8
+ versionName "5.0.19"
}
buildTypes {
debug {
@@ -20,13 +20,23 @@ android {
proguardFiles 'proguard-rules.pro'
}
}
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
- implementation 'com.android.support:appcompat-v7:28.0.0'
+ //ANDROIDX JETPACK LIBRARIES
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'com.google.android.material:material:1.2.0'
+ implementation 'androidx.preference:preference:1.1.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
//CrashX library
implementation project(":library")
-}
+}
\ No newline at end of file
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 0c14da9..b96224a 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -1,34 +1,28 @@
+ package="com.developer.crashx.crash">
-
-
-
-
-
-
diff --git a/sample/src/main/ic_launcher-web.png b/sample/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..07ad810
Binary files /dev/null and b/sample/src/main/ic_launcher-web.png differ
diff --git a/sample/src/main/java/com/developer/crashx/crash/CrashingApplication.java b/sample/src/main/java/com/developer/crashx/crash/CrashingApplication.java
new file mode 100644
index 0000000..b570070
--- /dev/null
+++ b/sample/src/main/java/com/developer/crashx/crash/CrashingApplication.java
@@ -0,0 +1,37 @@
+package com.developer.crashx.crash;
+
+import android.app.Application;
+import android.util.Log;
+
+import com.developer.crashx.CrashActivity;
+import com.developer.crashx.config.CrashConfig;
+
+public class CrashingApplication extends Application {
+
+ private static final String TAG = "CrashX";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ CrashConfig.Builder.create()
+ .apply();
+ }
+
+ private static class CustomEventListener implements CrashActivity.EventListener {
+ @Override
+ public void onLaunchErrorActivity() {
+ Log.i(TAG, "onLaunchErrorActivity()");
+ }
+
+ @Override
+ public void onRestartAppFromErrorActivity() {
+ Log.i(TAG, "onRestartAppFromErrorActivity()");
+ }
+
+ @Override
+ public void onCloseAppFromErrorActivity() {
+ Log.i(TAG, "onCloseAppFromErrorActivity()");
+ }
+ }
+}
diff --git a/sample/src/main/java/com/developer/crashx/crash/activity/ErrorActivity.java b/sample/src/main/java/com/developer/crashx/crash/activity/ErrorActivity.java
new file mode 100644
index 0000000..c6a26e4
--- /dev/null
+++ b/sample/src/main/java/com/developer/crashx/crash/activity/ErrorActivity.java
@@ -0,0 +1,51 @@
+package com.developer.crashx.crash.activity;
+
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.developer.crashx.CrashActivity;
+import com.developer.crashx.config.CrashConfig;
+import com.developer.crashx.crash.R;
+
+public class ErrorActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_custom_error);
+
+ TextView errorDetailsText = findViewById(R.id.error_details);
+ errorDetailsText.setText(CrashActivity.getStackTraceFromIntent(getIntent()));
+
+ Button restartButton = findViewById(R.id.restart_button);
+
+ final CrashConfig config = CrashActivity.getConfigFromIntent(getIntent());
+
+ if (config == null) {
+ finish();
+ return;
+ }
+
+ if (config.isShowRestartButton() && config.getRestartActivityClass() != null) {
+ restartButton.setText(R.string.restart_app);
+ restartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ CrashActivity.restartApplication(ErrorActivity.this, config);
+ }
+ });
+ } else {
+ restartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ CrashActivity.closeApplication(ErrorActivity.this, config);
+ }
+ });
+ }
+ }
+}
diff --git a/sample/src/main/java/com/kinda/crash/sample/activity/MainActivity.java b/sample/src/main/java/com/developer/crashx/crash/activity/MainActivity.java
similarity index 85%
rename from sample/src/main/java/com/kinda/crash/sample/activity/MainActivity.java
rename to sample/src/main/java/com/developer/crashx/crash/activity/MainActivity.java
index 5b844af..2704c3e 100644
--- a/sample/src/main/java/com/kinda/crash/sample/activity/MainActivity.java
+++ b/sample/src/main/java/com/developer/crashx/crash/activity/MainActivity.java
@@ -1,14 +1,14 @@
-package com.kinda.crash.sample.activity;
+package com.developer.crashx.crash.activity;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
-import com.kinda.crash.sample.R;
+import com.developer.crashx.crash.R;
public class MainActivity extends AppCompatActivity {
@@ -29,7 +29,6 @@ public void onClick(View view) {
});
crashBgThreadButton.setOnClickListener(new View.OnClickListener() {
- @SuppressLint("StaticFieldLeak") //For demo purposes we don't care about leaks
@Override
public void onClick(View view) {
new AsyncTask() {
@@ -42,7 +41,6 @@ protected Void doInBackground(Void... voids) {
});
crashWithDelayButton.setOnClickListener(new View.OnClickListener() {
- @SuppressLint("StaticFieldLeak") //For demo purposes we don't care about leaks
@Override
public void onClick(View view) {
new AsyncTask() {
diff --git a/sample/src/main/java/com/kinda/crash/sample/CrashingApplication.java b/sample/src/main/java/com/kinda/crash/sample/CrashingApplication.java
deleted file mode 100644
index 938ba16..0000000
--- a/sample/src/main/java/com/kinda/crash/sample/CrashingApplication.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.kinda.crash.sample;
-
-import android.app.Application;
-import android.util.Log;
-
-import com.kinda.crash.CrashActivity;
-import com.kinda.crash.config.CrashConfig;
-
-public class CrashingApplication extends Application {
-
- private static final String TAG = "SampleCrashingApp";
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- //You can uncomment any of the lines below, and test the results.
-
- CrashConfig.Builder.create()
- //Customizes what to do when the app crashes while it is in background. Possible values:
- //BackgroundMode.BACKGROUND_MODE_SHOW_CUSTOM: launch the error activity when the app is in background,
- //BackgroundMode.BACKGROUND_MODE_CRASH: launch the default system error when the app is in background,
- //BackgroundMode.BACKGROUND_MODE_SILENT: crash silently when the app is in background,
-// .backgroundMode(CrashConfig.BACKGROUND_MODE_SILENT)
- //This disables the interception of crashes. Use it to disable CrashActivity (for example, depending on your buildType).
-// .enabled(false)
- //This hides the "error details" button in the error activity, thus hiding the stack trace
-// .showErrorDetails(false)
- //This avoids the app from using the "Restart app" button and displaying a "Close app" button directly.
- //Even with restart app enabled, the Close app can still be displayed if your app has no launch activity.
-// .showRestartButton(false)
- //This makes the library track the activites visited by the user and their lifecycle calls.
- //Use it if you want that info in the error details screen shown on the error activity.
-// .trackActivities(true)
- //This hides the additional log shown when the error activity is launched.
- //It is shown by default because the Android Studio Logcat view by default only shows
- //the current process output, and this makes the stack trace more obvious to find.
-// .logErrorOnRestart(false)
- //Defines the time that must pass between app crashes to determine that we are not in a crash loop.
- //If a crash has occurred less that this time ago, the error activity will not be launched
- //and the system crash screen will be invoked.
-// .minTimeBetweenCrashesMs(2000)
- //This shows a different image on the error activity, instead of the default upside-down bug.
- //You may use a drawable or a mipmap.
-// .errorDrawable(R.mipmap.ic_launcher)
- //This sets the restart activity.
- //If you set this, this will be used. However, you can also set it with an intent-filter:
- //
- //If none are set, the default launch activity will be used.
-// .restartActivity(MainActivity.class)
- //This sets a custom error activity class instead of the default one.
- //If you set this, this will be used. However, you can also set it with an intent-filter:
- //
- //If none are set, the default launch activity will be used.
- //Comment it (and disable the intent filter) to see the customization effects on the default error activity.
- //Uncomment to use the custom error activity
-// .errorActivity(ErrorActivity.class)
- //This sets a EventListener to be notified of events regarding the error activity,
- //so you can, for example, report them to Google Analytics.
-// .eventListener(new CustomEventListener())
- .apply();
-
- //Initialize your error handler as normal.
- //i.e., ACRA.init(this);
- //or Fabric.with(this, new Crashlytics());
- }
-
- private static class CustomEventListener implements CrashActivity.EventListener {
- @Override
- public void onLaunchErrorActivity() {
- Log.i(TAG, "onLaunchErrorActivity()");
- }
-
- @Override
- public void onRestartAppFromErrorActivity() {
- Log.i(TAG, "onRestartAppFromErrorActivity()");
- }
-
- @Override
- public void onCloseAppFromErrorActivity() {
- Log.i(TAG, "onCloseAppFromErrorActivity()");
- }
- }
-}
diff --git a/sample/src/main/java/com/kinda/crash/sample/activity/ErrorActivity.java b/sample/src/main/java/com/kinda/crash/sample/activity/ErrorActivity.java
deleted file mode 100644
index 8e30c7c..0000000
--- a/sample/src/main/java/com/kinda/crash/sample/activity/ErrorActivity.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.kinda.crash.sample.activity;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.kinda.crash.CrashActivity;
-import com.kinda.crash.config.CrashConfig;
-import com.kinda.crash.sample.R;
-
-public class ErrorActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_custom_error);
-
- //**IMPORTANT**
- //The custom error activity in this sample is uglier than the default one and just
- //for demonstration purposes, please don't copy it to your project!
- //We recommend taking the original library's DefaultErrorActivity as a basis.
- //Of course, you are free to implement it as you wish in your application.
-
- //These four methods are available for you to use:
- //CrashActivity.getStackTraceFromIntent(getIntent()): gets the stack trace as a string
- //CrashActivity.getActivityLogFromIntent(getIntent()): gets the activity log as a string
- //CrashActivity.getAllErrorDetailsFromIntent(context, getIntent()): returns all error details including stacktrace as a string
- //CrashActivity.getConfigFromIntent(getIntent()): returns the config of the library when the error happened
-
- //Now, treat here the error as you wish. If you allow the user to restart or close the app,
- //don't forget to call the appropriate methods.
- //Otherwise, if you don't finish the activity, you will get the ErrorActivity on the activity stack and it will be visible again under some circumstances.
- //Also, you will get multiprocess problems in API<17.
-
- TextView errorDetailsText = findViewById(R.id.error_details);
- errorDetailsText.setText(CrashActivity.getStackTraceFromIntent(getIntent()));
-
- Button restartButton = findViewById(R.id.restart_button);
-
- final CrashConfig config = CrashActivity.getConfigFromIntent(getIntent());
-
- if (config == null) {
- //This should never happen - Just finish the activity to avoid a recursive crash.
- finish();
- return;
- }
-
- if (config.isShowRestartButton() && config.getRestartActivityClass() != null) {
- restartButton.setText(R.string.restart_app);
- restartButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- CrashActivity.restartApplication(ErrorActivity.this, config);
- }
- });
- } else {
- restartButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- CrashActivity.closeApplication(ErrorActivity.this, config);
- }
- });
- }
- }
-}
diff --git a/sample/src/main/res/layout/activity_custom_error.xml b/sample/src/main/res/layout/activity_custom_error.xml
index 78604fe..ff5ab28 100644
--- a/sample/src/main/res/layout/activity_custom_error.xml
+++ b/sample/src/main/res/layout/activity_custom_error.xml
@@ -23,10 +23,10 @@
+ android:src="@drawable/crash_ic_bug_report" />