Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52e4a07a authored by Hawkwood Glazier's avatar Hawkwood Glazier Committed by Android (Google) Code Review
Browse files

Merge "Guard off thread list mutations of the available clock map" into udc-dev

parents 0d0d38f3 9de8f1bf
Loading
Loading
Loading
Loading
+18 −15
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.Assert
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -41,6 +42,18 @@ import kotlinx.coroutines.launch
private const val DEBUG = true
private val KEY_TIMESTAMP = "appliedTimestamp"

private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
    key: TKey,
    value: TVal,
    onNew: () -> Unit
): TVal {
    val result = this.putIfAbsent(key, value)
    if (result == null) {
        onNew()
    }
    return result ?: value
}

/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
    val context: Context,
@@ -64,7 +77,7 @@ open class ClockRegistry(
        fun onAvailableClocksChanged() {}
    }

    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
    private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
    private val clockChangeListeners = mutableListOf<ClockChangeListener>()
    private val settingObserver =
        object : ContentObserver(null) {
@@ -92,14 +105,8 @@ open class ClockRegistry(
                var isClockListChanged = false
                for (clock in plugin.getClocks()) {
                    val id = clock.clockId
                    var isNew = false
                    val info =
                        availableClocks.getOrPut(id) {
                            isNew = true
                            ClockInfo(clock, plugin, manager)
                        }

                    if (isNew) {
                        availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
                            isClockListChanged = true
                            onConnected(id)
                        }
@@ -254,10 +261,8 @@ open class ClockRegistry(
            return
        }

        android.util.Log.e("HAWK", "triggerOnCurrentClockChanged")
        scope.launch(mainDispatcher) {
            assertMainThread()
            android.util.Log.e("HAWK", "isClockChanged")
            isClockChanged.set(false)
            clockChangeListeners.forEach { it.onCurrentClockChanged() }
        }
@@ -270,10 +275,8 @@ open class ClockRegistry(
            return
        }

        android.util.Log.e("HAWK", "triggerOnAvailableClocksChanged")
        scope.launch(mainDispatcher) {
            assertMainThread()
            android.util.Log.e("HAWK", "isClockListChanged")
            isClockListChanged.set(false)
            clockChangeListeners.forEach { it.onAvailableClocksChanged() }
        }
@@ -356,7 +359,7 @@ open class ClockRegistry(
    }

    private var isVerifying = AtomicBoolean(false)
    private fun verifyLoadedProviders() {
    fun verifyLoadedProviders() {
        val shouldSchedule = isVerifying.compareAndSet(false, true)
        if (!shouldSchedule) {
            return
+52 −5
Original line number Diff line number Diff line
@@ -122,7 +122,7 @@ class ClockRegistryTest : SysuiTestCase() {
            isEnabled = true,
            handleAllUsers = true,
            defaultClockProvider = fakeDefaultProvider,
            keepAllLoaded = true,
            keepAllLoaded = false,
            subTag = "Test",
        ) {
            override fun querySettings() { }
@@ -154,8 +154,8 @@ class ClockRegistryTest : SysuiTestCase() {
        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
        val list = registry.getClocks()
        assertEquals(
            list,
            listOf(
            list.toSet(),
            setOf(
                ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
                ClockMetadata("clock_1", "clock 1"),
                ClockMetadata("clock_2", "clock 2"),
@@ -187,8 +187,8 @@ class ClockRegistryTest : SysuiTestCase() {
        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
        val list = registry.getClocks()
        assertEquals(
            list,
            listOf(
            list.toSet(),
            setOf(
                ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
                ClockMetadata("clock_1", "clock 1"),
                ClockMetadata("clock_2", "clock 2")
@@ -293,6 +293,53 @@ class ClockRegistryTest : SysuiTestCase() {
        assertEquals(4, listChangeCallCount)
    }

    @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 plugin2 = FakeClockPlugin().addClock("clock_2", "clock 2")
        val plugin3 = FakeClockPlugin().addClock("clock_3", "clock 3")
        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)

        // Set the current clock to the final clock to load
        registry.applySettings(ClockSettings("clock_4", null))
        scheduler.runCurrent()

        // When ClockRegistry attempts to unload a plugin, we at that point decide to load and
        // 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)
        }

        // Load initial plugins
        pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
        pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
        pluginListener.onPluginLoaded(plugin3, mockContext, mockPluginLifecycle3)

        // Repeatedly verify the loaded providers to get final state
        registry.verifyLoadedProviders()
        scheduler.runCurrent()
        registry.verifyLoadedProviders()
        scheduler.runCurrent()

        // Verify all plugins were correctly loaded into the registry
        assertEquals(registry.getClocks().toSet(), setOf(
            ClockMetadata("DEFAULT", "Default Clock"),
            ClockMetadata("clock_2", "clock 2"),
            ClockMetadata("clock_3", "clock 3"),
            ClockMetadata("clock_4", "clock 4")
        ))
    }

    @Test
    fun jsonDeserialization_gotExpectedObject() {