Skip to content

Commit 9b607ae

Browse files
committed
refactor: rename CrashReportService to AcraCrashReporter
- refactor(crash): switch call sites to CrashReportService from :common - refactor: update imports to use CrashReportService from :common Assisted-by: Claude Opus 4 (1M context)
1 parent 3eb4e31 commit 9b607ae

50 files changed

Lines changed: 144 additions & 119 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ACRATest.kt

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,19 @@ import androidx.core.content.edit
2222
import androidx.test.annotation.UiThreadTest
2323
import androidx.test.ext.junit.runners.AndroidJUnit4
2424
import com.ichi2.anki.AnkiDroidApp
25-
import com.ichi2.anki.CrashReportService
26-
import com.ichi2.anki.CrashReportService.FEEDBACK_REPORT_ALWAYS
27-
import com.ichi2.anki.CrashReportService.FEEDBACK_REPORT_ASK
2825
import com.ichi2.anki.R
26+
import com.ichi2.anki.acraCoreConfigBuilder
2927
import com.ichi2.anki.analytics.UsageAnalytics
28+
import com.ichi2.anki.common.crashreporting.CrashReportService
29+
import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_ALWAYS
30+
import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_ASK
31+
import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_KEY
32+
import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_NEVER
3033
import com.ichi2.anki.logging.ProductionCrashReportingTree
3134
import com.ichi2.anki.preferences.sharedPrefs
3235
import com.ichi2.anki.servicelayer.ThrowableFilterService
36+
import com.ichi2.anki.setDebugACRAConfig
37+
import com.ichi2.anki.setProductionACRAConfig
3338
import com.ichi2.anki.testutil.GrantStoragePermission
3439
import org.acra.ACRA
3540
import org.acra.builder.ReportBuilder
@@ -70,7 +75,7 @@ class ACRATest : InstrumentedTest() {
7075
@Throws(Exception::class)
7176
fun testDebugConfiguration() {
7277
// Debug mode overrides all saved state so no setup needed
73-
CrashReportService.setDebugACRAConfig(sharedPrefs)
78+
setDebugACRAConfig(sharedPrefs)
7479
assertArrayEquals(
7580
"Debug logcat arguments not set correctly",
7681
CrashReportService.acraCoreConfigBuilder
@@ -90,21 +95,21 @@ class ACRATest : InstrumentedTest() {
9095
)
9196
assertEquals(
9297
"ACRA feedback was not turned off correctly",
93-
CrashReportService.FEEDBACK_REPORT_NEVER,
98+
FEEDBACK_REPORT_NEVER,
9499
sharedPrefs
95-
.getString(CrashReportService.FEEDBACK_REPORT_KEY, "undefined"),
100+
.getString(FEEDBACK_REPORT_KEY, "undefined"),
96101
)
97102
}
98103

99104
@Test
100105
@Throws(Exception::class)
101106
fun testProductionConfigurationUserDisabled() {
102107
// set up as if the user had prefs saved to disable completely
103-
setReportConfig(CrashReportService.FEEDBACK_REPORT_NEVER)
108+
setReportConfig(FEEDBACK_REPORT_NEVER)
104109

105110
// ACRA initializes production logcat via annotation and we can't mock Build.DEBUG
106111
// That means we are restricted from verifying production logcat args and this is the debug case again
107-
CrashReportService.setProductionACRAConfig(sharedPrefs)
112+
setProductionACRAConfig(sharedPrefs)
108113
verifyDebugACRAPreferences()
109114
}
110115

@@ -115,7 +120,7 @@ class ACRATest : InstrumentedTest() {
115120
setReportConfig(FEEDBACK_REPORT_ASK)
116121

117122
// If the user is set to ask, then it's production, with interaction mode dialog
118-
CrashReportService.setProductionACRAConfig(sharedPrefs)
123+
setProductionACRAConfig(sharedPrefs)
119124
verifyACRANotDisabled()
120125

121126
assertToastMessage(R.string.feedback_for_manual_toast_text)
@@ -134,7 +139,7 @@ class ACRATest : InstrumentedTest() {
134139

135140
// If the user is set to always, then it's production, with interaction mode toast
136141
// will be useful with ACRA 5.2.0
137-
CrashReportService.setProductionACRAConfig(sharedPrefs)
142+
setProductionACRAConfig(sharedPrefs)
138143

139144
// The same class/method combo is only sent once, so we face a new method each time (should test that system later)
140145
val crash = Exception("testCrashReportSend at " + System.currentTimeMillis())
@@ -173,7 +178,7 @@ class ACRATest : InstrumentedTest() {
173178
)
174179

175180
// Now let's clear data
176-
CrashReportService.deleteACRALimiterData(testContext)
181+
CrashReportService.deleteLimiterData(testContext)
177182

178183
// A third send should work again
179184
assertTrue(
@@ -192,7 +197,7 @@ class ACRATest : InstrumentedTest() {
192197
setReportConfig(FEEDBACK_REPORT_ALWAYS)
193198

194199
// If the user is set to always, then it's production, with interaction mode toast
195-
CrashReportService.setProductionACRAConfig(sharedPrefs)
200+
setProductionACRAConfig(sharedPrefs)
196201
verifyACRANotDisabled()
197202

198203
assertToastMessage(R.string.feedback_auto_toast_text)
@@ -207,13 +212,13 @@ class ACRATest : InstrumentedTest() {
207212
setReportConfig(FEEDBACK_REPORT_ALWAYS)
208213

209214
// If the user is set to ask, then it's production, with interaction mode dialog
210-
CrashReportService.setProductionACRAConfig(sharedPrefs)
215+
setProductionACRAConfig(sharedPrefs)
211216
verifyACRANotDisabled()
212217

213218
assertDialogEnabledStatus("dialog should be disabled when status is ALWAYS", false)
214219
assertToastMessage(R.string.feedback_auto_toast_text)
215220

216-
setAcraReportingMode(FEEDBACK_REPORT_ASK)
221+
CrashReportService.setReportingMode(FEEDBACK_REPORT_ASK)
217222

218223
assertDialogEnabledStatus("dialog should be re-enabled after changed to ASK", true)
219224
assertToastMessage(R.string.feedback_for_manual_toast_text)
@@ -226,12 +231,12 @@ class ACRATest : InstrumentedTest() {
226231
setReportConfig(FEEDBACK_REPORT_ASK)
227232

228233
// If the user is set to ask, then it's production, with interaction mode dialog
229-
CrashReportService.setProductionACRAConfig(sharedPrefs)
234+
setProductionACRAConfig(sharedPrefs)
230235
verifyACRANotDisabled()
231236

232237
assertToastMessage(R.string.feedback_for_manual_toast_text)
233238

234-
setAcraReportingMode(FEEDBACK_REPORT_ALWAYS)
239+
CrashReportService.setReportingMode(FEEDBACK_REPORT_ALWAYS)
235240

236241
assertToastMessage(R.string.feedback_auto_toast_text)
237242
}
@@ -281,10 +286,6 @@ class ACRATest : InstrumentedTest() {
281286
)
282287
}
283288

284-
private fun setAcraReportingMode(feedbackReportAlways: String) {
285-
CrashReportService.setAcraReportingMode(feedbackReportAlways)
286-
}
287-
288289
@Throws(ACRAConfigurationException::class)
289290
private fun assertDialogEnabledStatus(
290291
message: String,
@@ -333,7 +334,7 @@ class ACRATest : InstrumentedTest() {
333334
}
334335

335336
private fun setReportConfig(feedbackReportAsk: String) {
336-
sharedPrefs.edit { putString(CrashReportService.FEEDBACK_REPORT_KEY, feedbackReportAsk) }
337+
sharedPrefs.edit { putString(FEEDBACK_REPORT_KEY, feedbackReportAsk) }
337338
}
338339

339340
private val sharedPrefs: SharedPreferences

AnkiDroid/src/main/java/com/ichi2/anki/CrashReportService.kt renamed to AnkiDroid/src/main/java/com/ichi2/anki/AcraCrashReporter.kt

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.ichi2.anki
1717

18+
import android.app.Activity
1819
import android.app.Application
1920
import android.content.Context
2021
import android.content.SharedPreferences
@@ -26,6 +27,12 @@ import androidx.webkit.WebViewCompat
2627
import com.ichi2.anki.analytics.AnkiDroidCrashReportDialog
2728
import com.ichi2.anki.analytics.UsageAnalytics
2829
import 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
2936
import com.ichi2.anki.common.time.TimeManager
3037
import com.ichi2.anki.exception.ManuallyReportedException
3138
import com.ichi2.anki.exception.UserSubmittedException
@@ -43,13 +50,7 @@ import org.acra.config.ToastConfigurationBuilder
4350
import org.acra.sender.HttpSender
4451
import 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)

AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import com.ichi2.anki.android.input.ShortcutGroup
6161
import com.ichi2.anki.android.input.ShortcutGroupProvider
6262
import com.ichi2.anki.android.input.shortcut
6363
import com.ichi2.anki.common.annotations.LegacyNotifications
64+
import com.ichi2.anki.common.crashreporting.CrashReportService
6465
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
6566
import com.ichi2.anki.compat.CompatHelper
6667
import com.ichi2.anki.compat.CompatHelper.Companion.registerReceiverCompat

0 commit comments

Comments
 (0)