Enable and disable CrashHandler

Going to "Preferences" the user will be able to enable and disable the
CrashHanlder of Husky.
This commit is contained in:
Adolfo Santiago 2022-06-25 10:00:18 +02:00
parent c4a4b26320
commit 5ff66f5ac9
No known key found for this signature in database
GPG key ID: 244D6F9A317B4A65
8 changed files with 122 additions and 66 deletions

View file

@ -49,9 +49,9 @@
<string name="pref_title_composing">Composing</string> <string name="pref_title_composing">Composing</string>
<string name="pref_title_composing_title">Composing using zero-width space characters in emojis</string> <string name="pref_title_composing_title">Composing using zero-width space characters in emojis</string>
<string name="pref_acra_category">ACRA Settings</string> <string name="pref_crashhandler_category">CrashHanlder Settings</string>
<string name="pref_acra_body">Enable ACRA reporting</string> <string name="pref_crashhandler_body">Enable CrashHanlder email reporting</string>
<string name="key_enable_acra">acra.enable</string> <string name="pref_crashhandler_key">enableCrashHanlder</string>
<string name="pref_title_anonymize_upload_filenames">Anonymize uploaded file names</string> <string name="pref_title_anonymize_upload_filenames">Anonymize uploaded file names</string>
<string name="pref_title_hide_live_notification_description">Hide Live Notification username</string> <string name="pref_title_hide_live_notification_description">Hide Live Notification username</string>

View file

@ -113,7 +113,7 @@ class LoginActivity : BaseActivity(), Injectable {
} else { } else {
binding.toolbar.visibility = View.GONE binding.toolbar.visibility = View.GONE
} }
val a = 1/0
} }
override fun requiresLogin(): Boolean { override fun requiresLogin(): Boolean {

View file

@ -56,12 +56,19 @@ class TuskyApplication : Application(), HasAndroidInjector {
@Inject @Inject
lateinit var notificationWorkerFactory: NotificationWorkerFactory lateinit var notificationWorkerFactory: NotificationWorkerFactory
@Inject
protected lateinit var crashHandler: CrashHandler
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
AppInjector.init(this)
val preferences = PreferenceManager.getDefaultSharedPreferences(this) val preferences = PreferenceManager.getDefaultSharedPreferences(this)
CrashHandler.setAsDefaultHandler(this) if(preferences.getBoolean(PrefKeys.CRASH_HANDLER_ENABLE, false)) {
crashHandler.setAsDefaultHandler()
}
if(ApplicationUtils.isDebug()) { if(ApplicationUtils.isDebug()) {
Timber.plant(HyperlinkDebugTree()) Timber.plant(HyperlinkDebugTree())
@ -71,9 +78,6 @@ class TuskyApplication : Application(), HasAndroidInjector {
AutoDisposePlugins.setHideProxies(false) // a small performance optimization AutoDisposePlugins.setHideProxies(false) // a small performance optimization
AppInjector.init(this)
// init the custom emoji fonts // init the custom emoji fonts
val emojiSelection = preferences.getInt(PrefKeys.EMOJI, 0) val emojiSelection = preferences.getInt(PrefKeys.EMOJI, 0)
val emojiConfig = EmojiCompatFont.byId(emojiSelection) val emojiConfig = EmojiCompatFont.byId(emojiSelection)

View file

@ -27,11 +27,10 @@ import androidx.preference.PreferenceFragmentCompat
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.core.logging.CrashHandler
import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.service.StreamingService
import com.keylesspalace.tusky.settings.AppTheme import com.keylesspalace.tusky.settings.AppTheme
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.emojiPreference import com.keylesspalace.tusky.settings.emojiPreference
@ -62,6 +61,9 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
@Inject @Inject
lateinit var eventHub: EventHub lateinit var eventHub: EventHub
@Inject
lateinit var crashHandler: CrashHandler
private val iconSize by lazy { resources.getDimensionPixelSize(R.dimen.preference_icon_size) } private val iconSize by lazy { resources.getDimensionPixelSize(R.dimen.preference_icon_size) }
private var httpProxyPref: Preference? = null private var httpProxyPref: Preference? = null
@ -362,15 +364,25 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
} }
} }
/* preferenceCategory(R.string.pref_crashhandler_category) {
preferenceCategory(R.string.pref_acra_category) {
switchPreference { switchPreference {
setDefaultValue(false) setDefaultValue(false)
key = PREF_ENABLE_ACRA key = PrefKeys.CRASH_HANDLER_ENABLE
setTitle(R.string.pref_acra_body) setTitle(R.string.pref_crashhandler_body)
isSingleLineTitle = false isSingleLineTitle = false
setOnPreferenceChangeListener { _, value ->
with(value as Boolean) {
if(this) {
crashHandler.setAsDefaultHandler()
} else {
crashHandler.removeDefaultHandler()
}
}
true
}
}
} }
}*/
} }
} }

