From 81d56df8fa00d616cef66c490fdeac2f1de7255a Mon Sep 17 00:00:00 2001 From: Hawkwood Glazier Date: Tue, 10 May 2022 20:16:21 +0000 Subject: [PATCH] Setting for Lockscreen Clock Bug: 229771520 Test: Automated Change-Id: Ia5b4dfe38d48a41cbfb59109c819647b9e8398cf (cherry picked from commit e3004a6e5f2e8ef2e70df41bf965ed9be7b1675e) Merged-In: Ia5b4dfe38d48a41cbfb59109c819647b9e8398cf --- packages/SystemUI/shared/Android.bp | 1 + .../systemui/shared/clocks/ClockRegistry.kt | 76 ++++++++++-- .../shared/clocks/ClockRegistryTest.kt | 108 +++++++++++++++--- 3 files changed, 157 insertions(+), 28 deletions(-) diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 9f790c66302dd..114ea657a7583 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -50,6 +50,7 @@ android_library { "SystemUIUnfoldLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", + "gson-prebuilt-jar", "dagger2", "jsr330", ], diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 209b5761b0049..32459665e6cf5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -14,24 +14,42 @@ package com.android.systemui.shared.clocks import android.content.Context +import android.database.ContentObserver import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings import android.util.Log +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.PluginListener import com.android.systemui.shared.plugins.PluginManager +import com.google.gson.Gson import javax.inject.Inject private val TAG = ClockRegistry::class.simpleName -private const val DEFAULT_CLOCK_ID = "DEFAULT" +private val DEBUG = true +const val DEFAULT_CLOCK_ID = "DEFAULT" typealias ClockChangeListener = () -> Unit /** ClockRegistry aggregates providers and plugins */ -class ClockRegistry @Inject constructor( +open class ClockRegistry @Inject constructor( val context: Context, - val pluginManager: PluginManager + val pluginManager: PluginManager, + @Main val handler: Handler ) { - val pluginListener = object : PluginListener { + private val gson = Gson() + private val availableClocks = mutableMapOf() + private val clockChangeListeners = mutableListOf() + private val settingObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uris: Collection, flags: Int, userId: Int) = + clockChangeListeners.forEach { it() } + } + + private val pluginListener = object : PluginListener { override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) { + val currentId = currentClockId for (clock in plugin.getClocks()) { val id = clock.clockId val current = availableClocks[id] @@ -42,21 +60,48 @@ class ClockRegistry @Inject constructor( } availableClocks[id] = ClockInfo(clock, plugin) + + if (currentId == id) { + if (DEBUG) { + Log.i(TAG, "Current clock ($currentId) was connected") + } + clockChangeListeners.forEach { it() } + } } } override fun onPluginDisconnected(plugin: ClockProviderPlugin) { + val currentId = currentClockId for (clock in plugin.getClocks()) { availableClocks.remove(clock.clockId) + + if (currentId == clock.clockId) { + Log.w(TAG, "Current clock ($currentId) was disconnected") + clockChangeListeners.forEach { it() } + } } } } - private val availableClocks = mutableMapOf() + open var currentClockId: ClockId + get() { + val json = Settings.Secure.getString(context.contentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE) + return gson.fromJson(json, ClockSetting::class.java).clockId + } + set(value) { + val json = gson.toJson(ClockSetting(value, System.currentTimeMillis())) + Settings.Secure.putString(context.contentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json) + } init { pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java) - // TODO: Register Settings ContentObserver + context.contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), + false, + settingObserver, + UserHandle.USER_ALL) } fun getClocks(): List = availableClocks.map { (_, clock) -> clock.metadata } @@ -66,8 +111,14 @@ class ClockRegistry @Inject constructor( fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId) - fun getCurrentClock(): Clock { - val clockId = "" // TODO: Load setting + fun registerClockChangeListener(listener: ClockChangeListener) = + clockChangeListeners.add(listener) + + fun unregisterClockChangeListener(listener: ClockChangeListener) = + clockChangeListeners.remove(listener) + + fun createCurrentClock(): Clock { + val clockId = currentClockId if (!clockId.isNullOrEmpty()) { val clock = createClock(clockId) if (clock != null) { @@ -83,12 +134,13 @@ class ClockRegistry @Inject constructor( private fun createClock(clockId: ClockId): Clock? = availableClocks[clockId]?.provider?.createClock(clockId) - fun setCurrentClock(clockId: ClockId) { - // TODO: Write Setting - } - private data class ClockInfo( val metadata: ClockMetadata, val provider: ClockProvider ) + + private data class ClockSetting( + val clockId: ClockId, + val _applied_timestamp: Long + ) } \ No newline at end of file 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 5d3c57511631c..aaa2357a1c520 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,8 +15,11 @@ */ package com.android.systemui.shared.clocks +import org.mockito.Mockito.`when` as whenever import android.content.Context +import android.content.ContentResolver import android.graphics.drawable.Drawable +import android.os.Handler import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -43,17 +46,23 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockPluginManager: PluginManager @Mock private lateinit var mockClock: Clock @Mock private lateinit var mockThumbnail: Drawable + @Mock private lateinit var mockHandler: Handler + @Mock private lateinit var mockContentResolver: ContentResolver private lateinit var pluginListener: PluginListener private lateinit var registry: ClockRegistry - private fun failFactory(): Clock { - fail("Unexpected call to createClock") - return null!! - } + private var settingValue: String = "" - private fun failThumbnail(): Drawable? { - fail("Unexpected call to getThumbnail") - return null + companion object { + private fun failFactory(): Clock { + fail("Unexpected call to createClock") + return null!! + } + + private fun failThumbnail(): Drawable? { + fail("Unexpected call to getThumbnail") + return null + } } private class FakeClockPlugin : ClockProviderPlugin { @@ -68,8 +77,8 @@ class ClockRegistryTest : SysuiTestCase() { fun addClock( id: ClockId, name: String, - create: () -> Clock, - getThumbnail: () -> Drawable? + create: () -> Clock = ::failFactory, + getThumbnail: () -> Drawable? = ::failThumbnail ) { metadata.add(ClockMetadata(id, name)) createCallbacks[id] = create @@ -79,8 +88,14 @@ class ClockRegistryTest : SysuiTestCase() { @Before fun setUp() { + whenever(mockContext.contentResolver).thenReturn(mockContentResolver) + val captor = argumentCaptor>() - registry = ClockRegistry(mockContext, mockPluginManager) + registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) { + override var currentClockId: ClockId + get() = settingValue + set(value) { settingValue = value } + } verify(mockPluginManager).addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java)) pluginListener = captor.value @@ -89,12 +104,12 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun pluginRegistration_CorrectState() { val plugin1 = FakeClockPlugin() - plugin1.addClock("clock_1", "clock 1", ::failFactory, ::failThumbnail) - plugin1.addClock("clock_2", "clock 2", ::failFactory, ::failThumbnail) + plugin1.addClock("clock_1", "clock 1") + plugin1.addClock("clock_2", "clock 2") val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_3", "clock 3", ::failFactory, ::failThumbnail) - plugin2.addClock("clock_4", "clock 4", ::failFactory, ::failThumbnail) + plugin2.addClock("clock_3", "clock 3") + plugin2.addClock("clock_4", "clock 4") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) @@ -114,8 +129,8 @@ class ClockRegistryTest : SysuiTestCase() { plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_1", "clock 1", ::failFactory, ::failThumbnail) - plugin2.addClock("clock_2", "clock 2", ::failFactory, ::failThumbnail) + plugin2.addClock("clock_1", "clock 1") + plugin2.addClock("clock_2", "clock 2") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) @@ -130,4 +145,65 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail) assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail) } + + @Test + fun createCurrentClock_pluginConnected() { + val plugin1 = FakeClockPlugin() + plugin1.addClock("clock_1", "clock 1") + plugin1.addClock("clock_2", "clock 2") + + settingValue = "clock_3" + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3", { mockClock }) + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + + val clock = registry.createCurrentClock() + assertEquals(clock, mockClock) + } + + @Test + fun createDefaultClock_pluginDisconnected() { + val plugin1 = FakeClockPlugin() + plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock }) + plugin1.addClock("clock_2", "clock 2") + + settingValue = "clock_3" + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3") + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + pluginListener.onPluginDisconnected(plugin2) + + val clock = registry.createCurrentClock() + assertEquals(clock, mockClock) + } + + @Test + fun pluginRemoved_clockChanged() { + val plugin1 = FakeClockPlugin() + plugin1.addClock("clock_1", "clock 1") + plugin1.addClock("clock_2", "clock 2") + + settingValue = "clock_3" + val plugin2 = FakeClockPlugin() + plugin2.addClock("clock_3", "clock 3", { mockClock }) + plugin2.addClock("clock_4", "clock 4") + + pluginListener.onPluginConnected(plugin1, mockContext) + pluginListener.onPluginConnected(plugin2, mockContext) + + var changeCallCount = 0 + registry.registerClockChangeListener({ changeCallCount++ }) + + pluginListener.onPluginDisconnected(plugin1) + assertEquals(0, changeCallCount) + + pluginListener.onPluginDisconnected(plugin2) + assertEquals(1, changeCallCount) + } }