Loading packages/SystemUI/shared/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ android_library { "SystemUIUnfoldLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", "gson-prebuilt-jar", "dagger2", "jsr330", ], Loading packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +64 −12 Original line number Diff line number Diff line Loading @@ -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] Loading @@ -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<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 } Loading @@ -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) { Loading @@ -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 packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +92 −16 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -43,9 +46,14 @@ 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<ClockProviderPlugin> private lateinit var registry: ClockRegistry private var settingValue: String = "" companion object { private fun failFactory(): Clock { fail("Unexpected call to createClock") return null!! Loading @@ -55,6 +63,7 @@ class ClockRegistryTest : SysuiTestCase() { fail("Unexpected call to getThumbnail") return null } } private class FakeClockPlugin : ClockProviderPlugin { private val metadata = mutableListOf<ClockMetadata>() Loading @@ -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 Loading @@ -79,8 +88,14 @@ class ClockRegistryTest : SysuiTestCase() { @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 Loading @@ -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) Loading @@ -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) Loading @@ -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) } } Loading
packages/SystemUI/shared/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ android_library { "SystemUIUnfoldLib", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", "gson-prebuilt-jar", "dagger2", "jsr330", ], Loading
packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +64 −12 Original line number Diff line number Diff line Loading @@ -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] Loading @@ -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<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 } Loading @@ -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) { Loading @@ -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
packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +92 −16 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -43,9 +46,14 @@ 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<ClockProviderPlugin> private lateinit var registry: ClockRegistry private var settingValue: String = "" companion object { private fun failFactory(): Clock { fail("Unexpected call to createClock") return null!! Loading @@ -55,6 +63,7 @@ class ClockRegistryTest : SysuiTestCase() { fail("Unexpected call to getThumbnail") return null } } private class FakeClockPlugin : ClockProviderPlugin { private val metadata = mutableListOf<ClockMetadata>() Loading @@ -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 Loading @@ -79,8 +88,14 @@ class ClockRegistryTest : SysuiTestCase() { @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 Loading @@ -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) Loading @@ -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) Loading @@ -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) } }