react-native-icon-switcher 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/build.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +15 -1
- package/android/src/main/java/com/iconswitcher/IconSwitcher.kt +85 -10
- package/android/src/main/java/com/iconswitcher/IconSwitcherInitializer.kt +15 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherModule.kt +1 -1
- package/android/src/test/java/com/iconswitcher/IconSwitcherTest.kt +230 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -242,7 +242,7 @@ it('applies a pending switch', async () => {
|
|
|
242
242
|
|
|
243
243
|
Deferred apply, task recreation when aliases toggle, the `applyPending()` foreground guard, cold-start behaviour, and launcher shortcuts are documented in [docs/guides/android.md](docs/guides/android.md).
|
|
244
244
|
|
|
245
|
-
Summary: with default `applyOn: 'background'`, `set()` returns `pending` and the launcher updates
|
|
245
|
+
Summary: with default `applyOn: 'background'`, `set()` returns `pending` and the launcher updates when the last screen stops (including swipe-away from Recents) or after you background the app; listening starts at process start via AndroidX Startup. The process usually survives but the next open may look like a cold start. `applyPending()` in the foreground is refused with `foreground`.
|
|
246
246
|
|
|
247
247
|
## Requirements
|
|
248
248
|
|
package/android/build.gradle
CHANGED
|
@@ -43,6 +43,7 @@ android {
|
|
|
43
43
|
dependencies {
|
|
44
44
|
implementation "com.facebook.react:react-android"
|
|
45
45
|
implementation "androidx.lifecycle:lifecycle-process:2.8.7"
|
|
46
|
+
implementation "androidx.startup:startup-runtime:1.2.0"
|
|
46
47
|
|
|
47
48
|
testImplementation "androidx.test:core:1.7.0"
|
|
48
49
|
testImplementation "junit:junit:4.13.2"
|
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
3
|
+
|
|
4
|
+
<application>
|
|
5
|
+
<provider
|
|
6
|
+
android:name="androidx.startup.InitializationProvider"
|
|
7
|
+
android:authorities="${applicationId}.androidx-startup"
|
|
8
|
+
android:exported="false"
|
|
9
|
+
tools:node="merge">
|
|
10
|
+
<meta-data
|
|
11
|
+
android:name="com.iconswitcher.IconSwitcherInitializer"
|
|
12
|
+
android:value="androidx.startup"
|
|
13
|
+
tools:node="merge" />
|
|
14
|
+
</provider>
|
|
15
|
+
</application>
|
|
2
16
|
</manifest>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.iconswitcher
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
3
5
|
import android.content.ComponentName
|
|
4
6
|
import android.content.Context
|
|
5
7
|
import android.content.pm.ApplicationInfo
|
|
@@ -7,6 +9,7 @@ import android.content.pm.PackageManager
|
|
|
7
9
|
import android.os.Build
|
|
8
10
|
import android.os.Handler
|
|
9
11
|
import android.os.Looper
|
|
12
|
+
import androidx.annotation.VisibleForTesting
|
|
10
13
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
11
14
|
import androidx.lifecycle.Lifecycle
|
|
12
15
|
import androidx.lifecycle.LifecycleOwner
|
|
@@ -25,9 +28,13 @@ class IconSwitcher internal constructor(
|
|
|
25
28
|
private val packageName: String,
|
|
26
29
|
private val prefs: android.content.SharedPreferences,
|
|
27
30
|
private val lifecycleRegistrar: (DefaultLifecycleObserver) -> Unit,
|
|
31
|
+
private val activityLifecycleRegistrar: (Application.ActivityLifecycleCallbacks) -> Unit,
|
|
28
32
|
private val isInForeground: () -> Boolean,
|
|
29
33
|
) {
|
|
30
34
|
private val writeExecutor = Executors.newSingleThreadExecutor()
|
|
35
|
+
private val processStartLock = Any()
|
|
36
|
+
private var processStartInitialized = false
|
|
37
|
+
private var startedActivityCount = 0
|
|
31
38
|
|
|
32
39
|
private val markedAliases: Map<String, ComponentName> by lazy { discoverMarkedAliases() }
|
|
33
40
|
private val applyOn: String by lazy { readApplyOn() }
|
|
@@ -53,6 +60,9 @@ class IconSwitcher internal constructor(
|
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
},
|
|
63
|
+
activityLifecycleRegistrar = { callbacks ->
|
|
64
|
+
(context.applicationContext as Application).registerActivityLifecycleCallbacks(callbacks)
|
|
65
|
+
},
|
|
56
66
|
isInForeground = {
|
|
57
67
|
ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
|
|
58
68
|
},
|
|
@@ -67,21 +77,33 @@ class IconSwitcher internal constructor(
|
|
|
67
77
|
packageName = context.applicationContext.packageName,
|
|
68
78
|
prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE),
|
|
69
79
|
lifecycleRegistrar = { },
|
|
80
|
+
activityLifecycleRegistrar = { },
|
|
70
81
|
isInForeground = { false },
|
|
71
82
|
)
|
|
72
83
|
|
|
73
84
|
fun onModuleCreate() {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
onProcessStart()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fun onProcessStart() {
|
|
89
|
+
synchronized(processStartLock) {
|
|
90
|
+
if (processStartInitialized) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
activityLifecycleRegistrar(createActivityLifecycleCallbacks())
|
|
94
|
+
lifecycleRegistrar(
|
|
95
|
+
object : DefaultLifecycleObserver {
|
|
96
|
+
override fun onStop(owner: LifecycleOwner) {
|
|
97
|
+
val pending = prefs.getString(KEY_PENDING, null)
|
|
98
|
+
if (pending != null) {
|
|
99
|
+
runApply(pending, deferred = true)
|
|
100
|
+
}
|
|
80
101
|
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
runColdStart()
|
|
105
|
+
processStartInitialized = true
|
|
106
|
+
}
|
|
85
107
|
}
|
|
86
108
|
|
|
87
109
|
fun current(): String {
|
|
@@ -126,6 +148,41 @@ class IconSwitcher internal constructor(
|
|
|
126
148
|
return SetResult(STATUS_APPLIED)
|
|
127
149
|
}
|
|
128
150
|
|
|
151
|
+
private fun createActivityLifecycleCallbacks(): Application.ActivityLifecycleCallbacks {
|
|
152
|
+
return object : Application.ActivityLifecycleCallbacks {
|
|
153
|
+
override fun onActivityStarted(activity: Activity) {
|
|
154
|
+
startedActivityCount++
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override fun onActivityStopped(activity: Activity) {
|
|
158
|
+
// Clamped: when registered late (no startup provider, so from the module),
|
|
159
|
+
// activities that started earlier were never counted.
|
|
160
|
+
startedActivityCount = maxOf(0, startedActivityCount - 1)
|
|
161
|
+
if (startedActivityCount == 0 && !activity.isChangingConfigurations) {
|
|
162
|
+
val pending = prefs.getString(KEY_PENDING, null)
|
|
163
|
+
if (pending != null) {
|
|
164
|
+
applyPendingOnLastActivityStop(pending)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
override fun onActivityCreated(activity: Activity, savedInstanceState: android.os.Bundle?) {}
|
|
170
|
+
override fun onActivityResumed(activity: Activity) {}
|
|
171
|
+
override fun onActivityPaused(activity: Activity) {}
|
|
172
|
+
override fun onActivitySaveInstanceState(activity: Activity, outState: android.os.Bundle) {}
|
|
173
|
+
override fun onActivityDestroyed(activity: Activity) {}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fun applyPendingOnLastActivityStop(name: String) {
|
|
178
|
+
val future = submitOnWriteExecutor { applyOnExecutor(name, deferred = true) }
|
|
179
|
+
try {
|
|
180
|
+
future.get()
|
|
181
|
+
} catch (_: Exception) {
|
|
182
|
+
// applyOnExecutor stores lastError for deferred failures; never propagate here.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
129
186
|
private fun runColdStart() {
|
|
130
187
|
if (prefs.getString(KEY_PENDING, null) != null) {
|
|
131
188
|
return
|
|
@@ -376,5 +433,23 @@ class IconSwitcher internal constructor(
|
|
|
376
433
|
const val STATUS_APPLIED = "applied"
|
|
377
434
|
const val STATUS_PENDING = "pending"
|
|
378
435
|
const val STATUS_NONE = "none"
|
|
436
|
+
|
|
437
|
+
@Volatile
|
|
438
|
+
private var instance: IconSwitcher? = null
|
|
439
|
+
|
|
440
|
+
fun getInstance(context: Context): IconSwitcher {
|
|
441
|
+
val appContext = context.applicationContext
|
|
442
|
+
instance?.let { return it }
|
|
443
|
+
return synchronized(this) {
|
|
444
|
+
instance ?: IconSwitcher(appContext).also { instance = it }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@VisibleForTesting
|
|
449
|
+
internal fun clearInstanceForTests() {
|
|
450
|
+
synchronized(this) {
|
|
451
|
+
instance = null
|
|
452
|
+
}
|
|
453
|
+
}
|
|
379
454
|
}
|
|
380
455
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.iconswitcher
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.lifecycle.ProcessLifecycleInitializer
|
|
5
|
+
import androidx.startup.Initializer
|
|
6
|
+
|
|
7
|
+
class IconSwitcherInitializer : Initializer<Unit> {
|
|
8
|
+
override fun create(context: Context) {
|
|
9
|
+
IconSwitcher.getInstance(context).onProcessStart()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override fun dependencies(): List<Class<out Initializer<*>>> {
|
|
13
|
+
return listOf(ProcessLifecycleInitializer::class.java)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.bridge.WritableMap
|
|
|
8
8
|
class IconSwitcherModule(reactContext: ReactApplicationContext) :
|
|
9
9
|
NativeIconSwitcherSpec(reactContext) {
|
|
10
10
|
|
|
11
|
-
private val switcher = IconSwitcher(reactApplicationContext)
|
|
11
|
+
private val switcher = IconSwitcher.getInstance(reactApplicationContext)
|
|
12
12
|
|
|
13
13
|
override fun initialize() {
|
|
14
14
|
super.initialize()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.iconswitcher
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
3
5
|
import android.content.ComponentName
|
|
4
6
|
import android.content.Context
|
|
5
7
|
import android.content.pm.ActivityInfo
|
|
@@ -34,6 +36,7 @@ class IconSwitcherTest {
|
|
|
34
36
|
|
|
35
37
|
@Before
|
|
36
38
|
fun setUp() {
|
|
39
|
+
IconSwitcher.clearInstanceForTests()
|
|
37
40
|
context = ApplicationProvider.getApplicationContext()
|
|
38
41
|
context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE)
|
|
39
42
|
.edit()
|
|
@@ -205,6 +208,7 @@ class IconSwitcherTest {
|
|
|
205
208
|
packageName = packageName,
|
|
206
209
|
prefs = prefs,
|
|
207
210
|
lifecycleRegistrar = { lifecycleObserver = it },
|
|
211
|
+
activityLifecycleRegistrar = { },
|
|
208
212
|
isInForeground = { false },
|
|
209
213
|
)
|
|
210
214
|
|
|
@@ -331,6 +335,231 @@ class IconSwitcherTest {
|
|
|
331
335
|
ShadowLooper.idleMainLooper()
|
|
332
336
|
}
|
|
333
337
|
|
|
338
|
+
@Test
|
|
339
|
+
fun initializer_starts_singleton_without_react_module() {
|
|
340
|
+
IconSwitcherInitializer().create(context)
|
|
341
|
+
val fromInitializer = IconSwitcher.getInstance(context)
|
|
342
|
+
assertTrue(fromInitializer.isSupported)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@Test
|
|
346
|
+
fun on_process_start_twice_registers_activity_callbacks_once() {
|
|
347
|
+
var registrationCount = 0
|
|
348
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
349
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(context.packageName)
|
|
350
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
351
|
+
val switcher = switcherWithActivityCallbacks(
|
|
352
|
+
pm = pm,
|
|
353
|
+
onRegister = { registrationCount++ },
|
|
354
|
+
)
|
|
355
|
+
switcher.onProcessStart()
|
|
356
|
+
switcher.onProcessStart()
|
|
357
|
+
assertEquals(1, registrationCount)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@Test
|
|
361
|
+
fun getInstance_is_shared_between_initializer_calls() {
|
|
362
|
+
IconSwitcherInitializer().create(context)
|
|
363
|
+
val first = IconSwitcher.getInstance(context)
|
|
364
|
+
val second = IconSwitcher.getInstance(context)
|
|
365
|
+
assertTrue(first === second)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@Test
|
|
369
|
+
fun last_activity_stop_applies_pending_before_on_activity_stopped_returns() {
|
|
370
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
371
|
+
val packageName = context.packageName
|
|
372
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(packageName)
|
|
373
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
374
|
+
val settingsSlot = slot<List<PackageManager.ComponentEnabledSetting>>()
|
|
375
|
+
every { pm.setComponentEnabledSettings(capture(settingsSlot)) } returns Unit
|
|
376
|
+
every { pm.getComponentEnabledSetting(any()) } answers {
|
|
377
|
+
val component = firstArg<ComponentName>()
|
|
378
|
+
if (component.className == ".IconSwitcher_halloween") {
|
|
379
|
+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
380
|
+
} else {
|
|
381
|
+
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
val prefs = context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE)
|
|
386
|
+
prefs.edit().putString(IconSwitcher.KEY_PENDING, "halloween").apply()
|
|
387
|
+
|
|
388
|
+
var callbacks: Application.ActivityLifecycleCallbacks? = null
|
|
389
|
+
val switcher = switcherWithActivityCallbacks(pm) { callbacks = it }
|
|
390
|
+
switcher.onProcessStart()
|
|
391
|
+
|
|
392
|
+
val activity = mockk<Activity>(relaxed = true)
|
|
393
|
+
every { activity.isChangingConfigurations } returns false
|
|
394
|
+
callbacks!!.onActivityStarted(activity)
|
|
395
|
+
callbacks!!.onActivityStopped(activity)
|
|
396
|
+
|
|
397
|
+
assertTrue(
|
|
398
|
+
settingsSlot.captured.any {
|
|
399
|
+
it.componentName!!.className == ".IconSwitcher_halloween" &&
|
|
400
|
+
it.enabledFlags == PackageManager.DONT_KILL_APP
|
|
401
|
+
},
|
|
402
|
+
)
|
|
403
|
+
assertEquals(null, prefs.getString(IconSwitcher.KEY_PENDING, null))
|
|
404
|
+
assertEquals("halloween", prefs.getString(IconSwitcher.KEY_CURRENT, null))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@Test
|
|
408
|
+
fun last_activity_stop_applies_when_callbacks_were_registered_after_it_started() {
|
|
409
|
+
// No startup provider: listening starts from the module while a screen is
|
|
410
|
+
// already open, so its start was never counted.
|
|
411
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
412
|
+
val packageName = context.packageName
|
|
413
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(packageName)
|
|
414
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
415
|
+
val settingsSlot = slot<List<PackageManager.ComponentEnabledSetting>>()
|
|
416
|
+
every { pm.setComponentEnabledSettings(capture(settingsSlot)) } returns Unit
|
|
417
|
+
every { pm.getComponentEnabledSetting(any()) } answers {
|
|
418
|
+
val component = firstArg<ComponentName>()
|
|
419
|
+
if (component.className == ".IconSwitcher_halloween") {
|
|
420
|
+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
421
|
+
} else {
|
|
422
|
+
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
val prefs = context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE)
|
|
427
|
+
prefs.edit().putString(IconSwitcher.KEY_PENDING, "halloween").apply()
|
|
428
|
+
|
|
429
|
+
var callbacks: Application.ActivityLifecycleCallbacks? = null
|
|
430
|
+
val switcher = switcherWithActivityCallbacks(pm) { callbacks = it }
|
|
431
|
+
switcher.onProcessStart()
|
|
432
|
+
|
|
433
|
+
val activity = mockk<Activity>(relaxed = true)
|
|
434
|
+
every { activity.isChangingConfigurations } returns false
|
|
435
|
+
callbacks!!.onActivityStopped(activity)
|
|
436
|
+
|
|
437
|
+
assertTrue(settingsSlot.isCaptured)
|
|
438
|
+
assertEquals(null, prefs.getString(IconSwitcher.KEY_PENDING, null))
|
|
439
|
+
assertEquals("halloween", prefs.getString(IconSwitcher.KEY_CURRENT, null))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
@Test
|
|
443
|
+
fun last_activity_stop_with_two_activities_applies_only_after_both_stop() {
|
|
444
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
445
|
+
val packageName = context.packageName
|
|
446
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(packageName)
|
|
447
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
448
|
+
|
|
449
|
+
val prefs = context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE)
|
|
450
|
+
prefs.edit().putString(IconSwitcher.KEY_PENDING, "halloween").apply()
|
|
451
|
+
|
|
452
|
+
var callbacks: Application.ActivityLifecycleCallbacks? = null
|
|
453
|
+
val switcher = switcherWithActivityCallbacks(pm) { callbacks = it }
|
|
454
|
+
switcher.onProcessStart()
|
|
455
|
+
|
|
456
|
+
val first = mockk<Activity>(relaxed = true)
|
|
457
|
+
val second = mockk<Activity>(relaxed = true)
|
|
458
|
+
every { first.isChangingConfigurations } returns false
|
|
459
|
+
every { second.isChangingConfigurations } returns false
|
|
460
|
+
|
|
461
|
+
callbacks!!.onActivityStarted(first)
|
|
462
|
+
callbacks!!.onActivityStarted(second)
|
|
463
|
+
callbacks!!.onActivityStopped(second)
|
|
464
|
+
verify(exactly = 0) { pm.setComponentEnabledSettings(any()) }
|
|
465
|
+
|
|
466
|
+
every { pm.getComponentEnabledSetting(any()) } answers {
|
|
467
|
+
val component = firstArg<ComponentName>()
|
|
468
|
+
if (component.className == ".IconSwitcher_halloween") {
|
|
469
|
+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
470
|
+
} else {
|
|
471
|
+
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
every { pm.setComponentEnabledSettings(any<List<PackageManager.ComponentEnabledSetting>>()) } returns Unit
|
|
475
|
+
|
|
476
|
+
callbacks!!.onActivityStopped(first)
|
|
477
|
+
verify(exactly = 1) { pm.setComponentEnabledSettings(any()) }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
@Test
|
|
481
|
+
fun last_activity_stop_during_configuration_change_does_not_apply() {
|
|
482
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
483
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(context.packageName)
|
|
484
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
485
|
+
|
|
486
|
+
context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE)
|
|
487
|
+
.edit()
|
|
488
|
+
.putString(IconSwitcher.KEY_PENDING, "halloween")
|
|
489
|
+
.apply()
|
|
490
|
+
|
|
491
|
+
var callbacks: Application.ActivityLifecycleCallbacks? = null
|
|
492
|
+
val switcher = switcherWithActivityCallbacks(pm) { callbacks = it }
|
|
493
|
+
switcher.onProcessStart()
|
|
494
|
+
|
|
495
|
+
val activity = mockk<Activity>(relaxed = true)
|
|
496
|
+
every { activity.isChangingConfigurations } returns true
|
|
497
|
+
callbacks!!.onActivityStarted(activity)
|
|
498
|
+
callbacks!!.onActivityStopped(activity)
|
|
499
|
+
|
|
500
|
+
verify(exactly = 0) { pm.setComponentEnabledSettings(any()) }
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@Test
|
|
504
|
+
fun last_activity_stop_without_pending_does_nothing() {
|
|
505
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
506
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(context.packageName)
|
|
507
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
508
|
+
|
|
509
|
+
var callbacks: Application.ActivityLifecycleCallbacks? = null
|
|
510
|
+
val switcher = switcherWithActivityCallbacks(pm) { callbacks = it }
|
|
511
|
+
switcher.onProcessStart()
|
|
512
|
+
|
|
513
|
+
val activity = mockk<Activity>(relaxed = true)
|
|
514
|
+
every { activity.isChangingConfigurations } returns false
|
|
515
|
+
callbacks!!.onActivityStarted(activity)
|
|
516
|
+
callbacks!!.onActivityStopped(activity)
|
|
517
|
+
|
|
518
|
+
verify(exactly = 0) { pm.setComponentEnabledSettings(any()) }
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@Test
|
|
522
|
+
fun last_activity_stop_apply_failure_stores_last_error_without_throwing() {
|
|
523
|
+
val pm = mockk<PackageManager>(relaxed = true)
|
|
524
|
+
every { pm.getPackageInfo(any<String>(), any<Int>()) } returns robolectricPackageInfo(context.packageName)
|
|
525
|
+
every { pm.getApplicationInfo(any<String>(), any<Int>()) } returns robolectricApplicationInfo()
|
|
526
|
+
every { pm.setComponentEnabledSettings(any<List<PackageManager.ComponentEnabledSetting>>()) } throws
|
|
527
|
+
RuntimeException("pm failed")
|
|
528
|
+
every { pm.getComponentEnabledSetting(any()) } returns PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
|
529
|
+
|
|
530
|
+
val prefs = context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE)
|
|
531
|
+
prefs.edit().putString(IconSwitcher.KEY_PENDING, "halloween").apply()
|
|
532
|
+
|
|
533
|
+
var callbacks: Application.ActivityLifecycleCallbacks? = null
|
|
534
|
+
val switcher = switcherWithActivityCallbacks(pm) { callbacks = it }
|
|
535
|
+
switcher.onProcessStart()
|
|
536
|
+
|
|
537
|
+
val activity = mockk<Activity>(relaxed = true)
|
|
538
|
+
every { activity.isChangingConfigurations } returns false
|
|
539
|
+
callbacks!!.onActivityStarted(activity)
|
|
540
|
+
callbacks!!.onActivityStopped(activity)
|
|
541
|
+
|
|
542
|
+
assertEquals("halloween", prefs.getString(IconSwitcher.KEY_PENDING, null))
|
|
543
|
+
assertEquals("pm failed", prefs.getString(IconSwitcher.KEY_LAST_ERROR, null))
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private fun switcherWithActivityCallbacks(
|
|
547
|
+
pm: PackageManager,
|
|
548
|
+
prefs: android.content.SharedPreferences =
|
|
549
|
+
context.getSharedPreferences(IconSwitcher.PREFS_NAME, Context.MODE_PRIVATE),
|
|
550
|
+
onRegister: (Application.ActivityLifecycleCallbacks) -> Unit = { },
|
|
551
|
+
): IconSwitcher {
|
|
552
|
+
return IconSwitcher(
|
|
553
|
+
context = context,
|
|
554
|
+
packageManager = pm,
|
|
555
|
+
packageName = context.packageName,
|
|
556
|
+
prefs = prefs,
|
|
557
|
+
lifecycleRegistrar = { },
|
|
558
|
+
activityLifecycleRegistrar = onRegister,
|
|
559
|
+
isInForeground = { false },
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
|
|
334
563
|
private fun switcherWithForeground(
|
|
335
564
|
pm: PackageManager,
|
|
336
565
|
inForeground: Boolean,
|
|
@@ -343,6 +572,7 @@ class IconSwitcherTest {
|
|
|
343
572
|
packageName = context.packageName,
|
|
344
573
|
prefs = prefs,
|
|
345
574
|
lifecycleRegistrar = { },
|
|
575
|
+
activityLifecycleRegistrar = { },
|
|
346
576
|
isInForeground = { inForeground },
|
|
347
577
|
)
|
|
348
578
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-icon-switcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Switch the app icon at runtime on React Native and Expo from one config file, with the native wiring generated for you",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|