├── sample
├── .gitignore
├── proguard-rules.pro
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ ├── values-v14
│ │ │ └── styles.xml
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ └── layout
│ │ │ ├── activity_main.xml
│ │ │ └── activity_custom_error.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── henorek
│ │ └── sample
│ │ └── crashczyk
│ │ ├── activity
│ │ ├── MainActivity.java
│ │ └── CustomErrorActivity.java
│ │ └── SampleCrashingApplication.java
└── build.gradle
├── library
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── crashczyk_error_image.png
│ │ ├── drawable-mdpi
│ │ │ └── crashczyk_error_image.png
│ │ ├── drawable-xhdpi
│ │ │ └── crashczyk_error_image.png
│ │ ├── drawable-xxhdpi
│ │ │ └── crashczyk_error_image.png
│ │ ├── drawable-xxxhdpi
│ │ │ └── crashczyk_error_image.png
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ └── strings.xml
│ │ ├── values-v14
│ │ │ └── styles.xml
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ └── layout
│ │ │ └── crashczyk_default_error_activity.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── henorek
│ │ └── crashczyk
│ │ ├── activity
│ │ └── DefaultErrorActivity.java
│ │ └── Crashczyk.java
├── gradle.properties
└── build.gradle
├── images
└── frontpage.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── NOTICE
├── settings.gradle
├── gradle.properties
├── gradlew.bat
├── gradlew
├── README.md
└── LICENSE
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # No special config needed for CustomActivityOnCrash! :D
2 |
--------------------------------------------------------------------------------
/images/frontpage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/images/frontpage.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | */build
3 | */*.iml
4 | *.iml
5 | local.properties
6 | .idea
7 | .idea/**
8 | .gradle
9 | *.hprof
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-hdpi/crashczyk_error_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/library/src/main/res/drawable-hdpi/crashczyk_error_image.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-mdpi/crashczyk_error_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/library/src/main/res/drawable-mdpi/crashczyk_error_image.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xhdpi/crashczyk_error_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/library/src/main/res/drawable-xhdpi/crashczyk_error_image.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xxhdpi/crashczyk_error_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/library/src/main/res/drawable-xxhdpi/crashczyk_error_image.png
--------------------------------------------------------------------------------
/library/src/main/res/drawable-xxxhdpi/crashczyk_error_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henorek/crashczyk/HEAD/library/src/main/res/drawable-xxxhdpi/crashczyk_error_image.png
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Custom Activity on Crash library
2 | Copyright (c) 2014-2015 Eduard Ereza, http://www.eduardereza.com/
3 |
4 | This product is licensed under the terms of the Apache Software License 2.0. See the LICENSE file for the full license text.
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | include ':sample', ':library'
18 |
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2015 Eduard Ereza Martínez
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | #
7 | # You may obtain a copy of the License at
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | POM_NAME=KrawczykOnCrash library
18 | POM_ARTIFACT_ID=krawczykoncrash
19 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/library/src/main/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #CC0000
20 | #880000
21 | #808000
22 |
--------------------------------------------------------------------------------
/library/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | #3F51B5
19 | #283593
20 | #FF4081
21 |
--------------------------------------------------------------------------------
/library/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | 16dp
20 | 16dp
21 |
22 | 12sp
23 |
24 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 | 16dp
21 | 16dp
22 |
23 |
24 | 16dp
25 |
26 |
27 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'com.android.library'
18 |
19 | android {
20 | compileSdkVersion 23
21 | buildToolsVersion "23.0.1"
22 | resourcePrefix 'crashczyk_'
23 |
24 | defaultConfig {
25 | minSdkVersion 4
26 | targetSdkVersion 23
27 | versionCode 1
28 | versionName "1.0.0"
29 | }
30 | }
31 |
32 | apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/library/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'com.android.application'
18 |
19 | android {
20 | compileSdkVersion 23
21 | buildToolsVersion "23.0.1"
22 |
23 | defaultConfig {
24 | applicationId "com.henorek.sample.crashczyk"
25 | minSdkVersion 4
26 | targetSdkVersion 23
27 | versionCode 1
28 | versionName "1.0.0"
29 | }
30 | buildTypes {
31 | debug {
32 | minifyEnabled true
33 | proguardFiles 'proguard-rules.pro'
34 | }
35 | release {
36 | minifyEnabled true
37 | proguardFiles 'proguard-rules.pro'
38 | }
39 | }
40 | }
41 |
42 | dependencies {
43 | compile project(":library")
44 | }
45 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Settings specified in this file will override any Gradle settings
5 | # configured through the IDE.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | VERSION_NAME=1.0.0
20 | VERSION_CODE=1
21 | GROUP=com.henorek
22 |
23 | POM_DESCRIPTION=Android library that allows launching a custom activity when your app crashes, instead of showing the hated "Unfortunately, X has stopped" dialog.
24 | POM_URL=https://github.com/henorek/crashczyk
25 | POM_SCM_URL=https://github.com/henorek/crashczyk.git
26 | POM_SCM_CONNECTION=scm:git@github.com:henorek/crashczyk.git
27 | POM_SCM_DEV_CONNECTION=scm:git@github.com:henorek/crashczyk.git
28 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
29 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
30 | POM_LICENCE_DIST=repo
31 | POM_DEVELOPER_ID=henorek
32 | POM_DEVELOPER_NAME=Jarek Jankowski
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Coś poszło nie tak, mój przyjacielu.
19 | Mój przyjacielu, jak wyrazić to, co czuję...
20 | Zatańczmy zatem jeszcze raz.
21 | Ma aplikacjo, byłaś mi na prawdę bliska.
22 | Czym jest dla mnie przyjaźń Twa.
23 | Czym jest dla mnie przyjaźń Twa.
24 | Możesz spać spokojnie, przyjaźń swoje prawa zna.
25 | Skopiujmy to nim skończy się ten bal.
26 | Skopiujmy to nim skończy się ten bal.
27 | Czym jest dla mnie przyjaźń Twa.
28 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
26 |
27 |
32 |
33 |
38 |
39 |
44 |
45 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
19 |
20 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | CustomActivityOnCrash sample
20 |
21 |
22 | Hello! Press one of the following buttons to crash the app:
23 | Crash main thread
24 | Crash background thread
25 | Crash with delay (5s)
26 |
27 |
28 | Something went wrong!
29 | An error occurred. We\'re deeply sorry.
30 | Error details:
31 | Unknown exception
32 | Restart app
33 | Close app
34 |
35 |
36 |
37 |
38 | An error occurred in this sample app. This is the default error activity with customized strings.
39 | No stacktrace available!
40 | Restart now
41 | Close now
42 | Error information
43 | Error information
44 | OK
45 |
46 |
47 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/henorek/sample/crashczyk/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.henorek.sample.crashczyk.activity;
18 |
19 | import android.app.Activity;
20 | import android.os.AsyncTask;
21 | import android.os.Bundle;
22 | import android.view.View;
23 | import android.widget.Button;
24 |
25 | import com.henorek.sample.crashczyk.R;
26 |
27 |
28 | public class MainActivity extends Activity {
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_main);
34 |
35 | Button crashMainThreadButton = (Button) findViewById(R.id.button_crash_main_thread);
36 | Button crashBgThreadButton = (Button) findViewById(R.id.button_crash_bg_thread);
37 | Button crashWithDelayButton = (Button) findViewById(R.id.button_crash_with_delay);
38 |
39 | crashMainThreadButton.setOnClickListener(new View.OnClickListener() {
40 | @Override
41 | public void onClick(View view) {
42 | throw new RuntimeException("I'm a cool exception and I crashed the main thread!");
43 | }
44 | });
45 |
46 | crashBgThreadButton.setOnClickListener(new View.OnClickListener() {
47 | @Override
48 | public void onClick(View view) {
49 | new AsyncTask() {
50 | @Override
51 | protected Void doInBackground(Void... voids) {
52 | throw new RuntimeException("I'm also cool, and I crashed the background thread!");
53 | }
54 | }.execute();
55 | }
56 | });
57 |
58 | crashWithDelayButton.setOnClickListener(new View.OnClickListener() {
59 | @Override
60 | public void onClick(View view) {
61 | new AsyncTask() {
62 | @Override
63 | protected Void doInBackground(Void... voids) {
64 | try {
65 | Thread.sleep(5000);
66 | } catch (InterruptedException e) {
67 | //meh
68 | }
69 | throw new RuntimeException("I am a not so cool exception, and I am delayed, so you can check if the app crashes when in background!)");
70 | }
71 | }.execute();
72 | }
73 | });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_custom_error.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
26 |
27 |
32 |
33 |
38 |
39 |
44 |
45 |
51 |
52 |
58 |
59 |
60 |
66 |
67 |
68 |
69 |
76 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/crashczyk_default_error_activity.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
23 |
27 |
28 |
37 |
38 |
44 |
45 |
53 |
54 |
60 |
61 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/henorek/sample/crashczyk/SampleCrashingApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.henorek.sample.crashczyk;
18 |
19 | import android.app.Application;
20 |
21 | import com.henorek.crashczyk.Crashczyk;
22 |
23 |
24 | public class SampleCrashingApplication extends Application {
25 |
26 | @Override
27 | public void onCreate() {
28 | super.onCreate();
29 |
30 | //You can uncomment any of the lines below, and test the results.
31 | //If you comment out the install one, the library will not work and the default Android crash dialog will be shown.
32 |
33 | //This makes the library not launch the error activity when the app crashes while it is in background.
34 | // Crashczyk.setLaunchErrorActivityWhenInBackground(false);
35 |
36 | //This sets the restart activity.
37 | //If you set this, this will be used. However, you can also set it with an intent-filter:
38 | //
39 | //If none are set, the default launch activity will be used.
40 | // Crashczyk.setRestartActivityClass(MainActivity.class);
41 |
42 | //This hides the "error details" button in the error activity, thus hiding the stack trace
43 | // Crashczyk.setShowErrorDetails(false);
44 |
45 | //This avoids the app from using the "Restart app" button and displaying a "Close app" button directly.
46 | //Be careful, even with restart app enabled, the Close app can still be displayed if no suitable
47 | //restart activity is found!
48 | // Crashczyk.setEnableAppRestart(false);
49 |
50 | //This shows a different image on the error activity, instead of the default upside-down bug.
51 | //You may use a drawable or a mipmap.
52 | // Crashczyk.setDefaultErrorActivityDrawable(R.mipmap.ic_launcher);
53 |
54 | //This sets a custom error activity class instead of the default one.
55 | //If you set this, this will be used. However, you can also set it with an intent-filter:
56 | //
57 | //If none are set, the default launch activity will be used.
58 | //Comment it (and disable the intent filter) to see the customization effects on the default error activity.
59 | //Uncomment to use the custom error activity
60 | // Crashczyk.setErrorActivityClass(CustomErrorActivity.class);
61 |
62 | //This enables Crashczyk
63 | Crashczyk.install(this);
64 |
65 | //In a normal app, you would now initialize your error handler as normal.
66 | //i.e., ACRA.init(this);
67 | //or Crashlytics.start(this);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/henorek/sample/crashczyk/activity/CustomErrorActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.henorek.sample.crashczyk.activity;
18 |
19 | import android.app.Activity;
20 | import android.content.Intent;
21 | import android.os.Bundle;
22 | import android.view.View;
23 | import android.widget.Button;
24 | import android.widget.TextView;
25 |
26 | import com.henorek.crashczyk.Crashczyk;
27 | import com.henorek.sample.crashczyk.R;
28 |
29 |
30 | public class CustomErrorActivity extends Activity {
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 |
36 | setContentView(R.layout.activity_custom_error);
37 |
38 | //**IMPORTANT**
39 | //The custom error activity in this sample is uglier than the default one and just
40 | //for demonstration purposes, please don't copy it to your project!
41 | //We recommend taking the original library's DefaultErrorActivity as a basis.
42 | //Of course, you are free to implement it as you wish in your application.
43 |
44 | //These three methods are available for you to use:
45 | //Crashczyk.getStackTraceFromIntent(getIntent()): gets the stack trace as a string
46 | //Crashczyk.getAllErrorDetailsFromIntent(context, getIntent()): returns all error details including stacktrace as a string
47 | //Crashczyk.getRestartActivityClassFromIntent(getIntent()): returns the class of the restart activity to launch, or null if none
48 |
49 | //Now, treat here the error as you wish. If you allow the user to restart or close the app,
50 | //don't forget to call the appropriate methods.
51 | //Otherwise, if you don't finish the activity, you will get the CustomErrorActivity on the activity stack and it will be visible again under some circumstances.
52 | //Also, you will get multiprocess problems in API<17.
53 |
54 | TextView errorDetailsText = (TextView) findViewById(R.id.error_details);
55 | errorDetailsText.setText(Crashczyk.getStackTraceFromIntent(getIntent()));
56 |
57 | Button restartButton = (Button) findViewById(R.id.restart_button);
58 |
59 | final Class extends Activity> restartActivityClass = Crashczyk.getRestartActivityClassFromIntent(getIntent());
60 |
61 | if (restartActivityClass != null) {
62 | restartButton.setText(R.string.restart_app);
63 | restartButton.setOnClickListener(new View.OnClickListener() {
64 | @Override
65 | public void onClick(View v) {
66 | Intent intent = new Intent(CustomErrorActivity.this, restartActivityClass);
67 | Crashczyk.restartApplicationWithIntent(CustomErrorActivity.this, intent);
68 | }
69 | });
70 | } else {
71 | restartButton.setOnClickListener(new View.OnClickListener() {
72 | @Override
73 | public void onClick(View v) {
74 | Crashczyk.closeApplication(CustomErrorActivity.this);
75 | }
76 | });
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/library/src/main/java/com/henorek/crashczyk/activity/DefaultErrorActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.henorek.crashczyk.activity;
18 |
19 | import android.app.Activity;
20 | import android.app.AlertDialog;
21 | import android.content.ClipData;
22 | import android.content.ClipboardManager;
23 | import android.content.DialogInterface;
24 | import android.content.Intent;
25 | import android.os.Build;
26 | import android.os.Bundle;
27 | import android.util.TypedValue;
28 | import android.view.View;
29 | import android.widget.Button;
30 | import android.widget.ImageView;
31 | import android.widget.TextView;
32 | import android.widget.Toast;
33 |
34 | import com.henorek.crashczyk.Crashczyk;
35 | import com.henorek.crashczyk.R;
36 |
37 |
38 | public final class DefaultErrorActivity extends Activity {
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 |
44 | setContentView(R.layout.crashczyk_default_error_activity);
45 |
46 | //Close/restart button logic:
47 | //If a class if set, use restart.
48 | //Else, use close and just finish the app.
49 | //It is recommended that you follow this logic if implementing a custom error activity.
50 | Button restartButton = (Button) findViewById(R.id.crashczyk_error_activity_restart_button);
51 |
52 | final Class extends Activity> restartActivityClass = Crashczyk.getRestartActivityClassFromIntent(getIntent());
53 |
54 | if (restartActivityClass != null) {
55 | restartButton.setText(R.string.crashczyk_error_activity_restart_app);
56 | restartButton.setOnClickListener(new View.OnClickListener() {
57 | @Override
58 | public void onClick(View v) {
59 | Intent intent = new Intent(DefaultErrorActivity.this, restartActivityClass);
60 | Crashczyk.restartApplicationWithIntent(DefaultErrorActivity.this, intent);
61 | }
62 | });
63 | } else {
64 | restartButton.setOnClickListener(new View.OnClickListener() {
65 | @Override
66 | public void onClick(View v) {
67 | Crashczyk.closeApplication(DefaultErrorActivity.this);
68 | }
69 | });
70 | }
71 |
72 | Button moreInfoButton = (Button) findViewById(R.id.crashczyk_error_activity_more_info_button);
73 |
74 | if (Crashczyk.isShowErrorDetailsFromIntent(getIntent())) {
75 |
76 | moreInfoButton.setOnClickListener(new View.OnClickListener() {
77 | @Override
78 | public void onClick(View v) {
79 | //We retrieve all the error data and show it
80 |
81 | AlertDialog dialog = new AlertDialog.Builder(DefaultErrorActivity.this)
82 | .setTitle(R.string.crashczyk_error_activity_error_details_title)
83 | .setMessage(Crashczyk.getAllErrorDetailsFromIntent(DefaultErrorActivity.this, getIntent()))
84 | .setPositiveButton(R.string.crashczyk_error_activity_error_details_close, null)
85 | .setNeutralButton(R.string.crashczyk_error_activity_error_details_copy,
86 | new DialogInterface.OnClickListener() {
87 | @Override
88 | public void onClick(DialogInterface dialog, int which) {
89 | copyErrorToClipboard();
90 | Toast.makeText(DefaultErrorActivity.this, R.string.crashczyk_error_activity_error_details_copied, Toast.LENGTH_SHORT).show();
91 | }
92 | })
93 | .show();
94 | TextView textView = (TextView) dialog.findViewById(android.R.id.message);
95 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.crashczyk_error_activity_error_details_text_size));
96 | }
97 | });
98 | } else {
99 | moreInfoButton.setVisibility(View.GONE);
100 | }
101 |
102 | int defaultErrorActivityDrawableId = Crashczyk.getDefaultErrorActivityDrawableIdFromIntent(getIntent());
103 | ImageView errorImageView = ((ImageView) findViewById(R.id.crashczyk_error_activity_image));
104 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
105 | errorImageView.setImageDrawable(getResources().getDrawable(defaultErrorActivityDrawableId, getTheme()));
106 | } else {
107 | //noinspection deprecation
108 | errorImageView.setImageDrawable(getResources().getDrawable(defaultErrorActivityDrawableId));
109 | }
110 | }
111 |
112 | private void copyErrorToClipboard() {
113 | String errorInformation =
114 | Crashczyk.getAllErrorDetailsFromIntent(DefaultErrorActivity.this, getIntent());
115 |
116 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
117 | ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
118 | ClipData clip = ClipData.newPlainText(getString(R.string.crashczyk_error_activity_error_details_clipboard_label), errorInformation);
119 | clipboard.setPrimaryClip(clip);
120 | } else {
121 | //noinspection deprecation
122 | android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
123 | clipboard.setText(errorInformation);
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Crashczyk library
2 |
3 | This library launch a custom activity with Krzysztof Krawczyk (Polish singer) quotes when your app crashes. It's based on [Ereza project](https://github.com/Ereza/CustomActivityOnCrash) and it's directed to Polish users.
4 |
5 | 
6 |
7 | ## Plans for the future
8 |
9 | * Handle different exceptions with different sets of quotes
10 | * Rewrite whole code to Kotlin
11 |
12 | ## How to use
13 |
14 | ### 1. Add a dependency
15 |
16 | Add the following dependency to your build.gradle:
17 | ```gradle
18 | dependencies {
19 | compile 'com.henorek:crashczyk:1.0.0'
20 | }
21 | ```
22 |
23 | You can also do it manually, by downloading the source code, importing the `library` folder as an Android Library Module, and adding a dependency on your project to that module.
24 |
25 | ### 2. Set up your application
26 |
27 | On your application class, use this snippet:
28 | ```java
29 | @Override
30 | public void onCreate() {
31 | super.onCreate();
32 |
33 | //Install Crashczyk
34 | Crashczyk.install(this);
35 |
36 | //Now initialize your error handlers as normal
37 | //i.e., ACRA.init(this);
38 | //or Crashlytics.start(this);
39 | }
40 | ```
41 |
42 | **WARNING!** If you already have ACRA, Crashlytics or any similar library in your app, it will still work as normal, but the CustomActivityOnCrash initialization **MUST** be done first, or the original reporting tool will stop working.
43 |
44 | ### 3. Test it
45 |
46 | Make the app crash by using something like this in your code:
47 | ```java
48 | throw new RuntimeException("Boom!");
49 | ```
50 |
51 | The error activity should show up, instead of the system dialog.
52 |
53 | ### Optional: Customization
54 |
55 | **Custom behavior**
56 |
57 | You can call the following methods at any time to customize how the library works, although usually you will call them before calling `install(context)`:
58 |
59 | ```java
60 | Crashczyk.setLaunchErrorActivityWhenInBackground(boolean);
61 | ```
62 | This method defines if the error activity should be launched when the app crashes while on background.
63 | By default, this is true. On API<14, it's always true since there is no way to detect if the app is in foreground.
64 | If you set it to `false`, a crash while in background won't launch the error activity nor the system dialog, so it will be a silent crash.
65 | The default is `true`.
66 |
67 | ```java
68 | Crashczyk.setShowErrorDetails(boolean);
69 | ```
70 | This method defines if the error activity must show a button with error details.
71 | If you set it to `false`, the button on the default error activity will disappear, thus disabling the user from seeing the stack trace.
72 | The default is `true`.
73 |
74 | ```java
75 | Crashczyk.setDefaultErrorActivityDrawable(int);
76 | ```
77 | This method allows changing the default upside-down bug image with an image of your choice.
78 | You may pass a resource id for a drawable or a mipmap.
79 | The default is `R.drawable.crashczyk_error_image`.
80 |
81 | ```java
82 | Crashczyk.setEnableAppRestart(boolean);
83 | ```
84 | This method defines if the error activity must show a "Restart app" button or a "Close app" button.
85 | If you set it to `false`, the button on the default error activity will close the app instead of restarting.
86 | Warning! If you set it to `true`, there is the possibility of it still displaying the "Close app" button,
87 | if no restart activity is specified or found!
88 | The default is `true`.
89 |
90 | ```java
91 | Crashczyk.setRestartActivityClass(Class extends Activity>);
92 | ```
93 | This method sets the activity that must be launched by the error activity when the user presses the button to restart the app.
94 | 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
95 | `cat.ereza.customactivityoncrash.RESTART`, and if there is none, the default launchable activity on your app.
96 | If no launchable activity can be found and you didn't specify any, the "restart app" button will become a "close app" button,
97 | even if `setEnableAppRestart` is set to `true`.
98 |
99 | As noted, you can also use the following intent-filter to specify the restart activity:
100 | ```xml
101 |
102 |
103 |
104 |
105 | ```
106 |
107 | ```java
108 | Crashczyk.setErrorActivityClass(Class extends Activity>);
109 | ```
110 | This method allows you to set a custom error activity to be launched, instead of the default one.
111 | Use it if you need further customization that is not just strings, colors or themes (see below).
112 | If you don't set it (or set it to null), the library will use first activity on your manifest that has an intent-filter with action
113 | `com.henorek.crashczyk.ERROR`, and if there is none, a default error activity from the library.
114 | If you use this, the activity **must** be declared in your `AndroidManifest.xml`, with `process` set to `:error_activity`.
115 |
116 | Example:
117 | ```xml
118 |
122 | ```
123 |
124 | As noted, you can also use the following intent-filter to specify the error activity:
125 | ```xml
126 |
127 |
128 |
129 |
130 | ```
131 |
132 | **Customization of the default activity**
133 |
134 | You can override several resources to customize the default activity:
135 |
136 | *Theme:*
137 |
138 | You can override the default error activity theme by defining a theme in your app with the following id: `CrashczykTheme`
139 |
140 | *Image:*
141 |
142 | By default, an image of a bug is displayed. You can change it to any image by creating a `crashczyk_error_image` drawable on all density buckets (mdpi, hdpi, xhdpi, xxhdpi and xxxhdpi).
143 | You can also use the provided `Crashczyk.setDefaultErrorActivityDrawable(int)` method.
144 |
145 | *Strings:*
146 |
147 | You can provide new strings and translations for the default error activity strings by overriding the following strings:
148 | ```xml
149 | An unexpected error occurred.\nSorry for the inconvenience.
150 | Unknown exception
151 | Restart app
152 | Close app
153 | Error details
154 | Error details
155 | Close
156 | Copy to clipboard
157 | Copied to clipboard
158 | Error information
159 | ```
160 |
161 | *There is a `sample` project module with examples of these overrides. If in doubt, check the code in that module.*
162 |
163 | **Completely custom error activity**
164 |
165 | If you choose to create your own completely custom error activity, you can use these methods:
166 |
167 | ```java
168 | Crashczyk.getStackTraceFromIntent(getIntent());
169 | ```
170 | Returns the stack trace that caused the error as a string.
171 |
172 | ```java
173 | Crashczyk.getAllErrorDetailsFromIntent(getIntent());
174 | ```
175 | Returns several error details including the stack trace that caused the error, as a string. This is used in the default error activity error details dialog.
176 |
177 | ```java
178 | Crashczyk.getRestartActivityClassFromIntent(getIntent());
179 | ```
180 | Returns the class of the activity you have to launch to restart the app, or `null` if not set.
181 |
182 | ```java
183 | Crashczyk.restartApplicationWithIntent(activity, intent);
184 | ```
185 | Kills the current process and restarts the app again with an `startActivity()` to the passed intent.
186 | You **MUST** call this to restart the app, or you will end up having several `Application` class instances and experience multiprocess issues in API<17.
187 |
188 | ```java
189 | Crashczyk.closeApplication(activity);
190 | ```
191 | Closes the app and kills the current process.
192 | You **MUST** call this to close the app, or you will end up having several Application class instances and experience multiprocess issues in API<17.
193 |
194 | *The `sample` project module includes an example of a custom error activity. If in doubt, check the code in that module.*
195 |
196 | ## Using Proguard?
197 |
198 | No need to add special rules, the library should work even with obfuscation.
199 |
200 | ## Inner workings
201 |
202 | This library relies on the `Thread.setDefaultUncaughtExceptionHandler` method.
203 | When an exception is caught by the library's `UncaughtExceptionHandler` it does the following:
204 |
205 | 1. Captures the stack trace that caused the crash
206 | 2. Launches a new intent to the error activity passing the stacktrace as an extra.
207 | 3. Kills the current process.
208 |
209 | The inner workings are based on [ACRA](https://github.com/ACRA/acra)'s dialog reporting mode with some minor tweaks. Look at the code if you need more detail about how it works.
210 |
211 | ## Incompatibilities
212 |
213 | * Crashczyk will not work in these cases:
214 | * With any custom `UncaughtExceptionHandler` set after initializing the library, that does not call back to the original handler.
215 | * With ACRA enabled and reporting mode set to `TOAST` or `DIALOG`.
216 | * If you use a custom `UncaughtExceptionHandler`, it will not be called if you initialize it before the library initialization (so, Crashlytics or ACRA initialization must be done **after** Crashczyk initialization).
217 | * On some rare cases on devices with API<14, the app may enter a restart loop when a crash occurs. Therefore, using it on API<14 is not recommended.
218 | * If your app initialization or error activity crash, there is a possibility of entering an infinite restart loop (this is checked by the library for the most common cases, but could happen in rarer cases).
219 | * The library has not been tested with multidex enabled. It uses Class.forName() to load classes, so maybe that could cause some problem. If you test it with multidex enabled, please provide feedback!
220 | * The library has not been tested with multiprocess apps. If you test it with such configuration, please provide feedback too!
221 |
222 | ## Disclaimers
223 |
224 | * This will not avoid ANRs from happening.
225 | * This will not catch native errors.
226 | * There is no guarantee that this will work on every device.
227 | * This library will not make you toast for breakfast :)
228 |
229 | ## Contributing & license
230 |
231 | Any contribution in order to make this library better will be welcome!
232 |
233 | The library is licensed under the [Apache License 2.0](https://github.com/Ereza/CustomActivityOnCrash/blob/master/LICENSE).
234 |
235 | The bug image used in the default error activity is licensed under CC-BY by Riff: https://www.sketchport.com/drawing/6119265933459456/lady-bug
236 | If you use the image in your app, don't forget to mention that!
237 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/library/src/main/java/com/henorek/crashczyk/Crashczyk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Eduard Ereza Martínez
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | *
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.henorek.crashczyk;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.app.Activity;
21 | import android.app.Application;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.content.pm.ApplicationInfo;
25 | import android.content.pm.PackageInfo;
26 | import android.content.pm.PackageManager;
27 | import android.content.pm.ResolveInfo;
28 | import android.os.Build;
29 | import android.os.Bundle;
30 | import android.util.Log;
31 |
32 | import com.henorek.crashczyk.activity.DefaultErrorActivity;
33 |
34 | import java.io.PrintWriter;
35 | import java.io.Serializable;
36 | import java.io.StringWriter;
37 | import java.lang.ref.WeakReference;
38 | import java.text.DateFormat;
39 | import java.text.SimpleDateFormat;
40 | import java.util.Date;
41 | import java.util.List;
42 | import java.util.Locale;
43 | import java.util.zip.ZipEntry;
44 | import java.util.zip.ZipFile;
45 |
46 | @SuppressLint("NewApi")
47 | public final class Crashczyk {
48 |
49 | //Extras passed to the error activity
50 | private static final String EXTRA_RESTART_ACTIVITY_CLASS = "com.henorek.crashczyk.EXTRA_RESTART_ACTIVITY_CLASS";
51 | private static final String EXTRA_SHOW_ERROR_DETAILS = "com.henorek.crashczyk.EXTRA_SHOW_ERROR_DETAILS";
52 | private static final String EXTRA_STACK_TRACE = "com.henorek.crashczyk.EXTRA_STACK_TRACE";
53 | private static final String EXTRA_IMAGE_DRAWABLE_ID = "com.henorek.crashczyk.EXTRA_IMAGE_DRAWABLE_ID";
54 |
55 | //General constants
56 | private final static String TAG = "Crashczyk";
57 | private static final String INTENT_ACTION_ERROR_ACTIVITY = "com.henorek.crashczyk.ERROR";
58 | private static final String INTENT_ACTION_RESTART_ACTIVITY = "com.henorek.crashczyk.RESTART";
59 | private static final String CAOC_HANDLER_PACKAGE_NAME = "com.henorek.crashczyk";
60 | private static final String DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os";
61 | private static final int MAX_STACK_TRACE_SIZE = 131071; //128 KB - 1
62 |
63 | //Internal variables
64 | private static Application application;
65 | private static WeakReference lastActivityCreated = new WeakReference<>(null);
66 | private static boolean isInBackground = false;
67 |
68 | //Settable properties and their defaults
69 | private static boolean launchErrorActivityWhenInBackground = true;
70 | private static boolean showErrorDetails = true;
71 | private static boolean enableAppRestart = true;
72 | private static int defaultErrorActivityDrawableId = R.drawable.crashczyk_error_image;
73 | private static Class extends Activity> errorActivityClass = null;
74 | private static Class extends Activity> restartActivityClass = null;
75 |
76 | /**
77 | * Installs Crashczyk on the application using the default error activity.
78 | *
79 | * @param context Context to use for obtaining the ApplicationContext. Must not be null.
80 | */
81 | public static void install(Context context) {
82 | try {
83 | if (context == null) {
84 | Log.e(TAG, "Install failed: context is null!");
85 | } else {
86 | if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
87 | Log.w(TAG, "Crashczyk will be installed, but may not be reliable in API lower than 14");
88 | }
89 |
90 | //INSTALL!
91 | Thread.UncaughtExceptionHandler oldHandler = Thread.getDefaultUncaughtExceptionHandler();
92 |
93 | if (oldHandler != null && oldHandler.getClass().getName().startsWith(CAOC_HANDLER_PACKAGE_NAME)) {
94 | Log.e(TAG, "You have already installed Crashczyk, doing nothing!");
95 | } else {
96 | if (oldHandler != null && !oldHandler.getClass().getName().startsWith(DEFAULT_HANDLER_PACKAGE_NAME)) {
97 | Log.e(TAG, "IMPORTANT WARNING! You already have an UncaughtExceptionHandler, are you sure this is correct? If you use ACRA, Crashlytics or similar libraries, you must initialize them AFTER Crashczyk! Installing anyway, but your original handler will not be called.");
98 | }
99 |
100 | application = (Application) context.getApplicationContext();
101 |
102 | //We define a default exception handler that does what we want so it can be called from Crashlytics/ACRA
103 | Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
104 | @Override
105 | public void uncaughtException(Thread thread, final Throwable throwable) {
106 | Log.e(TAG, "App has crashed, executing Crashczyk's UncaughtExceptionHandler", throwable);
107 |
108 | if (errorActivityClass == null) {
109 | errorActivityClass = guessErrorActivityClass(application);
110 | }
111 |
112 | if (isStackTraceLikelyConflictive(throwable, errorActivityClass)) {
113 | Log.e(TAG, "Your application class or your error activity have crashed, the custom activity will not be launched!");
114 | } else {
115 | if (launchErrorActivityWhenInBackground || !isInBackground) {
116 | final Intent intent = new Intent(application, errorActivityClass);
117 | StringWriter sw = new StringWriter();
118 | PrintWriter pw = new PrintWriter(sw);
119 | throwable.printStackTrace(pw);
120 | String stackTraceString = sw.toString();
121 |
122 | //Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
123 | //The limit is 1MB on Android but some devices seem to have it lower.
124 | //See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
125 | //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
126 | if (stackTraceString.length() > MAX_STACK_TRACE_SIZE) {
127 | String disclaimer = " [stack trace too large]";
128 | stackTraceString = stackTraceString.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;
129 | }
130 |
131 | if (enableAppRestart && restartActivityClass == null) {
132 | //We can set the restartActivityClass because the app will terminate right now,
133 | //and when relaunched, will be null again by default.
134 | restartActivityClass = guessRestartActivityClass(application);
135 | } else if (!enableAppRestart) {
136 | //In case someone sets the activity and then decides to not restart
137 | restartActivityClass = null;
138 | }
139 |
140 | intent.putExtra(EXTRA_STACK_TRACE, stackTraceString);
141 | intent.putExtra(EXTRA_RESTART_ACTIVITY_CLASS, restartActivityClass);
142 | intent.putExtra(EXTRA_SHOW_ERROR_DETAILS, showErrorDetails);
143 | intent.putExtra(EXTRA_IMAGE_DRAWABLE_ID, defaultErrorActivityDrawableId);
144 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
145 | application.startActivity(intent);
146 | }
147 | }
148 | final Activity lastActivity = lastActivityCreated.get();
149 | if (lastActivity != null) {
150 | //We finish the activity, this solves a bug which causes infinite recursion.
151 | //This is unsolvable in API<14, so beware!
152 | //See: https://github.com/ACRA/acra/issues/42
153 | lastActivity.finish();
154 | lastActivityCreated.clear();
155 | }
156 | killCurrentProcess();
157 | }
158 | });
159 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
160 | application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
161 | int currentlyStartedActivities = 0;
162 |
163 | @Override
164 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
165 | if (activity.getClass() != errorActivityClass) {
166 | // Copied from ACRA:
167 | // Ignore activityClass because we want the last
168 | // application Activity that was started so that we can
169 | // explicitly kill it off.
170 | lastActivityCreated = new WeakReference<>(activity);
171 | }
172 | }
173 |
174 | @Override
175 | public void onActivityStarted(Activity activity) {
176 | currentlyStartedActivities++;
177 | isInBackground = (currentlyStartedActivities == 0);
178 | //Do nothing
179 | }
180 |
181 | @Override
182 | public void onActivityResumed(Activity activity) {
183 | //Do nothing
184 | }
185 |
186 | @Override
187 | public void onActivityPaused(Activity activity) {
188 | //Do nothing
189 | }
190 |
191 | @Override
192 | public void onActivityStopped(Activity activity) {
193 | //Do nothing
194 | currentlyStartedActivities--;
195 | isInBackground = (currentlyStartedActivities == 0);
196 | }
197 |
198 | @Override
199 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
200 | //Do nothing
201 | }
202 |
203 | @Override
204 | public void onActivityDestroyed(Activity activity) {
205 | //Do nothing
206 | }
207 | });
208 | }
209 |
210 | Log.i(TAG, "Crashczyk has been installed.");
211 | }
212 | }
213 | } catch (Throwable t) {
214 | Log.e(TAG, "An unknown error occurred while installing Crashczyk, it may not have been properly initialized. Please report this as a bug if needed.", t);
215 | }
216 | }
217 |
218 | /**
219 | * Given an Intent, returns if the error details button should be displayed.
220 | *
221 | * @param intent The Intent. Must not be null.
222 | * @return true if the button must be shown, false otherwise.
223 | */
224 | public static boolean isShowErrorDetailsFromIntent(Intent intent) {
225 | return intent.getBooleanExtra(Crashczyk.EXTRA_SHOW_ERROR_DETAILS, true);
226 | }
227 |
228 | /**
229 | * Given an Intent, returns the drawable id of the image to show on the default error activity.
230 | *
231 | * @param intent The Intent. Must not be null.
232 | * @return The id of the drawable to use.
233 | */
234 | public static int getDefaultErrorActivityDrawableIdFromIntent(Intent intent) {
235 | return intent.getIntExtra(Crashczyk.EXTRA_IMAGE_DRAWABLE_ID, R.drawable.crashczyk_error_image);
236 | }
237 |
238 | /**
239 | * Given an Intent, returns the stack trace extra from it.
240 | *
241 | * @param intent The Intent. Must not be null.
242 | * @return The stacktrace, or null if not provided.
243 | */
244 | public static String getStackTraceFromIntent(Intent intent) {
245 | return intent.getStringExtra(Crashczyk.EXTRA_STACK_TRACE);
246 | }
247 |
248 | /**
249 | * Given an Intent, returns several error details including the stack trace extra from the intent.
250 | *
251 | * @param context A valid context. Must not be null.
252 | * @param intent The Intent. Must not be null.
253 | * @return The full error details.
254 | */
255 | public static String getAllErrorDetailsFromIntent(Context context, Intent intent) {
256 | //I don't think that this needs localization because it's a development string...
257 |
258 | Date currentDate = new Date();
259 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
260 |
261 | //Get build date
262 | String buildDateAsString = getBuildDateAsString(context, dateFormat);
263 |
264 | //Get app version
265 | String versionName = getVersionName(context);
266 |
267 | String errorDetails = "";
268 |
269 | errorDetails += "Build version: " + versionName + " \n";
270 | errorDetails += "Build date: " + buildDateAsString + " \n";
271 | errorDetails += "Current date: " + dateFormat.format(currentDate) + " \n";
272 | errorDetails += "Device: " + getDeviceModelName() + " \n\n";
273 | errorDetails += "Stack trace: \n";
274 | errorDetails += getStackTraceFromIntent(intent);
275 | return errorDetails;
276 | }
277 |
278 | /**
279 | * Given an Intent, returns the restart activity class extra from it.
280 | *
281 | * @param intent The Intent. Must not be null.
282 | * @return The restart activity class, or null if not provided.
283 | */
284 | @SuppressWarnings("unchecked")
285 | public static Class extends Activity> getRestartActivityClassFromIntent(Intent intent) {
286 | Serializable serializedClass = intent.getSerializableExtra(Crashczyk.EXTRA_RESTART_ACTIVITY_CLASS);
287 |
288 | if (serializedClass != null && serializedClass instanceof Class) {
289 | return (Class extends Activity>) serializedClass;
290 | } else {
291 | return null;
292 | }
293 | }
294 |
295 | /**
296 | * Given an Intent, restarts the app and launches a startActivity to that intent.
297 | * The flags NEW_TASK and CLEAR_TASK are set if the Intent does not have them, to ensure
298 | * the app stack is fully cleared.
299 | * Must only be used from your error activity.
300 | *
301 | * @param activity The current error activity. Must not be null.
302 | * @param intent The Intent. Must not be null.
303 | */
304 | public static void restartApplicationWithIntent(Activity activity, Intent intent) {
305 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
306 | activity.finish();
307 | activity.startActivity(intent);
308 | killCurrentProcess();
309 | }
310 |
311 | /**
312 | * Closes the app. Must only be used from your error activity.
313 | *
314 | * @param activity The current error activity. Must not be null.
315 | */
316 | public static void closeApplication(Activity activity) {
317 | activity.finish();
318 | killCurrentProcess();
319 | }
320 |
321 |
322 | /// SETTERS AND GETTERS FOR THE CUSTOMIZABLE PROPERTIES
323 |
324 | /**
325 | * Returns if the error activity must be launched when the app is on background.
326 | *
327 | * @return true if it will be launched, false otherwise.
328 | */
329 | public static boolean isLaunchErrorActivityWhenInBackground() {
330 | return launchErrorActivityWhenInBackground;
331 | }
332 |
333 | /**
334 | * Defines if the error activity must be launched when the app is on background.
335 | * Set it to true if you want to launch the error activity when the app is in background,
336 | * false if you want it not to launch and crash silently.
337 | * This has no effect in API 14 and the error activity is always launched.
338 | * The default is true (the app will be brought to front when a crash occurs).
339 | */
340 | public static void setLaunchErrorActivityWhenInBackground(boolean launchErrorActivityWhenInBackground) {
341 | Crashczyk.launchErrorActivityWhenInBackground = launchErrorActivityWhenInBackground;
342 | }
343 |
344 | /**
345 | * Returns if the error activity will show the error details button.
346 | *
347 | * @return true if it will be shown, false otherwise.
348 | */
349 | public static boolean isShowErrorDetails() {
350 | return showErrorDetails;
351 | }
352 |
353 | /**
354 | * Defines if the error activity must shown the error details button.
355 | * Set it to true if you want to show the full stack trace and device info,
356 | * false if you want it to be hidden.
357 | * The default is true.
358 | */
359 | public static void setShowErrorDetails(boolean showErrorDetails) {
360 | Crashczyk.showErrorDetails = showErrorDetails;
361 | }
362 |
363 | /**
364 | * Returns the default error activity drawable identifier.
365 | *
366 | * @return the default error activity drawable identifier
367 | */
368 | public static int getDefaultErrorActivityDrawable() {
369 | return defaultErrorActivityDrawableId;
370 | }
371 |
372 | /**
373 | * Defines which drawable to use in the default error activity image.
374 | * Set this if you want to use an image other than the default one.
375 | * The default is R.drawable.crashczyk_error_image (a cute upside-down bug).
376 | */
377 | public static void setDefaultErrorActivityDrawable(int defaultErrorActivityDrawableId) {
378 | Crashczyk.defaultErrorActivityDrawableId = defaultErrorActivityDrawableId;
379 | }
380 |
381 | /**
382 | * Returns if the error activity should show a restart button.
383 | * Note that even if restart is enabled, a valid restart activity could not be found.
384 | * In that case, a close button will still be used.
385 | *
386 | * @return true if a restart button should be shown, false if a close button must be used.
387 | */
388 | public static boolean isEnableAppRestart() {
389 | return enableAppRestart;
390 | }
391 |
392 | /**
393 | * Defines if the error activity should show a restart button.
394 | * Set it to true if you want to show a restart button,
395 | * false if you want to show a close button.
396 | * Note that even if restart is enabled, a valid restart activity could not be found.
397 | * In that case, a close button will still be used.
398 | * The default is true.
399 | */
400 | public static void setEnableAppRestart(boolean enableAppRestart) {
401 | Crashczyk.enableAppRestart = enableAppRestart;
402 | }
403 |
404 | /**
405 | * Returns the error activity class to launch when a crash occurs.
406 | *
407 | * @return The class, or null if not set.
408 | */
409 | public static Class extends Activity> getErrorActivityClass() {
410 | return errorActivityClass;
411 | }
412 |
413 | /**
414 | * Sets the error activity class to launch when a crash occurs.
415 | * If null,the default error activity will be used.
416 | */
417 | public static void setErrorActivityClass(Class extends Activity> errorActivityClass) {
418 | Crashczyk.errorActivityClass = errorActivityClass;
419 | }
420 |
421 | /**
422 | * Returns the main activity class that the error activity must launch when a crash occurs.
423 | *
424 | * @return The class, or null if not set.
425 | */
426 | public static Class extends Activity> getRestartActivityClass() {
427 | return restartActivityClass;
428 | }
429 |
430 | /**
431 | * Sets the main activity class that the error activity must launch when a crash occurs.
432 | * If not set or set to null, the default error activity will close instead.
433 | */
434 | public static void setRestartActivityClass(Class extends Activity> restartActivityClass) {
435 | Crashczyk.restartActivityClass = restartActivityClass;
436 | }
437 |
438 |
439 | /// INTERNAL METHODS NOT TO BE USED BY THIRD PARTIES
440 |
441 | /**
442 | * INTERNAL method that checks if the stack trace that just crashed is conflictive. This is true in the following scenarios:
443 | * - The application has crashed while initializing (handleBindApplication is in the stack)
444 | * - The error activity has crashed (activityClass is in the stack)
445 | *
446 | * @param throwable The throwable from which the stack trace will be checked
447 | * @param activityClass The activity class to launch when the app crashes
448 | * @return true if this stack trace is conflictive and the activity must not be launched, false otherwise
449 | */
450 | private static boolean isStackTraceLikelyConflictive(Throwable throwable, Class extends Activity> activityClass) {
451 | do {
452 | StackTraceElement[] stackTrace = throwable.getStackTrace();
453 | for (StackTraceElement element : stackTrace) {
454 | if ((element.getClassName().equals("android.app.ActivityThread") && element.getMethodName().equals("handleBindApplication")) || element.getClassName().equals(activityClass.getName())) {
455 | return true;
456 | }
457 | }
458 | } while ((throwable = throwable.getCause()) != null);
459 | return false;
460 | }
461 |
462 | /**
463 | * INTERNAL method that returns the build date of the current APK as a string, or null if unable to determine it.
464 | *
465 | * @param context A valid context. Must not be null.
466 | * @param dateFormat DateFormat to use to convert from Date to String
467 | * @return The formatted date, or "Unknown" if unable to determine it.
468 | */
469 | private static String getBuildDateAsString(Context context, DateFormat dateFormat) {
470 | String buildDate;
471 | try {
472 | ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
473 | ZipFile zf = new ZipFile(ai.sourceDir);
474 | ZipEntry ze = zf.getEntry("classes.dex");
475 | long time = ze.getTime();
476 | buildDate = dateFormat.format(new Date(time));
477 | zf.close();
478 | } catch (Exception e) {
479 | buildDate = "Unknown";
480 | }
481 | return buildDate;
482 | }
483 |
484 | /**
485 | * INTERNAL method that returns the version name of the current app, or null if unable to determine it.
486 | *
487 | * @param context A valid context. Must not be null.
488 | * @return The version name, or "Unknown if unable to determine it.
489 | */
490 | private static String getVersionName(Context context) {
491 | try {
492 | PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
493 | return packageInfo.versionName;
494 | } catch (Exception e) {
495 | return "Unknown";
496 | }
497 | }
498 |
499 | /**
500 | * INTERNAL method that returns the device model name with correct capitalization.
501 | * Taken from: http://stackoverflow.com/a/12707479/1254846
502 | *
503 | * @return The device model name (i.e., "LGE Nexus 5")
504 | */
505 | private static String getDeviceModelName() {
506 | String manufacturer = Build.MANUFACTURER;
507 | String model = Build.MODEL;
508 | if (model.startsWith(manufacturer)) {
509 | return capitalize(model);
510 | } else {
511 | return capitalize(manufacturer) + " " + model;
512 | }
513 | }
514 |
515 | /**
516 | * INTERNAL method that capitalizes the first character of a string
517 | *
518 | * @param s The string to capitalize
519 | * @return The capitalized string
520 | */
521 | private static String capitalize(String s) {
522 | if (s == null || s.length() == 0) {
523 | return "";
524 | }
525 | char first = s.charAt(0);
526 | if (Character.isUpperCase(first)) {
527 | return s;
528 | } else {
529 | return Character.toUpperCase(first) + s.substring(1);
530 | }
531 | }
532 |
533 | /**
534 | * INTERNAL method used to guess which activity must be called from the error activity to restart the app.
535 | * It will first get activities from the AndroidManifest with intent filter ,
536 | * if it cannot find them, then it will get the default launcher.
537 | * If there is no default launcher, this returns null.
538 | *
539 | * @param context A valid context. Must not be null.
540 | * @return The guessed restart activity class, or null if no suitable one is found
541 | */
542 | private static Class extends Activity> guessRestartActivityClass(Context context) {
543 | Class extends Activity> resolvedActivityClass;
544 |
545 | //If action is defined, use that
546 | resolvedActivityClass = Crashczyk.getRestartActivityClassWithIntentFilter(context);
547 |
548 | //Else, get the default launcher activity
549 | if (resolvedActivityClass == null) {
550 | resolvedActivityClass = getLauncherActivity(context);
551 | }
552 |
553 | return resolvedActivityClass;
554 | }
555 |
556 | /**
557 | * INTERNAL method used to get the first activity with an intent-filter ,
558 | * If there is no activity with that intent filter, this returns null.
559 | *
560 | * @param context A valid context. Must not be null.
561 | * @return A valid activity class, or null if no suitable one is found
562 | */
563 | @SuppressWarnings("unchecked")
564 | private static Class extends Activity> getRestartActivityClassWithIntentFilter(Context context) {
565 | List resolveInfos = context.getPackageManager().queryIntentActivities(
566 | new Intent().setAction(INTENT_ACTION_RESTART_ACTIVITY),
567 | PackageManager.GET_RESOLVED_FILTER);
568 |
569 | if (resolveInfos != null && resolveInfos.size() > 0) {
570 | ResolveInfo resolveInfo = resolveInfos.get(0);
571 | try {
572 | return (Class extends Activity>) Class.forName(resolveInfo.activityInfo.name);
573 | } catch (ClassNotFoundException e) {
574 | //Should not happen, print it to the log!
575 | Log.e(TAG, "Failed when resolving the restart activity class via intent filter, stack trace follows!", e);
576 | }
577 | }
578 |
579 | return null;
580 | }
581 |
582 | /**
583 | * INTERNAL method used to get the default launcher activity for the app.
584 | * If there is no launchable activity, this returns null.
585 | *
586 | * @param context A valid context. Must not be null.
587 | * @return A valid activity class, or null if no suitable one is found
588 | */
589 | @SuppressWarnings("unchecked")
590 | private static Class extends Activity> getLauncherActivity(Context context) {
591 | Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
592 | if (intent != null) {
593 | try {
594 | return (Class extends Activity>) Class.forName(intent.getComponent().getClassName());
595 | } catch (ClassNotFoundException e) {
596 | //Should not happen, print it to the log!
597 | Log.e(TAG, "Failed when resolving the restart activity class via getLaunchIntentForPackage, stack trace follows!", e);
598 | }
599 | }
600 |
601 | return null;
602 | }
603 |
604 | /**
605 | * INTERNAL method used to guess which error activity must be called when the app crashes.
606 | * It will first get activities from the AndroidManifest with intent filter ,
607 | * if it cannot find them, then it will use the default error activity.
608 | *
609 | * @param context A valid context. Must not be null.
610 | * @return The guessed error activity class, or the default error activity if not found
611 | */
612 | private static Class extends Activity> guessErrorActivityClass(Context context) {
613 | Class extends Activity> resolvedActivityClass;
614 |
615 | //If action is defined, use that
616 | resolvedActivityClass = Crashczyk.getErrorActivityClassWithIntentFilter(context);
617 |
618 | //Else, get the default launcher activity
619 | if (resolvedActivityClass == null) {
620 | resolvedActivityClass = DefaultErrorActivity.class;
621 | }
622 |
623 | return resolvedActivityClass;
624 | }
625 |
626 | /**
627 | * INTERNAL method used to get the first activity with an intent-filter ,
628 | * If there is no activity with that intent filter, this returns null.
629 | *
630 | * @param context A valid context. Must not be null.
631 | * @return A valid activity class, or null if no suitable one is found
632 | */
633 | @SuppressWarnings("unchecked")
634 | private static Class extends Activity> getErrorActivityClassWithIntentFilter(Context context) {
635 | List resolveInfos = context.getPackageManager().queryIntentActivities(
636 | new Intent().setAction(INTENT_ACTION_ERROR_ACTIVITY),
637 | PackageManager.GET_RESOLVED_FILTER);
638 |
639 | if (resolveInfos != null && resolveInfos.size() > 0) {
640 | ResolveInfo resolveInfo = resolveInfos.get(0);
641 | try {
642 | return (Class extends Activity>) Class.forName(resolveInfo.activityInfo.name);
643 | } catch (ClassNotFoundException e) {
644 | //Should not happen, print it to the log!
645 | Log.e(TAG, "Failed when resolving the error activity class via intent filter, stack trace follows!", e);
646 | }
647 | }
648 |
649 | return null;
650 | }
651 |
652 | /**
653 | * INTERNAL method that kills the current process.
654 | * It is used after restarting or killing the app.
655 | */
656 | private static void killCurrentProcess() {
657 | android.os.Process.killProcess(android.os.Process.myPid());
658 | System.exit(10);
659 | }
660 | }
661 |
--------------------------------------------------------------------------------