diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index be23f69..427ed23 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -8,10 +8,10 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: set up JDK 1.8
+ - name: set up JDK 11
uses: actions/setup-java@v1
with:
- java-version: 1.8
+ java-version: 11
- name: Build with Gradle
run: ./gradlew build test
\ No newline at end of file
diff --git a/.github/workflows/style-check.yml b/.github/workflows/style-check.yml
index 9cd7ce0..37d611f 100644
--- a/.github/workflows/style-check.yml
+++ b/.github/workflows/style-check.yml
@@ -8,10 +8,10 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: set up JDK 1.8
+ - name: set up JDK 11
uses: actions/setup-java@v1
with:
- java-version: 1.8
+ java-version: 11
- name: Ktlint check
run: ./gradlew ktlintCheck
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 61a9130..fb7f4a8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..875a112
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7bb9736..b8e0b1b 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,34 @@
+
+
+
-
+
diff --git a/.jitpack.yml b/.jitpack.yml
new file mode 100644
index 0000000..f78f664
--- /dev/null
+++ b/.jitpack.yml
@@ -0,0 +1 @@
+jdk: openjdk11
\ No newline at end of file
diff --git a/README.md b/README.md
index b5ebd52..97b9cf8 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,15 @@ dependencies {
The library ships with Proguard rules to ensure that it works correctly even after minification.
+### Multi-Process Service
+
+`WhatTheStack` runs a bound service in a separate process to show you the error screen on a crash.
+
+We need to run this code in a separate process because you can't reliably launch new Activities
+in the host application's process after an uncaught exception is thrown.
+
+
+
## Contributions
We are happy to accept any external contributions in the form of PRs, issues, or blog posts.
diff --git a/app/build.gradle b/app/build.gradle
index e469a44..297b44a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -41,7 +41,11 @@ android {
}
buildFeatures {
- viewBinding true
+ compose true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion "1.0.5"
}
}
@@ -53,9 +57,12 @@ dependencies {
implementation libs.kotlinStdLib
implementation libs.appCompat
implementation libs.coreKtx
- implementation libs.fragmentKtx
- implementation libs.constraintLayout
- implementation libs.materialComponents
+
+ implementation libs.composeActivity
+ implementation libs.composeMaterial
+ implementation libs.composeTooling
+ implementation libs.accompanistSysUi
+ implementation libs.accompanistInsets
testImplementation libs.junit
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6840f78..b7bfb9e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,11 +5,13 @@
-
+
diff --git a/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt b/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt
index 4c890cc..a4cfa9d 100644
--- a/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt
+++ b/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt
@@ -1,20 +1,19 @@
package com.haroldadmin.crashyapp
import android.os.Bundle
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import com.haroldadmin.crashyapp.databinding.ActivityMainBinding
+import com.haroldadmin.crashyapp.ui.pages.HomePage
+import com.haroldadmin.crashyapp.ui.theme.CrashyAppTheme
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val binding = ActivityMainBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
- binding.crashButton.setOnClickListener {
- throw BecauseICanException()
+ setContent {
+ CrashyAppTheme {
+ HomePage()
+ }
}
}
}
-
-private class BecauseICanException : Exception("This exception is thrown purely because it can be thrown")
diff --git a/app/src/main/java/com/haroldadmin/crashyapp/ui/pages/HomePage.kt b/app/src/main/java/com/haroldadmin/crashyapp/ui/pages/HomePage.kt
new file mode 100644
index 0000000..66eb41c
--- /dev/null
+++ b/app/src/main/java/com/haroldadmin/crashyapp/ui/pages/HomePage.kt
@@ -0,0 +1,41 @@
+package com.haroldadmin.crashyapp.ui.pages
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun HomePage() {
+ Scaffold(
+ topBar = {
+ TopAppBar {
+ Text(text = "Crashy App", style = MaterialTheme.typography.h6)
+ }
+ }
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .padding(8.dp)
+ .fillMaxWidth()
+ .fillMaxHeight()
+ ) {
+ Text(
+ text = "Press the button to see the error screen from WhatTheStack!",
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = { throw BecauseICanException() }) {
+ Text(text = "Crash!")
+ }
+ }
+ }
+}
+
+private class BecauseICanException :
+ Exception("This exception is thrown purely because it can be thrown")
diff --git a/app/src/main/java/com/haroldadmin/crashyapp/ui/theme/CrashyAppTheme.kt b/app/src/main/java/com/haroldadmin/crashyapp/ui/theme/CrashyAppTheme.kt
new file mode 100644
index 0000000..c14ff85
--- /dev/null
+++ b/app/src/main/java/com/haroldadmin/crashyapp/ui/theme/CrashyAppTheme.kt
@@ -0,0 +1,20 @@
+package com.haroldadmin.crashyapp.ui.theme
+
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+private val ColorPalette = lightColors(
+ primary = Color(0xffd32f2f),
+ primaryVariant = Color(0xff9a0007),
+ secondary = Color(0xff616161),
+ secondaryVariant = Color(0x33373737),
+)
+
+@Composable
+fun CrashyAppTheme(
+ content: @Composable () -> Unit
+) {
+ MaterialTheme(colors = ColorPalette, content = content)
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 0131968..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index f12742b..0000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- #d32f2f
- #ff6659
- #9a0007
- #616161
- #8e8e8e
- #373737
- #ffffff
- #ffffff
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
deleted file mode 100644
index d1882f5..0000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Crashy App
-
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 05ba9fc..f3f0b8f 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,13 +1,4 @@
-
-
-
diff --git a/build.gradle b/build.gradle
index 613bb94..658afa7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,9 @@
buildscript {
ext.buildConfig = [
"applicationId": "com.haroldadmin.crashyapp",
- "compileSdk" : 29,
+ "compileSdk" : 31,
"minSdk" : 21,
- "targetSdk" : 29,
+ "targetSdk" : 31,
"versionCode" : 1,
"versionName" : "0.0.1"
]
@@ -33,7 +33,7 @@ allprojects {
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
ktlint {
- version = "0.36.0"
+ version = "0.43.0"
ignoreFailures = false
disabledRules = ["no-wildcard-imports"]
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2a3d007..35a7f95 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
-kotlin = "1.5.20"
-agp = "4.2.0"
+kotlin = "1.5.31"
+agp = "7.0.3"
ktlint = "10.1.0"
appCompat = "1.3.0"
coreTest = "2.0.0"
@@ -17,9 +17,17 @@ espressoCore = "3.2.0"
mockk = "1.9.3"
robolectric = "4.3.1"
startup = "1.0.0"
+composeActivity = "1.3.1"
+compose = "1.0.5"
+accompanist = "0.21.3-beta"
[libraries]
+composeActivity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" }
+composeMaterial = { module = "androidx.compose.material:material", version.ref = "compose" }
+composeTooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
+accompanistSysUi = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
+accompanistInsets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" }
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
ktlintGradlePlugin = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" }
diff --git a/what-the-stack/build.gradle b/what-the-stack/build.gradle
index 564ebb8..a9122bc 100644
--- a/what-the-stack/build.gradle
+++ b/what-the-stack/build.gradle
@@ -36,7 +36,11 @@ android {
}
buildFeatures {
- viewBinding true
+ compose true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion libs.versions.compose.get()
}
}
@@ -46,11 +50,13 @@ dependencies {
implementation libs.kotlinStdLib
implementation libs.appCompat
implementation libs.coreKtx
- implementation libs.fragmentKtx
- implementation libs.constraintLayout
- implementation libs.materialComponents
implementation libs.startup
- implementation libs.insetter
+
+ implementation libs.composeActivity
+ implementation libs.composeMaterial
+ implementation libs.composeTooling
+ implementation libs.accompanistSysUi
+ implementation libs.accompanistInsets
testImplementation libs.junit
testImplementation libs.mockk
diff --git a/what-the-stack/src/main/AndroidManifest.xml b/what-the-stack/src/main/AndroidManifest.xml
index 5f3d7e6..3d37122 100644
--- a/what-the-stack/src/main/AndroidManifest.xml
+++ b/what-the-stack/src/main/AndroidManifest.xml
@@ -13,10 +13,12 @@
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/Annotations.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/Annotations.kt
new file mode 100644
index 0000000..a84ab18
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/Annotations.kt
@@ -0,0 +1,17 @@
+package com.haroldadmin.whatthestack
+
+/**
+ * Indicates that the annotated class/function runs in the host application's
+ * process, not in the bound service's process
+ */
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.SOURCE)
+internal annotation class HostAppProcess
+
+/**
+ * Indicates that the annotated class/function runs in the bound service's
+ * process, not in the host app's process
+ */
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.SOURCE)
+internal annotation class ServiceProcess
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt
index d02d8bb..2d564a5 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt
@@ -1,7 +1,7 @@
package com.haroldadmin.whatthestack
import android.os.Parcelable
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
/**
* Represents the data of the exception to be displayed to the user.
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStack.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStack.kt
deleted file mode 100644
index d54f471..0000000
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStack.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.haroldadmin.whatthestack
-
-import android.content.Context
-import android.content.Intent
-import android.os.Messenger
-
-/**
- * **DO NOT USE**
- * A class to allow initialization of WhatTheStack service.
- *
- * WhatTheStack initializes automatically using a content provider. You do not need to initialize
- * it explicitly using this class.
- *
- * @param applicationContext The context used to start the service to catch uncaught exceptions
- */
-@Deprecated(
- "WhatTheStack initializes automatically at application startup. Do not explicitly initialize it",
- level = DeprecationLevel.ERROR
-)
-class WhatTheStack(private val applicationContext: Context) {
-
- @Suppress("unused")
- fun init() {
- InitializationManager.init(applicationContext)
- }
-}
-
-internal object InitializationManager {
-
- private var isInitialized: Boolean = false
-
- private val connection = WhatTheStackConnection(
- onConnected = { binder ->
- val messenger = Messenger(binder)
- val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
- val exceptionHandler = WhatTheStackExceptionHandler(messenger, defaultHandler)
- Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
- }
- )
-
- fun init(applicationContext: Context) {
- if (isInitialized) { return }
- isInitialized = true
- val intent = Intent(applicationContext, WhatTheStackService::class.java)
- applicationContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)
- }
-}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt
index 54c46c6..108afee 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt
@@ -1,18 +1,14 @@
package com.haroldadmin.whatthestack
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
import android.os.Bundle
-import android.text.method.ScrollingMovementMethod
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
-import com.google.android.material.snackbar.Snackbar
-import com.haroldadmin.whatthestack.databinding.ActivityWhatTheStackBinding
-import dev.chrisbanes.insetter.Insetter
-import dev.chrisbanes.insetter.windowInsetTypesOf
+import com.google.accompanist.insets.ProvideWindowInsets
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import com.haroldadmin.whatthestack.ui.pages.ExceptionPage
+import com.haroldadmin.whatthestack.ui.theme.SystemBarsColor
+import com.haroldadmin.whatthestack.ui.theme.WhatTheStackTheme
/**
* An Activity which displays various pieces of information regarding the exception which
@@ -20,90 +16,27 @@ import dev.chrisbanes.insetter.windowInsetTypesOf
*/
class WhatTheStackActivity : AppCompatActivity() {
- private lateinit var binding: ActivityWhatTheStackBinding
-
- private val clipboardManager: ClipboardManager by lazy {
- getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityWhatTheStackBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
WindowCompat.setDecorFitsSystemWindows(window, false)
- Insetter.builder()
- .padding(windowInsetTypesOf(statusBars = true, navigationBars = true))
- .applyToView(binding.root)
-
- val type = intent.getStringExtra(KEY_EXCEPTION_TYPE)
- val cause = intent.getStringExtra(KEY_EXCEPTION_CAUSE)
- val message = intent.getStringExtra(KEY_EXCEPTION_MESSAGE)
- val stackTrace = intent.getStringExtra(KEY_EXCEPTION_STACKTRACE)
-
- binding.stacktrace.apply {
- text = stackTrace
- setHorizontallyScrolling(true)
- movementMethod = ScrollingMovementMethod()
- }
-
- binding.exceptionName.apply {
- text = getString(R.string.exception_name, type)
- }
-
- binding.exceptionCause.apply {
- text = getString(R.string.exception_cause, cause)
- }
-
- binding.exceptionMessage.apply {
- text = getString(R.string.exception_message, message)
- }
-
- binding.copyStacktrace.apply {
- setOnClickListener {
- val clipping = ClipData.newPlainText("stacktrace", stackTrace)
- clipboardManager.setPrimaryClip(clipping)
- snackbar { R.string.copied_message }
- }
- }
-
- binding.shareStacktrace.apply {
- setOnClickListener {
- val sendIntent: Intent = Intent().apply {
- this.action = Intent.ACTION_SEND
- this.putExtra(Intent.EXTRA_TEXT, stackTrace)
- this.type = "text/plain"
+ val type = intent.getStringExtra(KEY_EXCEPTION_TYPE) ?: ""
+ val message = intent.getStringExtra(KEY_EXCEPTION_MESSAGE) ?: ""
+ val stackTrace = intent.getStringExtra(KEY_EXCEPTION_STACKTRACE) ?: ""
+
+ setContent {
+ val sysUiController = rememberSystemUiController()
+ sysUiController.setSystemBarsColor(SystemBarsColor)
+
+ WhatTheStackTheme {
+ ProvideWindowInsets {
+ ExceptionPage(
+ type = type,
+ message = message,
+ stackTrace = stackTrace
+ )
}
-
- val shareIntent = Intent.createChooser(sendIntent, null)
- startActivity(shareIntent)
- }
- }
-
- binding.launchApplication.apply {
- setOnClickListener {
- context.packageManager.getLaunchIntentForPackage(applicationContext.packageName)
- ?.let {
- it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- startActivity(it)
- }
}
}
-
- binding.searchStackoverflow.apply {
- setOnClickListener {
- val searchQuery = "$cause: $message"
- val searchIntent: Intent = Intent().apply {
- this.action = Intent.ACTION_VIEW
- this.data = Uri.parse(generateStackoverflowSearchUrl(searchQuery))
- }
- startActivity(searchIntent)
- }
- }
- }
-
- private inline fun snackbar(messageProvider: () -> Int) {
- Snackbar.make(binding.nestedScrollRoot, messageProvider(), Snackbar.LENGTH_SHORT).show()
}
}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt
deleted file mode 100644
index 16df4d5..0000000
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.haroldadmin.whatthestack
-
-import android.content.ComponentName
-import android.content.ServiceConnection
-import android.os.IBinder
-
-internal class WhatTheStackConnection(
- private val onDisconnected: () -> Unit = { Unit },
- private val onConnected: (IBinder) -> Unit
-) : ServiceConnection {
-
- override fun onServiceDisconnected(name: ComponentName?) {
- onDisconnected()
- }
-
- override fun onServiceConnected(name: ComponentName?, service: IBinder) {
- onConnected(service)
- }
-}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt
index d581270..c3b7353 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt
@@ -2,32 +2,36 @@ package com.haroldadmin.whatthestack
import android.os.Message
import android.os.Messenger
-import android.os.Process
import androidx.core.os.bundleOf
/**
* A [Thread.UncaughtExceptionHandler] which is meant to be used as a default exception handler on
- * the application. Any uncaught exceptions which are handled by this handler are processed and
- * send to [WhatTheStackService] to show the error screen.
+ * the application.
+ *
+ * It runs in the host app's process to:
+ * 1. Process any exception it catches and forward the result in a [Message] to [WhatTheStackService]
+ * 2. Call the default exception handler it replaced, if any
+ * 3. Kill the app process if there was no previous default exception handler
*/
-
+@HostAppProcess
internal class WhatTheStackExceptionHandler(
- private val service: Messenger,
- private val defaultHandler: Thread.UncaughtExceptionHandler?
+ private val serviceMessenger: Messenger,
+ private val defaultHandler: Thread.UncaughtExceptionHandler?,
) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
-
+ e.printStackTrace()
val exceptionData = e.process()
+ serviceMessenger.send(
+ Message().apply {
+ data = bundleOf(
+ KEY_EXCEPTION_TYPE to exceptionData.type,
+ KEY_EXCEPTION_CAUSE to exceptionData.cause,
+ KEY_EXCEPTION_MESSAGE to exceptionData.message,
+ KEY_EXCEPTION_STACKTRACE to exceptionData.stacktrace
+ )
+ }
+ )
- service.send(Message().apply {
- data = bundleOf(
- KEY_EXCEPTION_TYPE to exceptionData.type,
- KEY_EXCEPTION_CAUSE to exceptionData.cause,
- KEY_EXCEPTION_MESSAGE to exceptionData.message,
- KEY_EXCEPTION_STACKTRACE to exceptionData.stacktrace
- )
- })
-
- defaultHandler?.uncaughtException(t, e) ?: Process.killProcess(Process.myPid())
+ defaultHandler?.uncaughtException(t, e)
}
}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackInitializer.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackInitializer.kt
index bf4475e..3a51c69 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackInitializer.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackInitializer.kt
@@ -1,30 +1,57 @@
package com.haroldadmin.whatthestack
+import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.Messenger
import androidx.startup.Initializer
import java.lang.Class
/**
* WhatTheStackInitializer is an [androidx.startup.Initializer] for WhatTheStack
- *
- * This particular initializer does not need to return anything, but it is required to return
- * a sensible value here so we return an instance of a dummy class [WhatTheStackInitializedToken]
- * instead.
*/
-class WhatTheStackInitializer : Initializer {
+@HostAppProcess
+class WhatTheStackInitializer : Initializer {
- override fun create(context: Context): WhatTheStackInitializedToken {
- InitializationManager.init(context)
- return WhatTheStackInitializedToken()
- }
+ /**
+ * Runs in the host app's process to:
+ *
+ * 1. Start [WhatTheStackService] as a bound service to allow communication between the
+ * app's process and the service's process
+ * 2. Replace the app's default [Thread.UncaughtExceptionHandler] with [WhatTheStackExceptionHandler]
+ * when the service is connected.
+ *
+ * This method does not need to return anything, but it is required to return
+ * a sensible value here so we return a dummy object [InitializedToken] instead.
+ */
+ override fun create(context: Context): InitializedToken {
+ val connection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder) {
+ val messenger = Messenger(service)
+ val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+ val customExceptionHandler = WhatTheStackExceptionHandler(
+ messenger,
+ defaultExceptionHandler
+ )
+ Thread.setDefaultUncaughtExceptionHandler(customExceptionHandler)
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) = Unit
+ }
- override fun dependencies(): List>> {
- return emptyList()
+ val intent = Intent(context, WhatTheStackService::class.java)
+ context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
+
+ return InitializedToken
}
-}
-/**
- * A dummy class that does nothing but represent a type that can be returned by
- * [WhatTheStackInitializer]
- */
-class WhatTheStackInitializedToken
+ override fun dependencies(): List>> = emptyList()
+
+ /**
+ * A dummy object that does nothing but represent a type that can be returned by
+ * [WhatTheStackInitializer]
+ */
+ object InitializedToken
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt
index 19a8cd5..fcae7e4 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt
@@ -3,49 +3,64 @@ package com.haroldadmin.whatthestack
import android.app.Service
import android.content.Context
import android.content.Intent
-import android.os.Handler
-import android.os.IBinder
-import android.os.Message
-import android.os.Messenger
+import android.os.*
/**
- * A Bound Service which runs in a separate process than the application.
+ * A Bound Service which runs in a separate process than the host application.
*
- * Messages are sent to this service when an uncaught exception is thrown in the consuming
- * application. This exception is caught using a default exception handler set by this library,
- * which processes the exception and sends it as a message to this service.
+ * This service must be started with [Context.bindService]. A bound service lives only as long as
+ * the calling context, so `bindService` must be called on an **APPLICATION CONTEXT**.
*
- * This service then starts an activity with the processed exception data as an intent extra.
+ * [WhatTheStackInitializer] starts this service, and it dies when the host app terminates.
+ * Therefore we don't need to explicitly handle [Service.onCreate], [Service.onDestroy] or call
+ * [Service.stopSelf].
+ *
+ * [WhatTheStackExceptionHandler] sends messages to this service whenever an uncaught exception
+ * is thrown in the host application. This service then starts an activity with the processed
+ * exception data as an intent extra.
*/
-
+@ServiceProcess
class WhatTheStackService : Service() {
-
+ /**
+ * [Handler] that runs on the main thread to handle incoming processed uncaught
+ * exceptions from [WhatTheStackExceptionHandler]
+ *
+ * We need to lazily initialize it because [getApplicationContext] returns null right
+ * after the service is created.
+ */
private val handler by lazy { WhatTheStackHandler(applicationContext) }
+ /**
+ * Runs when [WhatTheStackInitializer] calls [Context.bindService] to create a connection
+ * to this service.
+ *
+ * It creates a [Messenger] that can be used to communicate with its [handler],
+ * and returns its [IBinder].
+ */
override fun onBind(intent: Intent?): IBinder? {
- return Messenger(handler).binder
+ val messenger = Messenger(handler)
+ return messenger.binder
}
+}
+
+/**
+ * A [Handler] that runs on the main thread of the service process to process
+ * incoming uncaught exception messages.
+ */
+@ServiceProcess
+private class WhatTheStackHandler(
+ private val applicationContext: Context
+) : Handler(Looper.getMainLooper()) {
- internal class WhatTheStackHandler(
- private val applicationContext: Context
- ) : Handler() {
- override fun handleMessage(msg: Message) {
- val type = msg.data.getString(KEY_EXCEPTION_TYPE)
- val cause = msg.data.getString(KEY_EXCEPTION_CAUSE)
- val message = msg.data.getString(KEY_EXCEPTION_MESSAGE)
- val stacktrace = msg.data.getString(KEY_EXCEPTION_STACKTRACE)
- Intent()
- .apply {
- setClass(applicationContext, WhatTheStackActivity::class.java)
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- putExtra(KEY_EXCEPTION_TYPE, type)
- putExtra(KEY_EXCEPTION_CAUSE, cause)
- putExtra(KEY_EXCEPTION_MESSAGE, message)
- putExtra(KEY_EXCEPTION_STACKTRACE, stacktrace)
- }
- .also { intent ->
- applicationContext.startActivity(intent)
- }
- }
+ override fun handleMessage(msg: Message) {
+ Intent()
+ .apply {
+ setClass(applicationContext, WhatTheStackActivity::class.java)
+ putExtras(msg.data)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ .also { intent ->
+ applicationContext.startActivity(intent)
+ }
}
}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OutlinedIconButton.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OutlinedIconButton.kt
new file mode 100644
index 0000000..6d47bcc
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OutlinedIconButton.kt
@@ -0,0 +1,38 @@
+package com.haroldadmin.whatthestack.ui.components
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+
+@Composable
+internal fun OutlinedIconButton(
+ text: String,
+ @DrawableRes iconId: Int,
+ onClick: () -> Unit,
+ contentDescription: String,
+ modifier: Modifier = Modifier,
+) {
+ OutlinedButton(
+ onClick = onClick,
+ modifier = modifier.fillMaxWidth(),
+ colors = ButtonDefaults.outlinedButtonColors(
+ backgroundColor = MaterialTheme.colors.background,
+ contentColor = MaterialTheme.colors.onBackground,
+ disabledContentColor = MaterialTheme.colors.onBackground.copy(alpha = 0.5f)
+ ),
+ ) {
+ Icon(
+ painter = painterResource(id = iconId),
+ contentDescription = contentDescription
+ )
+ Text(
+ text = text,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ )
+ }
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OverlineLabel.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OverlineLabel.kt
new file mode 100644
index 0000000..bbd9524
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OverlineLabel.kt
@@ -0,0 +1,21 @@
+package com.haroldadmin.whatthestack.ui.components
+
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+
+/**
+ * A text label with the "overline" typography style
+ */
+@Composable
+internal fun OverlineLabel(label: String, modifier: Modifier = Modifier) {
+ Text(
+ text = label,
+ style = MaterialTheme.typography.overline,
+ fontWeight = FontWeight.Medium,
+ color = MaterialTheme.colors.onSurface,
+ modifier = modifier
+ )
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/pages/ExceptionPage.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/pages/ExceptionPage.kt
new file mode 100644
index 0000000..931c5b9
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/pages/ExceptionPage.kt
@@ -0,0 +1,225 @@
+package com.haroldadmin.whatthestack.ui.pages
+
+import android.content.Intent
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.net.Uri
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.accompanist.insets.navigationBarsHeight
+import com.google.accompanist.insets.statusBarsHeight
+import com.haroldadmin.whatthestack.R
+import com.haroldadmin.whatthestack.generateStackoverflowSearchUrl
+import com.haroldadmin.whatthestack.ui.components.OutlinedIconButton
+import com.haroldadmin.whatthestack.ui.components.OverlineLabel
+import com.haroldadmin.whatthestack.ui.preview.SampleData
+import com.haroldadmin.whatthestack.ui.theme.WhatTheStackTheme
+import kotlinx.coroutines.launch
+
+@Composable
+fun ExceptionPage(
+ type: String,
+ message: String,
+ stackTrace: String
+) {
+ val clipboard = LocalClipboardManager.current
+ val context = LocalContext.current
+ val scaffoldState = rememberScaffoldState()
+ val coroutineScope = rememberCoroutineScope()
+
+ val snackbarMessage = stringResource(id = R.string.copied_message)
+
+ Scaffold(scaffoldState = scaffoldState) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Spacer(modifier = Modifier.statusBarsHeight(additional = 8.dp))
+ PageHeader()
+ ExceptionDetails(
+ type = type,
+ message = message,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ ExceptionOptions(
+ onCopy = {
+ coroutineScope.launch {
+ clipboard.setText(AnnotatedString(stackTrace))
+ scaffoldState.snackbarHostState.showSnackbar(snackbarMessage)
+ }
+ },
+ onShare = {
+ val sendIntent: Intent = Intent().apply {
+ this.action = Intent.ACTION_SEND
+ this.putExtra(Intent.EXTRA_TEXT, stackTrace)
+ this.type = "text/plain"
+ }
+
+ val shareIntent = Intent.createChooser(sendIntent, "Stacktrace")
+ context.startActivity(shareIntent)
+ },
+ onSearch = {
+ val searchQuery = "$type: $message"
+ val url = generateStackoverflowSearchUrl(searchQuery)
+ val searchIntent = Intent().apply {
+ action = Intent.ACTION_VIEW
+ data = Uri.parse(url)
+ }
+ context.startActivity(searchIntent)
+ },
+ onRestart = {
+ val applicationContext = context.applicationContext
+ val packageManager = applicationContext.packageManager
+ val packageName = applicationContext.packageName
+
+ val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
+ if (launchIntent != null) {
+ launchIntent.flags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ context.startActivity(launchIntent)
+ }
+ }
+ )
+ Stacktrace(
+ stackTrace = stackTrace,
+ modifier = Modifier.padding(top = 8.dp)
+ )
+ Spacer(modifier = Modifier.navigationBarsHeight(additional = 8.dp))
+ }
+ }
+}
+
+@Composable
+fun PageHeader() {
+ Text(
+ stringResource(id = R.string.header_text),
+ style = MaterialTheme.typography.h4,
+ modifier = Modifier.padding(vertical = 4.dp),
+ color = MaterialTheme.colors.onBackground
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = stringResource(id = R.string.explanation_text),
+ color = MaterialTheme.colors.onBackground
+ )
+}
+
+@Composable
+fun ExceptionDetails(type: String, message: String, modifier: Modifier) {
+ Column(modifier = modifier) {
+ OverlineLabel(label = stringResource(id = R.string.exception_name))
+ Text(
+ text = type,
+ fontFamily = FontFamily.Monospace,
+ color = MaterialTheme.colors.onBackground
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ OverlineLabel(label = stringResource(id = R.string.exception_message))
+ Text(
+ text = message,
+ fontFamily = FontFamily.Monospace,
+ color = MaterialTheme.colors.onBackground
+ )
+ }
+}
+
+@Composable
+fun ExceptionOptions(
+ onCopy: () -> Unit,
+ onShare: () -> Unit,
+ onRestart: () -> Unit,
+ onSearch: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(modifier = modifier) {
+ OutlinedIconButton(
+ text = stringResource(id = R.string.copy_stacktrace),
+ iconId = R.drawable.ic_outline_content_copy_24,
+ onClick = onCopy,
+ contentDescription = "Copy",
+ modifier = Modifier.padding(vertical = 4.dp),
+ )
+ OutlinedIconButton(
+ text = stringResource(id = R.string.share_stacktrace),
+ iconId = R.drawable.ic_outline_share_24,
+ onClick = onShare,
+ contentDescription = "Share",
+ modifier = Modifier.padding(vertical = 4.dp)
+ )
+ OutlinedIconButton(
+ text = stringResource(id = R.string.search_stackoverflow),
+ iconId = R.drawable.ic_round_search_24,
+ onClick = onSearch,
+ contentDescription = "Search Stackoverflow",
+ modifier = Modifier.padding(vertical = 4.dp)
+ )
+ OutlinedIconButton(
+ text = stringResource(id = R.string.restart_application),
+ iconId = R.drawable.ic_baseline_refresh_24,
+ onClick = onRestart,
+ contentDescription = "Restart"
+ )
+ }
+}
+
+@Composable
+fun Stacktrace(stackTrace: String, modifier: Modifier) {
+ Column(modifier) {
+ OverlineLabel(label = stringResource(id = R.string.stacktrace))
+ Surface(modifier = Modifier.padding(top = 4.dp)) {
+ SelectionContainer {
+ Text(
+ text = stackTrace,
+ style = MaterialTheme.typography.body2.copy(fontSize = 12.sp),
+ fontFamily = FontFamily.Monospace,
+ color = MaterialTheme.colors.primary,
+ modifier = Modifier
+ .padding(4.dp)
+ .horizontalScroll(rememberScrollState())
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun ExceptionPagePreview() {
+ WhatTheStackTheme {
+ ExceptionPage(
+ type = SampleData.ExceptionType,
+ message = SampleData.ExceptionMessage,
+ stackTrace = SampleData.Stacktrace
+ )
+ }
+}
+
+@Preview(uiMode = UI_MODE_NIGHT_YES)
+@Composable
+fun ExceptionPagePreviewNightMode() {
+ WhatTheStackTheme {
+ ExceptionPage(
+ type = SampleData.ExceptionType,
+ message = SampleData.ExceptionMessage,
+ stackTrace = SampleData.Stacktrace
+ )
+ }
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/preview/SampleData.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/preview/SampleData.kt
new file mode 100644
index 0000000..a16438f
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/preview/SampleData.kt
@@ -0,0 +1,32 @@
+package com.haroldadmin.whatthestack.ui.preview
+
+object SampleData {
+ const val ExceptionType = "Runtime Exception"
+
+ const val ExceptionMessage = "This exception was thrown purely because it can be thrown"
+
+ const val Stacktrace =
+ """java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
+ at com.android.internal.os.RuntimeInitMethodAndArgsCaller.run(RuntimeInit.java:558)
+ at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
+Caused by: java.lang.reflect.InvocationTargetException
+ at java.lang.reflect.Method.invoke(Native Method)
+ at com.android.internal.os.RuntimeInitMethodAndArgsCaller.run(RuntimeInit.java:548)
+ ... 1 more
+Caused by: com.haroldadmin.crashyapp.BecauseICanException: This exception is thrown purely because it can be thrown
+ at com.haroldadmin.crashyapp.MainActivity.onCreatelambda-0(MainActivity.kt:15)
+ at com.haroldadmin.crashyapp.MainActivity.r8lambdapFZVHP1EeT4E2LW7TLA5yGBRTTk(Unknown Source:0)
+ at com.haroldadmin.crashyapp.MainActivityxternalSyntheticLambda0.onClick(Unknown Source:0)
+ at android.view.View.performClick(View.java:7441)
+ at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
+ at android.view.View.performClickInternal(View.java:7418)
+ at android.view.View.access$3700(View.java:835)
+ at android.view.ViewPerformClick.run(View.java:28676)
+ at android.os.Handler.handleCallback(Handler.java:938)
+ at android.os.Handler.dispatchMessage(Handler.java:99)
+ at android.os.Looper.loopOnce(Looper.java:201)
+ at android.os.Looper.loop(Looper.java:288)
+ at android.app.ActivityThread.main(ActivityThread.java:7839)
+ ... 3 more
+"""
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/theme/WhatTheStackTheme.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/theme/WhatTheStackTheme.kt
new file mode 100644
index 0000000..120e11f
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/theme/WhatTheStackTheme.kt
@@ -0,0 +1,38 @@
+package com.haroldadmin.whatthestack.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+private val DarkColorPalette = darkColors(
+ primary = Color(0xffd32f2f),
+ primaryVariant = Color(0xffff6659),
+ secondary = Color(0xff616161),
+ secondaryVariant = Color(0xff373737),
+)
+
+private val LightColorPalette = lightColors(
+ primary = Color(0xffd32f2f),
+ primaryVariant = Color(0xff9a0007),
+ secondary = Color(0xff616161),
+ secondaryVariant = Color(0x33373737),
+)
+
+internal val SystemBarsColor = Color(0x33373737)
+
+@Composable
+fun WhatTheStackTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(colors = colors, content = content)
+}
diff --git a/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml b/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml
index f2be45b..5ab492c 100644
--- a/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml b/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml
index 79c5a76..eb384e5 100644
--- a/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorOnPrimary">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml b/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml
index 3a6a059..394e3ef 100644
--- a/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/drawable/ic_round_search_24.xml b/what-the-stack/src/main/res/drawable/ic_round_search_24.xml
index c1818d5..b86e6fc 100644
--- a/what-the-stack/src/main/res/drawable/ic_round_search_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_round_search_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/layout/activity_what_the_stack.xml b/what-the-stack/src/main/res/layout/activity_what_the_stack.xml
deleted file mode 100644
index 7e85fa7..0000000
--- a/what-the-stack/src/main/res/layout/activity_what_the_stack.xml
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/what-the-stack/src/main/res/values/colors.xml b/what-the-stack/src/main/res/values/colors.xml
deleted file mode 100644
index 9f135e1..0000000
--- a/what-the-stack/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- #d32f2f
- #ff6659
- #9a0007
- #616161
- #8e8e8e
- #373737
- #33373737
- #ffffff
- #ffffff
-
\ No newline at end of file
diff --git a/what-the-stack/src/main/res/values/strings.xml b/what-the-stack/src/main/res/values/strings.xml
index 2fecf52..54394d8 100644
--- a/what-the-stack/src/main/res/values/strings.xml
+++ b/what-the-stack/src/main/res/values/strings.xml
@@ -26,14 +26,13 @@
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
- Exception: %1$s
- Cause: %1$s
+ Exception
Stacktrace
- Message: %1$s
- Copy
- Share
- Restart
+ Message
+ Copy Stacktrace
+ Share Stacktrace
+ Restart Application
Relaunch App
Stacktrace copied!
- Stackoverflow
+ Search Stackoverflow
\ No newline at end of file
diff --git a/what-the-stack/src/main/res/values/styles.xml b/what-the-stack/src/main/res/values/styles.xml
index 98b8f55..0870988 100644
--- a/what-the-stack/src/main/res/values/styles.xml
+++ b/what-the-stack/src/main/res/values/styles.xml
@@ -1,14 +1,4 @@
-
+
\ No newline at end of file
diff --git a/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt b/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt
index a9a116c..17364ce 100644
--- a/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt
+++ b/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt
@@ -1,7 +1,7 @@
package com.haroldadmin.whatthestack
-import java.io.PrintStream
import org.junit.Test
+import java.io.PrintStream
internal class ExceptionProcessingTest {
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/StringOutputStream.kt b/what-the-stack/src/test/java/com/haroldadmin/whatthestack/StringOutputStream.kt
similarity index 100%
rename from what-the-stack/src/main/java/com/haroldadmin/whatthestack/StringOutputStream.kt
rename to what-the-stack/src/test/java/com/haroldadmin/whatthestack/StringOutputStream.kt