Setting for Lockscreen Clock

Bug: 229771520
Test: Automated
Change-Id: Ia5b4dfe38d48a41cbfb59109c819647b9e8398cf
(cherry picked from commit e3004a6e5f2e8ef2e70df41bf965ed9be7b1675e)
Merged-In: Ia5b4dfe38d48a41cbfb59109c819647b9e8398cf
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 9f790c6..114ea65 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@
         "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 209b576..3245966 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<ClockProviderPlugin> {
+    private val gson = Gson()
+    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+    private val clockChangeListeners = mutableListOf<ClockChangeListener>()
+    private val settingObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
+            clockChangeListeners.forEach { it() }
+    }
+
+    private val pluginListener = object : PluginListener<ClockProviderPlugin> {
         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 @@
                 }
 
                 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<ClockId, ClockInfo>()
+    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<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
@@ -66,8 +111,14 @@
 
     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 @@
     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 5d3c575..aaa2357 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 @@
     @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<ClockProviderPlugin>
     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 @@
         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 @@
 
     @Before
     fun setUp() {
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+
         val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
-        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 @@
     @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 @@
         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 @@
         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)
+    }
 }