Skip to content

Commit 59900ff

Browse files
committed
fix(analytics): apply an opt-in change to the running session
The Settings switch is a plain SwitchPreferenceCompat, so it writes the preference itself. The GA client's 'enabled' flag is fixed when the client is built, so that write had no effect until the next launch. Rebuild the client when the preference changes from anywhere.
1 parent ba383b2 commit 59900ff

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnkiDroidUsageAnalytics.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,18 @@ object AnkiDroidUsageAnalytics {
7070
private val sharedPrefsListener =
7171
SharedPreferences.OnSharedPreferenceChangeListener { prefs, key ->
7272
if (key == ANALYTICS_OPTIN_KEY) {
73-
optIn = prefs.getBoolean(key, false)
74-
Timber.i("Setting analytics opt-in to: %b", optIn)
73+
val newOptIn = prefs.getBoolean(key, false)
74+
// [isEnabled] updates `optIn` before it writes, so an unchanged value
75+
// means it's already handling this toggle: don't rebuild twice
76+
if (newOptIn != optIn) {
77+
optIn = newOptIn
78+
Timber.i("Setting analytics opt-in to: %b", optIn)
79+
// the client's `enabled` flag is fixed when it's built, so writes
80+
// from elsewhere (the Settings switch) need a rebuild to take effect
81+
if (::analyticsContext.isInitialized) {
82+
reinitialize(analyticsContext)
83+
}
84+
}
7585
}
7686
}
7787

AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnkiDroidUsageAnalyticsTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33

44
package com.ichi2.anki.analytics
55

6+
import androidx.core.content.edit
67
import androidx.test.ext.junit.runners.AndroidJUnit4
78
import com.ichi2.anki.EmptyApplicationCategory
89
import com.ichi2.anki.R
910
import com.ichi2.anki.RobolectricTest
1011
import com.ichi2.testutils.EmptyApplication
12+
import io.mockk.every
13+
import io.mockk.mockkObject
14+
import io.mockk.unmockkObject
15+
import io.mockk.verify
1116
import org.hamcrest.CoreMatchers.equalTo
1217
import org.hamcrest.MatcherAssert.assertThat
1318
import org.junit.Test
@@ -38,4 +43,42 @@ class AnkiDroidUsageAnalyticsTest : RobolectricTest() {
3843
fun `analytics is disabled by default`() {
3944
assertThat(getPreferences().getBoolean(AnkiDroidUsageAnalytics.ANALYTICS_OPTIN_KEY, false), equalTo(false))
4045
}
46+
47+
@Test
48+
fun `a preference write from elsewhere rebuilds the client`() {
49+
withStubbedRebuild {
50+
getPreferences().edit(commit = true) { putBoolean(AnkiDroidUsageAnalytics.ANALYTICS_OPTIN_KEY, true) }
51+
52+
assertThat(AnkiDroidUsageAnalytics.isEnabled, equalTo(true))
53+
verify(exactly = 1) { AnkiDroidUsageAnalytics.reinitialize(any()) }
54+
}
55+
}
56+
57+
@Test
58+
fun `a write that leaves the value unchanged does not rebuild the client`() {
59+
withStubbedRebuild {
60+
getPreferences().edit(commit = true) { putBoolean(AnkiDroidUsageAnalytics.ANALYTICS_OPTIN_KEY, false) }
61+
62+
verify(exactly = 0) { AnkiDroidUsageAnalytics.reinitialize(any()) }
63+
}
64+
}
65+
66+
/**
67+
* Runs [block] with the analytics listener registered and `reinitialize` stubbed out:
68+
* the real one rebuilds on [kotlinx.coroutines.Dispatchers.IO] and would outlive the test.
69+
*/
70+
private fun withStubbedRebuild(block: () -> Unit) {
71+
mockkObject(AnkiDroidUsageAnalytics)
72+
every { AnkiDroidUsageAnalytics.reinitialize(any()) } returns Unit
73+
try {
74+
AnkiDroidUsageAnalytics.initialize(targetContext)
75+
block()
76+
} finally {
77+
// clear the preference before unmocking, so the resulting change
78+
// doesn't reach the real `reinitialize`
79+
getPreferences().edit(commit = true) { remove(AnkiDroidUsageAnalytics.ANALYTICS_OPTIN_KEY) }
80+
unmockkObject(AnkiDroidUsageAnalytics)
81+
AnalyticsExceptionHandler.uninstall()
82+
}
83+
}
4184
}

0 commit comments

Comments
 (0)