1515 */
1616package com.ichi2.anki
1717
18+ import android.app.Activity
1819import android.app.Application
1920import android.content.Context
2021import android.content.SharedPreferences
@@ -26,6 +27,12 @@ import androidx.webkit.WebViewCompat
2627import com.ichi2.anki.analytics.AnkiDroidCrashReportDialog
2728import com.ichi2.anki.analytics.UsageAnalytics
2829import com.ichi2.anki.analytics.UsageAnalytics.sendAnalyticsException
30+ import com.ichi2.anki.common.crashreporting.CrashReportService
31+ import com.ichi2.anki.common.crashreporting.CrashReporter
32+ import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_ALWAYS
33+ import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_ASK
34+ import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_KEY
35+ import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_NEVER
2936import com.ichi2.anki.common.time.TimeManager
3037import com.ichi2.anki.exception.ManuallyReportedException
3138import com.ichi2.anki.exception.UserSubmittedException
@@ -43,13 +50,7 @@ import org.acra.config.ToastConfigurationBuilder
4350import org.acra.sender.HttpSender
4451import timber.log.Timber
4552
46- object CrashReportService {
47- // ACRA constants used for stored preferences
48- const val FEEDBACK_REPORT_KEY = " reportErrorMode"
49- const val FEEDBACK_REPORT_ASK = " 2"
50- const val FEEDBACK_REPORT_NEVER = " 1"
51- const val FEEDBACK_REPORT_ALWAYS = " 0"
52-
53+ private object AcraCrashReporter : CrashReporter {
5354 /* * Our ACRA configurations, initialized during Application.onCreate() */
5455 @JvmStatic
5556 private var logcatArgs =
@@ -166,15 +167,15 @@ object CrashReportService {
166167 */
167168 @JvmStatic
168169 fun initialize (application : Application ) {
169- CrashReportService .application = application
170+ this .application = application
170171 // FIXME ACRA needs to reinitialize after language is changed, but with the new language
171172 // this is difficult because the Application (AnkiDroidApp) does not change it's baseContext
172173 // perhaps a solution could be to change AnkiDroidApp to have a context wrapper that it sets
173174 // as baseContext, and that wrapper allows a resources/configuration update, then
174175 // in GeneralSettingsFragment for the language dialog change listener, the context wrapper
175176 // could be updated directly with the new locale code so that calling getString on would fetch
176177 // the new language string ?
177- toastText = ToastType .AUTO_TOAST .getToastMessage(CrashReportService . application)
178+ toastText = ToastType .AUTO_TOAST .getToastMessage(application)
178179
179180 // Setup logging and crash reporting
180181 if (BuildConfig .DEBUG ) {
@@ -195,6 +196,8 @@ object CrashReportService {
195196 * Set the reporting mode for ACRA based on the value of the FEEDBACK_REPORT_KEY preference
196197 * @param value value of FEEDBACK_REPORT_KEY preference
197198 */
199+ override fun setReportingMode (value : String ) = setAcraReportingMode(value)
200+
198201 fun setAcraReportingMode (value : String ) {
199202 application.sharedPrefs().edit {
200203 // Set the ACRA disable value
@@ -262,17 +265,32 @@ object CrashReportService {
262265 }
263266
264267 /* * Used when we don't have an exception to throw, but we know something is wrong and want to diagnose it */
265- fun sendExceptionReport (
268+ override fun sendExceptionReport (
266269 message : String? ,
267270 origin : String? ,
268271 ) = sendExceptionReport(ManuallyReportedException (message), origin)
269272
270- fun sendExceptionReport (
273+ override fun sendExceptionReport (
271274 e : Throwable ,
272275 origin : String? ,
273- additionalInfo : String? = null,
274- onlyIfSilent : Boolean = false,
275- context : Context = application.applicationContext,
276+ additionalInfo : String? ,
277+ onlyIfSilent : Boolean ,
278+ ) = sendExceptionReportInternal(e, origin, additionalInfo, onlyIfSilent, application.applicationContext)
279+
280+ override fun sendExceptionReport (
281+ e : Throwable ,
282+ origin : String? ,
283+ additionalInfo : String? ,
284+ onlyIfSilent : Boolean ,
285+ context : Context ,
286+ ) = sendExceptionReportInternal(e, origin, additionalInfo, onlyIfSilent, context)
287+
288+ private fun sendExceptionReportInternal (
289+ e : Throwable ,
290+ origin : String? ,
291+ additionalInfo : String? ,
292+ onlyIfSilent : Boolean ,
293+ context : Context ,
276294 ) {
277295 sendAnalyticsException(e, false )
278296 AnkiDroidApp .sentExceptionReportHack = true
@@ -297,6 +315,11 @@ object CrashReportService {
297315
298316 fun isProperServiceProcess (): Boolean = ACRA .isACRASenderServiceProcess()
299317
318+ override fun isEnabled (
319+ context : Context ,
320+ defaultValue : Boolean ,
321+ ): Boolean = isAcraEnabled(context, defaultValue)
322+
300323 fun isAcraEnabled (
301324 context : Context ,
302325 defaultValue : Boolean ,
@@ -314,6 +337,8 @@ object CrashReportService {
314337 *
315338 * @param context the context leading to the directory with ACRA limiter data
316339 */
340+ override fun deleteLimiterData (context : Context ) = deleteACRALimiterData(context)
341+
317342 fun deleteACRALimiterData (context : Context ) {
318343 try {
319344 LimiterData ().store(context)
@@ -322,7 +347,7 @@ object CrashReportService {
322347 }
323348 }
324349
325- fun onPreferenceChanged (
350+ override fun onPreferenceChanged (
326351 ctx : Context ,
327352 newValue : String ,
328353 ) {
@@ -338,24 +363,24 @@ object CrashReportService {
338363 * @return the status of the report, true if the report was sent, false if the report is already
339364 * submitted
340365 */
341- fun sendReport (ankiActivity : AnkiActivity ): Boolean {
342- val preferences = ankiActivity .sharedPrefs()
366+ override fun sendReport (activity : Activity ): Boolean {
367+ val preferences = activity .sharedPrefs()
343368 val reportMode = preferences.getString(FEEDBACK_REPORT_KEY , " " )
344369 return if (FEEDBACK_REPORT_NEVER == reportMode) {
345370 preferences.edit { putBoolean(ACRA .PREF_DISABLE_ACRA , false ) }
346371 toastText = ToastType .MANUAL_TOAST .getToastMessage(application)
347372 createAcraCoreConfigBuilder()
348- val sendStatus = sendReportFor(ankiActivity )
373+ val sendStatus = sendReportFor(activity )
349374 dialogEnabled = false
350375 createAcraCoreConfigBuilder()
351376 preferences.edit { putBoolean(ACRA .PREF_DISABLE_ACRA , true ) }
352377 sendStatus
353378 } else {
354- sendReportFor(ankiActivity )
379+ sendReportFor(activity )
355380 }
356381 }
357382
358- private fun sendReportFor (activity : AnkiActivity ): Boolean {
383+ private fun sendReportFor (activity : Activity ): Boolean {
359384 val currentTimestamp = TimeManager .time.intTimeMS()
360385 val lastReportTimestamp = getTimestampOfLastReport(activity)
361386 return if (currentTimestamp - lastReportTimestamp > MIN_INTERVAL_MS ) {
@@ -376,7 +401,7 @@ object CrashReportService {
376401 * @param activity the Activity used for Context access when interrogating ACRA reports
377402 * @return the timestamp of the most recent report, or -1 if no reports at all
378403 */
379- private fun getTimestampOfLastReport (activity : AnkiActivity ): Long =
404+ private fun getTimestampOfLastReport (activity : Activity ): Long =
380405 LimiterData
381406 .load(activity)
382407 .reportMetadata
@@ -385,35 +410,23 @@ object CrashReportService {
385410}
386411
387412/* *
388- * Runs the provided block, catching [Exception], logging it and reporting it to [CrashReportService]
389- *
390- * **Example**
391- * ```
392- * runCatchingWithReport("callingMethod", onlyIfSilent = true) {
393- * doSomethingRisky()
394- * }
395- * ```
396- *
397- * **Note**: This differs from [runCatching] - `Error` is thrown
398- *
399- * @param origin Data logged to Timber, and provided as the 'origin' field in the error report
400- * @param onlyIfSilent Skip crash report if the crash reporting service is not 'always accept'
401- * @param block Code to execute
402- *
403- * @throws Error If raised, this will be reported and rethrown
404- *
405- * @return A Result containing either the successful result of [block] or the [Exception] thrown
413+ * Initializes ACRA crash reporting and wires it up as the
414+ * global [CrashReportService] reporter.
406415 */
407- fun <T > runCatchingWithReport (
408- origin : String? ,
409- onlyIfSilent : Boolean = false,
410- block : () -> T ,
411- ): Result <T > =
412- try {
413- Result .success(block())
414- } catch (e: Throwable ) {
415- Timber .w(e, origin)
416- CrashReportService .sendExceptionReport(e, origin, onlyIfSilent = onlyIfSilent)
417- if (e is Error ) throw e
418- Result .failure(e)
419- }
416+ context(application: Application )
417+ fun initializeAcraCrashReporter () {
418+ AcraCrashReporter .initialize(application)
419+ CrashReportService .setReporter(AcraCrashReporter )
420+ }
421+
422+ fun isAcraSenderProcess (): Boolean = AcraCrashReporter .isProperServiceProcess()
423+
424+ @VisibleForTesting(otherwise = VisibleForTesting .NONE )
425+ val CrashReportService .acraCoreConfigBuilder: CoreConfigurationBuilder
426+ get() = AcraCrashReporter .acraCoreConfigBuilder
427+
428+ @VisibleForTesting(otherwise = VisibleForTesting .NONE )
429+ fun setDebugACRAConfig (sharedPrefs : SharedPreferences ) = AcraCrashReporter .setDebugACRAConfig(sharedPrefs)
430+
431+ @VisibleForTesting(otherwise = VisibleForTesting .NONE )
432+ fun setProductionACRAConfig (sharedPrefs : SharedPreferences ) = AcraCrashReporter .setProductionACRAConfig(sharedPrefs)
0 commit comments