View file

@ -32,27 +32,15 @@ import com.keylesspalace.tusky.core.ui.callbacks.ActivityCallback
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.lang.Thread.UncaughtExceptionHandler
import javax.inject.Inject
import timber.log.Timber import timber.log.Timber
class CrashHandler( class CrashHandler @Inject constructor(
private val defaultHandler: Thread.UncaughtExceptionHandler,
private val huskyApp: Application private val huskyApp: Application
) : Thread.UncaughtExceptionHandler { ) : UncaughtExceptionHandler {
private var lastActivity: Activity? = null private val activityCallbacks = object : ActivityCallback() {
companion object {
fun setAsDefaultHandler(application: Application) {
val handler = Thread.getDefaultUncaughtExceptionHandler()?.let {
CrashHandler(it, application)
}
Thread.setDefaultUncaughtExceptionHandler(handler)
}
}
init {
huskyApp.registerActivityLifecycleCallbacks(
object : ActivityCallback() {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
lastActivity = activity lastActivity = activity
Timber.d("onActivityCreated[${activity::class.simpleName}]") Timber.d("onActivityCreated[${activity::class.simpleName}]")
@ -68,8 +56,9 @@ class CrashHandler(
Timber.d("onActivityStopped[${activity::class.simpleName}]") Timber.d("onActivityStopped[${activity::class.simpleName}]")
} }
} }
)
} private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
private var lastActivity: Activity? = null
override fun uncaughtException(thread: Thread, throwable: Throwable) { override fun uncaughtException(thread: Thread, throwable: Throwable) {
try { try {
@ -77,7 +66,7 @@ class CrashHandler(
} catch(e: IOException) { } catch(e: IOException) {
Timber.e("CrashHandler Exception[${e.message}]") Timber.e("CrashHandler Exception[${e.message}]")
} finally { } finally {
lastActivity?.finish() lastActivity?.finish() ?: defaultHandler?.uncaughtException(thread, throwable)
} }
} }
@ -111,7 +100,10 @@ class CrashHandler(
val intent = Intent(Intent.ACTION_SEND).apply { val intent = Intent(Intent.ACTION_SEND).apply {
type = "message/rfc822" type = "message/rfc822"
putExtra(Intent.EXTRA_EMAIL, arrayOf(activity.getString(R.string.crashhandler_email))) putExtra(
Intent.EXTRA_EMAIL,
arrayOf(activity.getString(R.string.crashhandler_email))
)
putExtra(Intent.EXTRA_SUBJECT, "Husky ${BuildConfig.VERSION_NAME} crash") putExtra(Intent.EXTRA_SUBJECT, "Husky ${BuildConfig.VERSION_NAME} crash")
putExtra(Intent.EXTRA_TEXT, formattedLog) putExtra(Intent.EXTRA_TEXT, formattedLog)
putExtra(Intent.EXTRA_STREAM, getCrashFileUri(activity, stacktrace)) putExtra(Intent.EXTRA_STREAM, getCrashFileUri(activity, stacktrace))
@ -129,7 +121,10 @@ class CrashHandler(
} }
private fun getCrashFileUri(activity: Activity, stacktrace: String): Uri { private fun getCrashFileUri(activity: Activity, stacktrace: String): Uri {
val file = File("${huskyApp.cacheDir}/crashes", activity.getString(R.string.crashhandler_email_report_filename)) val file = File(
"${huskyApp.cacheDir}/crashes",
activity.getString(R.string.crashhandler_email_report_filename)
)
FileOutputStream(file).apply { FileOutputStream(file).apply {
write(stacktrace.toByteArray()) write(stacktrace.toByteArray())
}.also { }.also {
@ -141,4 +136,21 @@ class CrashHandler(
file file
) )
} }
fun setAsDefaultHandler() {
val handler = defaultHandler?.let {
this@CrashHandler
}
Thread.setDefaultUncaughtExceptionHandler(handler)
huskyApp.registerActivityLifecycleCallbacks(activityCallbacks)
Timber.d("Set default handler[${handler}]")
}
fun removeDefaultHandler() {
Thread.setDefaultUncaughtExceptionHandler(defaultHandler)
huskyApp.unregisterActivityLifecycleCallbacks(activityCallbacks)
Timber.d("Remove default handler")
}
} }

View file

@ -27,6 +27,7 @@ import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.EventHubImpl import com.keylesspalace.tusky.appstore.EventHubImpl
import com.keylesspalace.tusky.components.notifications.Notifier import com.keylesspalace.tusky.components.notifications.Notifier
import com.keylesspalace.tusky.components.notifications.SystemNotifier import com.keylesspalace.tusky.components.notifications.SystemNotifier
import com.keylesspalace.tusky.core.logging.CrashHandler
import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.network.TimelineCases
@ -59,8 +60,10 @@ class AppModule {
} }
@Provides @Provides
fun providesTimelineUseCases(api: MastodonApi, fun providesTimelineUseCases(
eventHub: EventHub): TimelineCases { api: MastodonApi,
eventHub: EventHub
): TimelineCases {
return TimelineCasesImpl(api, eventHub) return TimelineCasesImpl(api, eventHub)
} }
@ -73,19 +76,42 @@ class AppModule {
fun providesDatabase(appContext: Context): AppDatabase { fun providesDatabase(appContext: Context): AppDatabase {
return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB") return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB")
.allowMainThreadQueries() .allowMainThreadQueries()
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, .addMigrations(
AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8, AppDatabase.MIGRATION_2_3,
AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, AppDatabase.MIGRATION_3_4,
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, AppDatabase.MIGRATION_4_5,
AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, AppDatabase.MIGRATION_5_6,
AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, AppDatabase.MIGRATION_6_7,
AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, AppDatabase.MIGRATION_7_8,
AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24, AppDatabase.MIGRATION_24_25, AppDatabase.MIGRATION_8_9,
AppDatabase.MIGRATION_25_26, AppDatabase.MIGRATION_26_27) AppDatabase.MIGRATION_9_10,
AppDatabase.MIGRATION_10_11,
AppDatabase.MIGRATION_11_12,
AppDatabase.MIGRATION_12_13,
AppDatabase.MIGRATION_10_13,
AppDatabase.MIGRATION_13_14,
AppDatabase.MIGRATION_14_15,
AppDatabase.MIGRATION_15_16,
AppDatabase.MIGRATION_16_17,
AppDatabase.MIGRATION_17_18,
AppDatabase.MIGRATION_18_19,
AppDatabase.MIGRATION_19_20,
AppDatabase.MIGRATION_20_21,
AppDatabase.MIGRATION_21_22,
AppDatabase.MIGRATION_22_23,
AppDatabase.MIGRATION_23_24,
AppDatabase.MIGRATION_24_25,
AppDatabase.MIGRATION_25_26,
AppDatabase.MIGRATION_26_27
)
.build() .build()
} }
@Provides @Provides
@Singleton @Singleton
fun notifier(context: Context): Notifier = SystemNotifier(context) fun notifier(context: Context): Notifier = SystemNotifier(context)
@Provides
@Singleton
fun crashHandler(app: Application) = CrashHandler(app)
} }

View file

@ -93,4 +93,6 @@ object PrefKeys {
const val TAB_FILTER_HOME_REPLIES = "tabFilterHomeReplies" const val TAB_FILTER_HOME_REPLIES = "tabFilterHomeReplies"
const val TAB_FILTER_HOME_BOOSTS = "tabFilterHomeBoosts" const val TAB_FILTER_HOME_BOOSTS = "tabFilterHomeBoosts"
const val CRASH_HANDLER_ENABLE = "enableCrashHanlder"
} }

View file

@ -10,12 +10,12 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_acra_category"> <PreferenceCategory android:title="@string/pref_crashhandler_category">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="@string/key_enable_acra" android:key="@string/pref_crashhandler_key"
android:title="@string/pref_acra_body" /> android:title="@string/pref_crashhandler_body" />
</PreferenceCategory> </PreferenceCategory>