diff options
| -rw-r--r-- | packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt | 75 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt | 144 |
2 files changed, 134 insertions, 85 deletions
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index c41dc53fdc6b..cb76ad7c77fe 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -507,9 +507,10 @@ open class ClockRegistry( } } - private var isVerifying = AtomicBoolean(false) + private var isQueued = AtomicBoolean(false) fun verifyLoadedProviders() { - val shouldSchedule = isVerifying.compareAndSet(false, true) + Log.i(TAG, Thread.currentThread().getStackTrace().toString()) + val shouldSchedule = isQueued.compareAndSet(false, true) if (!shouldSchedule) { logger.tryLog( TAG, @@ -521,48 +522,54 @@ open class ClockRegistry( } scope.launch(bgDispatcher) { - if (keepAllLoaded) { - logger.tryLog( - TAG, - LogLevel.INFO, - {}, - { "verifyLoadedProviders: keepAllLoaded=true" } - ) - // Enforce that all plugins are loaded if requested - for ((_, info) in availableClocks) { - info.manager?.loadPlugin() + // TODO(b/267372164): Use better threading approach when converting to flows + synchronized(availableClocks) { + isQueued.set(false) + if (keepAllLoaded) { + logger.tryLog( + TAG, + LogLevel.INFO, + {}, + { "verifyLoadedProviders: keepAllLoaded=true" } + ) + // Enforce that all plugins are loaded if requested + for ((_, info) in availableClocks) { + info.manager?.loadPlugin() + } + return@launch + } + + val currentClock = availableClocks[currentClockId] + if (currentClock == null) { + logger.tryLog( + TAG, + LogLevel.INFO, + {}, + { "verifyLoadedProviders: currentClock=null" } + ) + // Current Clock missing, load no plugins and use default + for ((_, info) in availableClocks) { + info.manager?.unloadPlugin() + } + return@launch } - isVerifying.set(false) - return@launch - } - val currentClock = availableClocks[currentClockId] - if (currentClock == null) { logger.tryLog( TAG, LogLevel.INFO, {}, - { "verifyLoadedProviders: currentClock=null" } + { "verifyLoadedProviders: load currentClock" } ) - // Current Clock missing, load no plugins and use default - for ((_, info) in availableClocks) { - info.manager?.unloadPlugin() - } - isVerifying.set(false) - return@launch - } - - logger.tryLog(TAG, LogLevel.INFO, {}, { "verifyLoadedProviders: load currentClock" }) - val currentManager = currentClock.manager - currentManager?.loadPlugin() + val currentManager = currentClock.manager + currentManager?.loadPlugin() - for ((_, info) in availableClocks) { - val manager = info.manager - if (manager != null && currentManager != manager) { - manager.unloadPlugin() + for ((_, info) in availableClocks) { + val manager = info.manager + if (manager != null && currentManager != manager) { + manager.unloadPlugin() + } } } - isVerifying.set(false) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 48665fe0c9b0..e71473681211 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.shared.clocks +import android.content.ComponentName import android.content.ContentResolver import android.content.Context import android.graphics.drawable.Drawable @@ -28,12 +29,11 @@ import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.ClockSettings -import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginLifecycleManager +import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.fail import kotlinx.coroutines.CoroutineDispatcher @@ -46,6 +46,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.never +import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -66,7 +67,6 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockContentResolver: ContentResolver - @Mock private lateinit var mockPluginLifecycle: PluginLifecycleManager<ClockProviderPlugin> private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry @@ -84,6 +84,41 @@ class ClockRegistryTest : SysuiTestCase() { } } + private class FakeLifecycle( + private val tag: String, + private val plugin: ClockProviderPlugin?, + ) : PluginLifecycleManager<ClockProviderPlugin> { + var onLoad: (() -> Unit)? = null + var onUnload: (() -> Unit)? = null + + private var mIsLoaded: Boolean = true + override fun isLoaded() = mIsLoaded + override fun getPlugin(): ClockProviderPlugin? = if (isLoaded) plugin else null + + var mComponentName = ComponentName("Package[$tag]", "Class[$tag]") + override fun toString() = "Manager[$tag]" + override fun getPackage(): String = mComponentName.getPackageName() + override fun getComponentName(): ComponentName = mComponentName + + private var isDebug: Boolean = false + override fun getIsDebug(): Boolean = isDebug + override fun setIsDebug(value: Boolean) { isDebug = value } + + override fun loadPlugin() { + if (!mIsLoaded) { + mIsLoaded = true + onLoad?.invoke() + } + } + + override fun unloadPlugin() { + if (mIsLoaded) { + mIsLoaded = false + onUnload?.invoke() + } + } + } + private class FakeClockPlugin : ClockProviderPlugin { private val metadata = mutableListOf<ClockMetadata>() private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>() @@ -150,13 +185,15 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = FakeLifecycle("1", plugin1) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") + val lifecycle2 = FakeLifecycle("2", plugin2) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val list = registry.getClocks() assertEquals( list.toSet(), @@ -178,18 +215,18 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail }) .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin2 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val list = registry.getClocks() assertEquals( list.toSet(), @@ -204,8 +241,8 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(registry.createExampleClock("clock_2"), mockClock) assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail) assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail) - verify(mockPluginLifecycle1, never()).unloadPlugin() - verify(mockPluginLifecycle2, times(2)).unloadPlugin() + verify(lifecycle1, never()).unloadPlugin() + verify(lifecycle2, times(2)).unloadPlugin() } @Test @@ -213,14 +250,16 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val clock = registry.createCurrentClock() assertEquals(mockClock, clock) @@ -231,17 +270,19 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) assertEquals(DEFAULT_CLOCK_ID, registry.activeClockId) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) assertEquals("clock_3", registry.activeClockId) } @@ -250,15 +291,17 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) - pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) + pluginListener.onPluginUnloaded(plugin2, lifecycle2) val clock = registry.createCurrentClock() assertEquals(clock, mockDefaultClock) @@ -266,15 +309,15 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun pluginRemoved_clockAndListChanged() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) var changeCallCount = 0 var listChangeCallCount = 0 @@ -288,32 +331,32 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(1, changeCallCount) assertEquals(0, listChangeCallCount) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(1, listChangeCallCount) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) scheduler.runCurrent() assertEquals(2, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginUnloaded(plugin1, mockPluginLifecycle1) + pluginListener.onPluginUnloaded(plugin1, lifecycle1) scheduler.runCurrent() assertEquals(2, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle2) + pluginListener.onPluginUnloaded(plugin2, lifecycle2) scheduler.runCurrent() assertEquals(3, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginDetached(mockPluginLifecycle1) + pluginListener.onPluginDetached(lifecycle1) scheduler.runCurrent() assertEquals(3, changeCallCount) assertEquals(3, listChangeCallCount) - pluginListener.onPluginDetached(mockPluginLifecycle2) + pluginListener.onPluginDetached(lifecycle2) scheduler.runCurrent() assertEquals(3, changeCallCount) assertEquals(4, listChangeCallCount) @@ -321,8 +364,9 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun unknownPluginAttached_clockAndListUnchanged_loadRequested() { - val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle.getPackage()).thenReturn("some.other.package") + val lifecycle = FakeLifecycle("", null).apply { + mComponentName = ComponentName("some.other.package", "SomeClass") + } var changeCallCount = 0 var listChangeCallCount = 0 @@ -331,7 +375,7 @@ class ClockRegistryTest : SysuiTestCase() { override fun onAvailableClocksChanged() { listChangeCallCount++ } }) - assertEquals(true, pluginListener.onPluginAttached(mockPluginLifecycle)) + assertEquals(true, pluginListener.onPluginAttached(lifecycle)) scheduler.runCurrent() assertEquals(0, changeCallCount) assertEquals(0, listChangeCallCount) @@ -339,10 +383,12 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun knownPluginAttached_clockAndListChanged_notLoaded() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.clocks.metro") - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.clocks.bignum") + val lifecycle1 = FakeLifecycle("Metro", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.metro", "MetroClock") + } + val lifecycle2 = FakeLifecycle("BigNum", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNumClock") + } var changeCallCount = 0 var listChangeCallCount = 0 @@ -356,12 +402,12 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(1, changeCallCount) assertEquals(0, listChangeCallCount) - assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle1)) + assertEquals(false, pluginListener.onPluginAttached(lifecycle1)) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(1, listChangeCallCount) - assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle2)) + assertEquals(false, pluginListener.onPluginAttached(lifecycle2)) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(2, listChangeCallCount) @@ -369,18 +415,14 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun pluginAddRemove_concurrentModification() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - val mockPluginLifecycle3 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - val mockPluginLifecycle4 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin().addClock("clock_1", "clock 1") + val lifecycle1 = FakeLifecycle("1", plugin1) val plugin2 = FakeClockPlugin().addClock("clock_2", "clock 2") + val lifecycle2 = FakeLifecycle("2", plugin2) val plugin3 = FakeClockPlugin().addClock("clock_3", "clock 3") + val lifecycle3 = FakeLifecycle("3", plugin3) val plugin4 = FakeClockPlugin().addClock("clock_4", "clock 4") - whenever(mockPluginLifecycle1.isLoaded).thenReturn(true) - whenever(mockPluginLifecycle2.isLoaded).thenReturn(true) - whenever(mockPluginLifecycle3.isLoaded).thenReturn(true) - whenever(mockPluginLifecycle4.isLoaded).thenReturn(true) + val lifecycle4 = FakeLifecycle("4", plugin4) // Set the current clock to the final clock to load registry.applySettings(ClockSettings("clock_4", null)) @@ -390,15 +432,15 @@ class ClockRegistryTest : SysuiTestCase() { // unload other plugins. This causes ClockRegistry to modify the list of available clock // plugins while it is being iterated over. In production this happens as a result of a // thread race, instead of synchronously like it does here. - whenever(mockPluginLifecycle2.unloadPlugin()).then { - pluginListener.onPluginDetached(mockPluginLifecycle1) - pluginListener.onPluginLoaded(plugin4, mockContext, mockPluginLifecycle4) + lifecycle2.onUnload = { + pluginListener.onPluginDetached(lifecycle1) + pluginListener.onPluginLoaded(plugin4, mockContext, lifecycle4) } // Load initial plugins - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) - pluginListener.onPluginLoaded(plugin3, mockContext, mockPluginLifecycle3) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) + pluginListener.onPluginLoaded(plugin3, mockContext, lifecycle3) // Repeatedly verify the loaded providers to get final state registry.verifyLoadedProviders() @@ -484,11 +526,11 @@ class ClockRegistryTest : SysuiTestCase() { private fun testTransitClockFlag(flag: Boolean) { featureFlags.set(TRANSIT_CLOCK, flag) registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK) - val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("DIGITAL_CLOCK_METRO", "metro clock") - pluginListener.onPluginLoaded(plugin, mockContext, mockPluginLifecycle) + val lifecycle = FakeLifecycle("metro", plugin) + pluginListener.onPluginLoaded(plugin, mockContext, lifecycle) val list = registry.getClocks() if (flag) { |