Loading packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +165 −60 Original line number Diff line number Diff line Loading @@ -13,11 +13,13 @@ */ package com.android.systemui.shared.clocks import android.app.ActivityManager import android.app.UserSwitchObserver 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 androidx.annotation.OpenForTesting Loading @@ -29,17 +31,23 @@ import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.ClockSettings import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.Assert import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch private val TAG = ClockRegistry::class.simpleName private val TAG = ClockRegistry::class.simpleName!! private const val DEBUG = true /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( val context: Context, val pluginManager: PluginManager, val handler: Handler, val scope: CoroutineScope, val mainDispatcher: CoroutineDispatcher, val bgDispatcher: CoroutineDispatcher, val isEnabled: Boolean, userHandle: Int, val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, ) { Loading @@ -50,12 +58,20 @@ open class ClockRegistry( 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.onClockChanged() } private val settingObserver = object : ContentObserver(null) { override fun onChange( selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int ) { scope.launch(bgDispatcher) { querySettings() } } } private val pluginListener = object : PluginListener<ClockProviderPlugin> { private val pluginListener = object : PluginListener<ClockProviderPlugin> { override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) = connectClocks(plugin) Loading @@ -63,53 +79,111 @@ open class ClockRegistry( disconnectClocks(plugin) } open var settings: ClockSettings? get() { private val userSwitchObserver = object : UserSwitchObserver() { override fun onUserSwitchComplete(newUserId: Int) { scope.launch(bgDispatcher) { querySettings() } } } // TODO(b/267372164): Migrate to flows var settings: ClockSettings? = null get() = field protected set(value) { if (field != value) { field = value scope.launch(mainDispatcher) { onClockChanged() } } } var isRegistered: Boolean = false private set @OpenForTesting open fun querySettings() { assertNotMainThread() val result = try { val json = Settings.Secure.getString( val json = if (handleAllUsers) { Settings.Secure.getStringForUser( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, ActivityManager.getCurrentUser() ) } else { Settings.Secure.getString( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE ) if (json == null || json.isEmpty()) { return null } return ClockSettings.deserialize(json) ClockSettings.deserialize(json) } catch (ex: Exception) { Log.e(TAG, "Failed to parse clock settings", ex) return null null } settings = result } protected set(value) { @OpenForTesting open fun applySettings(value: ClockSettings?) { assertNotMainThread() try { val json = if (value != null) { value._applied_timestamp = System.currentTimeMillis() ClockSettings.serialize(value) } else { "" } value?._applied_timestamp = System.currentTimeMillis() val json = ClockSettings.serialize(value) if (handleAllUsers) { Settings.Secure.putStringForUser( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json, ActivityManager.getCurrentUser() ) } else { Settings.Secure.putString( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json ) } } catch (ex: Exception) { Log.e(TAG, "Failed to set clock settings", ex) } settings = value } private fun mutateSetting(mutator: (ClockSettings) -> Unit) { val settings = this.settings ?: ClockSettings() mutator(settings) this.settings = settings @OpenForTesting protected open fun assertMainThread() { Assert.isMainThread() } @OpenForTesting protected open fun assertNotMainThread() { Assert.isNotMainThread() } private fun onClockChanged() { assertMainThread() clockChangeListeners.forEach { it.onClockChanged() } } private fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) } } var currentClockId: ClockId get() = settings?.clockId ?: fallbackClockId set(value) { mutateSetting { it.clockId = value } } set(value) { mutateSetting { it.copy(clockId = value) } } var seedColor: Int? get() = settings?.seedColor set(value) { mutateSetting { it.seedColor = value } } set(value) { mutateSetting { it.copy(seedColor = value) } } init { connectClocks(defaultClockProvider) Loading @@ -118,19 +192,51 @@ open class ClockRegistry( "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID" ) } } fun registerListeners() { if (!isEnabled || isRegistered) { return } isRegistered = true if (isEnabled) { pluginManager.addPluginListener( pluginListener, ClockProviderPlugin::class.java, /*allowMultiple=*/ true ) scope.launch(bgDispatcher) { querySettings() } if (handleAllUsers) { context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), /*notifyForDescendants=*/ false, settingObserver, userHandle UserHandle.USER_ALL ) ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG) } else { context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), /*notifyForDescendants=*/ false, settingObserver ) } } fun unregisterListeners() { if (!isRegistered) { return } isRegistered = false pluginManager.removePluginListener(pluginListener) context.contentResolver.unregisterContentObserver(settingObserver) if (handleAllUsers) { ActivityManager.getService().unregisterUserSwitchObserver(userSwitchObserver) } } Loading @@ -157,7 +263,7 @@ open class ClockRegistry( if (DEBUG) { Log.i(TAG, "Current clock ($currentId) was connected") } clockChangeListeners.forEach { it.onClockChanged() } onClockChanged() } } } Loading @@ -172,13 +278,12 @@ open class ClockRegistry( if (currentId == clock.clockId) { Log.w(TAG, "Current clock ($currentId) was disconnected") clockChangeListeners.forEach { it.onClockChanged() } onClockChanged() } } } @OpenForTesting open fun getClocks(): List<ClockMetadata> { fun getClocks(): List<ClockMetadata> { if (!isEnabled) { return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata) } Loading Loading @@ -213,16 +318,16 @@ open class ClockRegistry( return createClock(DEFAULT_CLOCK_ID)!! } private fun createClock(clockId: ClockId): ClockController? { val settings = this.settings ?: ClockSettings() if (clockId != settings.clockId) { settings.clockId = clockId private fun createClock(targetClockId: ClockId): ClockController? { var settings = this.settings ?: ClockSettings() if (targetClockId != settings.clockId) { settings = settings.copy(clockId = targetClockId) } return availableClocks[clockId]?.provider?.createClock(settings) return availableClocks[targetClockId]?.provider?.createClock(settings) } private data class ClockInfo( val metadata: ClockMetadata, val provider: ClockProvider val provider: ClockProvider, ) } packages/SystemUI/src/com/android/systemui/util/Assert.java→packages/SystemUI/customization/src/com/android/systemui/util/Assert.java +0 −0 File moved. View file packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +24 −11 Original line number Diff line number Diff line Loading @@ -45,7 +45,7 @@ interface ClockProvider { /** Initializes and returns the target clock design */ @Deprecated("Use overload with ClockSettings") fun createClock(id: ClockId): ClockController { return createClock(ClockSettings(id, null, null)) return createClock(ClockSettings(id, null)) } /** Initializes and returns the target clock design */ Loading Loading @@ -186,16 +186,21 @@ data class ClockMetadata( /** Structure for keeping clock-specific settings */ @Keep data class ClockSettings( var clockId: ClockId? = null, var seedColor: Int? = null, var _applied_timestamp: Long? = null, val clockId: ClockId? = null, val seedColor: Int? = null, ) { var _applied_timestamp: Long? = null companion object { private val KEY_CLOCK_ID = "clockId" private val KEY_SEED_COLOR = "seedColor" private val KEY_TIMESTAMP = "_applied_timestamp" fun serialize(setting: ClockSettings): String { fun serialize(setting: ClockSettings?): String { if (setting == null) { return "" } return JSONObject() .put(KEY_CLOCK_ID, setting.clockId) .put(KEY_SEED_COLOR, setting.seedColor) Loading @@ -203,13 +208,21 @@ data class ClockSettings( .toString() } fun deserialize(jsonStr: String): ClockSettings { fun deserialize(jsonStr: String?): ClockSettings? { if (jsonStr.isNullOrEmpty()) { return null } val json = JSONObject(jsonStr) return ClockSettings( val result = ClockSettings( json.getString(KEY_CLOCK_ID), if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null, if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null ) if (!json.isNull(KEY_TIMESTAMP)) { result._applied_timestamp = json.getLong(KEY_TIMESTAMP) } return result } } } packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +13 −6 Original line number Diff line number Diff line Loading @@ -18,13 +18,12 @@ package com.android.keyguard.dagger; import android.content.Context; import android.content.res.Resources; import android.os.Handler; import android.os.UserHandle; import android.view.LayoutInflater; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; Loading @@ -34,6 +33,8 @@ import com.android.systemui.shared.clocks.DefaultClockProvider; import dagger.Module; import dagger.Provides; import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.CoroutineScope; /** Dagger Module for clocks. */ @Module Loading @@ -44,17 +45,23 @@ public abstract class ClockRegistryModule { public static ClockRegistry getClockRegistry( @Application Context context, PluginManager pluginManager, @Main Handler handler, @Application CoroutineScope scope, @Main CoroutineDispatcher mainDispatcher, @Background CoroutineDispatcher bgDispatcher, FeatureFlags featureFlags, @Main Resources resources, LayoutInflater layoutInflater) { return new ClockRegistry( ClockRegistry registry = new ClockRegistry( context, pluginManager, handler, scope, mainDispatcher, bgDispatcher, featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), UserHandle.USER_ALL, /* handleAllUsers= */ true, new DefaultClockProvider(context, layoutInflater, resources), context.getString(R.string.lockscreen_clock_id_fallback)); registry.registerListeners(); return registry; } } packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +33 −21 Original line number Diff line number Diff line Loading @@ -18,8 +18,6 @@ package com.android.systemui.shared.clocks import android.content.ContentResolver import android.content.Context import android.graphics.drawable.Drawable import android.os.Handler import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading @@ -34,6 +32,9 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import junit.framework.Assert.assertEquals import junit.framework.Assert.fail import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import org.json.JSONException import org.junit.Before import org.junit.Rule Loading @@ -49,19 +50,19 @@ import org.mockito.junit.MockitoJUnit class ClockRegistryTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var dispatcher: CoroutineDispatcher private lateinit var scope: TestScope @Mock private lateinit var mockContext: Context @Mock private lateinit var mockPluginManager: PluginManager @Mock private lateinit var mockClock: ClockController @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockHandler: Handler @Mock private lateinit var mockContentResolver: ContentResolver private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry private var settingValue: ClockSettings? = null companion object { private fun failFactory(): ClockController { fail("Unexpected call to createClock") Loading Loading @@ -99,6 +100,9 @@ class ClockRegistryTest : SysuiTestCase() { @Before fun setUp() { dispatcher = StandardTestDispatcher() scope = TestScope(dispatcher) fakeDefaultProvider = FakeClockPlugin() .addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail }) whenever(mockContext.contentResolver).thenReturn(mockContentResolver) Loading @@ -107,15 +111,22 @@ class ClockRegistryTest : SysuiTestCase() { registry = object : ClockRegistry( mockContext, mockPluginManager, mockHandler, scope = scope.backgroundScope, mainDispatcher = dispatcher, bgDispatcher = dispatcher, isEnabled = true, userHandle = UserHandle.USER_ALL, defaultClockProvider = fakeDefaultProvider handleAllUsers = true, defaultClockProvider = fakeDefaultProvider, ) { override var settings: ClockSettings? get() = settingValue set(value) { settingValue = value } override fun querySettings() { } override fun applySettings(value: ClockSettings?) { settings = value } // Unit Test does not validate threading override fun assertMainThread() {} override fun assertNotMainThread() {} } registry.registerListeners() verify(mockPluginManager) .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true)) Loading Loading @@ -187,16 +198,16 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") settingValue = ClockSettings("clock_3", null, null) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) val clock = registry.createCurrentClock() assertEquals(clock, mockClock) assertEquals(mockClock, clock) } @Test Loading @@ -205,11 +216,11 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") settingValue = ClockSettings("clock_3", null, null) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) pluginListener.onPluginDisconnected(plugin2) Loading @@ -224,11 +235,11 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") settingValue = ClockSettings("clock_3", null, null) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) Loading @@ -244,7 +255,7 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonDeserialization_gotExpectedObject() { val expected = ClockSettings("ID", null, 500) val expected = ClockSettings("ID", null).apply { _applied_timestamp = 500 } val actual = ClockSettings.deserialize("""{ "clockId":"ID", "_applied_timestamp":500 Loading @@ -254,14 +265,14 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonDeserialization_noTimestamp_gotExpectedObject() { val expected = ClockSettings("ID", null, null) val expected = ClockSettings("ID", null) val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}") assertEquals(expected, actual) } @Test fun jsonDeserialization_nullTimestamp_gotExpectedObject() { val expected = ClockSettings("ID", null, null) val expected = ClockSettings("ID", null) val actual = ClockSettings.deserialize("""{ "clockId":"ID", "_applied_timestamp":null Loading @@ -271,7 +282,7 @@ class ClockRegistryTest : SysuiTestCase() { @Test(expected = JSONException::class) fun jsonDeserialization_noId_threwException() { val expected = ClockSettings("ID", null, 500) val expected = ClockSettings(null, null).apply { _applied_timestamp = 500 } val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}") assertEquals(expected, actual) } Loading @@ -279,14 +290,15 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonSerialization_gotExpectedString() { val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}" val actual = ClockSettings.serialize(ClockSettings("ID", null, 500)) val actual = ClockSettings.serialize(ClockSettings("ID", null) .apply { _applied_timestamp = 500 }) assertEquals(expected, actual) } @Test fun jsonSerialization_noTimestamp_gotExpectedString() { val expected = "{\"clockId\":\"ID\"}" val actual = ClockSettings.serialize(ClockSettings("ID", null, null)) val actual = ClockSettings.serialize(ClockSettings("ID", null)) assertEquals(expected, actual) } } Loading
packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +165 −60 Original line number Diff line number Diff line Loading @@ -13,11 +13,13 @@ */ package com.android.systemui.shared.clocks import android.app.ActivityManager import android.app.UserSwitchObserver 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 androidx.annotation.OpenForTesting Loading @@ -29,17 +31,23 @@ import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.ClockSettings import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.Assert import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch private val TAG = ClockRegistry::class.simpleName private val TAG = ClockRegistry::class.simpleName!! private const val DEBUG = true /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( val context: Context, val pluginManager: PluginManager, val handler: Handler, val scope: CoroutineScope, val mainDispatcher: CoroutineDispatcher, val bgDispatcher: CoroutineDispatcher, val isEnabled: Boolean, userHandle: Int, val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, ) { Loading @@ -50,12 +58,20 @@ open class ClockRegistry( 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.onClockChanged() } private val settingObserver = object : ContentObserver(null) { override fun onChange( selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int ) { scope.launch(bgDispatcher) { querySettings() } } } private val pluginListener = object : PluginListener<ClockProviderPlugin> { private val pluginListener = object : PluginListener<ClockProviderPlugin> { override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) = connectClocks(plugin) Loading @@ -63,53 +79,111 @@ open class ClockRegistry( disconnectClocks(plugin) } open var settings: ClockSettings? get() { private val userSwitchObserver = object : UserSwitchObserver() { override fun onUserSwitchComplete(newUserId: Int) { scope.launch(bgDispatcher) { querySettings() } } } // TODO(b/267372164): Migrate to flows var settings: ClockSettings? = null get() = field protected set(value) { if (field != value) { field = value scope.launch(mainDispatcher) { onClockChanged() } } } var isRegistered: Boolean = false private set @OpenForTesting open fun querySettings() { assertNotMainThread() val result = try { val json = Settings.Secure.getString( val json = if (handleAllUsers) { Settings.Secure.getStringForUser( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, ActivityManager.getCurrentUser() ) } else { Settings.Secure.getString( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE ) if (json == null || json.isEmpty()) { return null } return ClockSettings.deserialize(json) ClockSettings.deserialize(json) } catch (ex: Exception) { Log.e(TAG, "Failed to parse clock settings", ex) return null null } settings = result } protected set(value) { @OpenForTesting open fun applySettings(value: ClockSettings?) { assertNotMainThread() try { val json = if (value != null) { value._applied_timestamp = System.currentTimeMillis() ClockSettings.serialize(value) } else { "" } value?._applied_timestamp = System.currentTimeMillis() val json = ClockSettings.serialize(value) if (handleAllUsers) { Settings.Secure.putStringForUser( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json, ActivityManager.getCurrentUser() ) } else { Settings.Secure.putString( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json ) } } catch (ex: Exception) { Log.e(TAG, "Failed to set clock settings", ex) } settings = value } private fun mutateSetting(mutator: (ClockSettings) -> Unit) { val settings = this.settings ?: ClockSettings() mutator(settings) this.settings = settings @OpenForTesting protected open fun assertMainThread() { Assert.isMainThread() } @OpenForTesting protected open fun assertNotMainThread() { Assert.isNotMainThread() } private fun onClockChanged() { assertMainThread() clockChangeListeners.forEach { it.onClockChanged() } } private fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) { scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) } } var currentClockId: ClockId get() = settings?.clockId ?: fallbackClockId set(value) { mutateSetting { it.clockId = value } } set(value) { mutateSetting { it.copy(clockId = value) } } var seedColor: Int? get() = settings?.seedColor set(value) { mutateSetting { it.seedColor = value } } set(value) { mutateSetting { it.copy(seedColor = value) } } init { connectClocks(defaultClockProvider) Loading @@ -118,19 +192,51 @@ open class ClockRegistry( "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID" ) } } fun registerListeners() { if (!isEnabled || isRegistered) { return } isRegistered = true if (isEnabled) { pluginManager.addPluginListener( pluginListener, ClockProviderPlugin::class.java, /*allowMultiple=*/ true ) scope.launch(bgDispatcher) { querySettings() } if (handleAllUsers) { context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), /*notifyForDescendants=*/ false, settingObserver, userHandle UserHandle.USER_ALL ) ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG) } else { context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), /*notifyForDescendants=*/ false, settingObserver ) } } fun unregisterListeners() { if (!isRegistered) { return } isRegistered = false pluginManager.removePluginListener(pluginListener) context.contentResolver.unregisterContentObserver(settingObserver) if (handleAllUsers) { ActivityManager.getService().unregisterUserSwitchObserver(userSwitchObserver) } } Loading @@ -157,7 +263,7 @@ open class ClockRegistry( if (DEBUG) { Log.i(TAG, "Current clock ($currentId) was connected") } clockChangeListeners.forEach { it.onClockChanged() } onClockChanged() } } } Loading @@ -172,13 +278,12 @@ open class ClockRegistry( if (currentId == clock.clockId) { Log.w(TAG, "Current clock ($currentId) was disconnected") clockChangeListeners.forEach { it.onClockChanged() } onClockChanged() } } } @OpenForTesting open fun getClocks(): List<ClockMetadata> { fun getClocks(): List<ClockMetadata> { if (!isEnabled) { return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata) } Loading Loading @@ -213,16 +318,16 @@ open class ClockRegistry( return createClock(DEFAULT_CLOCK_ID)!! } private fun createClock(clockId: ClockId): ClockController? { val settings = this.settings ?: ClockSettings() if (clockId != settings.clockId) { settings.clockId = clockId private fun createClock(targetClockId: ClockId): ClockController? { var settings = this.settings ?: ClockSettings() if (targetClockId != settings.clockId) { settings = settings.copy(clockId = targetClockId) } return availableClocks[clockId]?.provider?.createClock(settings) return availableClocks[targetClockId]?.provider?.createClock(settings) } private data class ClockInfo( val metadata: ClockMetadata, val provider: ClockProvider val provider: ClockProvider, ) }
packages/SystemUI/src/com/android/systemui/util/Assert.java→packages/SystemUI/customization/src/com/android/systemui/util/Assert.java +0 −0 File moved. View file
packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +24 −11 Original line number Diff line number Diff line Loading @@ -45,7 +45,7 @@ interface ClockProvider { /** Initializes and returns the target clock design */ @Deprecated("Use overload with ClockSettings") fun createClock(id: ClockId): ClockController { return createClock(ClockSettings(id, null, null)) return createClock(ClockSettings(id, null)) } /** Initializes and returns the target clock design */ Loading Loading @@ -186,16 +186,21 @@ data class ClockMetadata( /** Structure for keeping clock-specific settings */ @Keep data class ClockSettings( var clockId: ClockId? = null, var seedColor: Int? = null, var _applied_timestamp: Long? = null, val clockId: ClockId? = null, val seedColor: Int? = null, ) { var _applied_timestamp: Long? = null companion object { private val KEY_CLOCK_ID = "clockId" private val KEY_SEED_COLOR = "seedColor" private val KEY_TIMESTAMP = "_applied_timestamp" fun serialize(setting: ClockSettings): String { fun serialize(setting: ClockSettings?): String { if (setting == null) { return "" } return JSONObject() .put(KEY_CLOCK_ID, setting.clockId) .put(KEY_SEED_COLOR, setting.seedColor) Loading @@ -203,13 +208,21 @@ data class ClockSettings( .toString() } fun deserialize(jsonStr: String): ClockSettings { fun deserialize(jsonStr: String?): ClockSettings? { if (jsonStr.isNullOrEmpty()) { return null } val json = JSONObject(jsonStr) return ClockSettings( val result = ClockSettings( json.getString(KEY_CLOCK_ID), if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null, if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null ) if (!json.isNull(KEY_TIMESTAMP)) { result._applied_timestamp = json.getLong(KEY_TIMESTAMP) } return result } } }
packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +13 −6 Original line number Diff line number Diff line Loading @@ -18,13 +18,12 @@ package com.android.keyguard.dagger; import android.content.Context; import android.content.res.Resources; import android.os.Handler; import android.os.UserHandle; import android.view.LayoutInflater; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; Loading @@ -34,6 +33,8 @@ import com.android.systemui.shared.clocks.DefaultClockProvider; import dagger.Module; import dagger.Provides; import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.CoroutineScope; /** Dagger Module for clocks. */ @Module Loading @@ -44,17 +45,23 @@ public abstract class ClockRegistryModule { public static ClockRegistry getClockRegistry( @Application Context context, PluginManager pluginManager, @Main Handler handler, @Application CoroutineScope scope, @Main CoroutineDispatcher mainDispatcher, @Background CoroutineDispatcher bgDispatcher, FeatureFlags featureFlags, @Main Resources resources, LayoutInflater layoutInflater) { return new ClockRegistry( ClockRegistry registry = new ClockRegistry( context, pluginManager, handler, scope, mainDispatcher, bgDispatcher, featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), UserHandle.USER_ALL, /* handleAllUsers= */ true, new DefaultClockProvider(context, layoutInflater, resources), context.getString(R.string.lockscreen_clock_id_fallback)); registry.registerListeners(); return registry; } }
packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +33 −21 Original line number Diff line number Diff line Loading @@ -18,8 +18,6 @@ package com.android.systemui.shared.clocks import android.content.ContentResolver import android.content.Context import android.graphics.drawable.Drawable import android.os.Handler import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading @@ -34,6 +32,9 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import junit.framework.Assert.assertEquals import junit.framework.Assert.fail import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import org.json.JSONException import org.junit.Before import org.junit.Rule Loading @@ -49,19 +50,19 @@ import org.mockito.junit.MockitoJUnit class ClockRegistryTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var dispatcher: CoroutineDispatcher private lateinit var scope: TestScope @Mock private lateinit var mockContext: Context @Mock private lateinit var mockPluginManager: PluginManager @Mock private lateinit var mockClock: ClockController @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockHandler: Handler @Mock private lateinit var mockContentResolver: ContentResolver private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry private var settingValue: ClockSettings? = null companion object { private fun failFactory(): ClockController { fail("Unexpected call to createClock") Loading Loading @@ -99,6 +100,9 @@ class ClockRegistryTest : SysuiTestCase() { @Before fun setUp() { dispatcher = StandardTestDispatcher() scope = TestScope(dispatcher) fakeDefaultProvider = FakeClockPlugin() .addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail }) whenever(mockContext.contentResolver).thenReturn(mockContentResolver) Loading @@ -107,15 +111,22 @@ class ClockRegistryTest : SysuiTestCase() { registry = object : ClockRegistry( mockContext, mockPluginManager, mockHandler, scope = scope.backgroundScope, mainDispatcher = dispatcher, bgDispatcher = dispatcher, isEnabled = true, userHandle = UserHandle.USER_ALL, defaultClockProvider = fakeDefaultProvider handleAllUsers = true, defaultClockProvider = fakeDefaultProvider, ) { override var settings: ClockSettings? get() = settingValue set(value) { settingValue = value } override fun querySettings() { } override fun applySettings(value: ClockSettings?) { settings = value } // Unit Test does not validate threading override fun assertMainThread() {} override fun assertNotMainThread() {} } registry.registerListeners() verify(mockPluginManager) .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true)) Loading Loading @@ -187,16 +198,16 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") settingValue = ClockSettings("clock_3", null, null) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) val clock = registry.createCurrentClock() assertEquals(clock, mockClock) assertEquals(mockClock, clock) } @Test Loading @@ -205,11 +216,11 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") settingValue = ClockSettings("clock_3", null, null) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) pluginListener.onPluginDisconnected(plugin2) Loading @@ -224,11 +235,11 @@ class ClockRegistryTest : SysuiTestCase() { .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") settingValue = ClockSettings("clock_3", null, null) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") registry.applySettings(ClockSettings("clock_3", null)) pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) Loading @@ -244,7 +255,7 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonDeserialization_gotExpectedObject() { val expected = ClockSettings("ID", null, 500) val expected = ClockSettings("ID", null).apply { _applied_timestamp = 500 } val actual = ClockSettings.deserialize("""{ "clockId":"ID", "_applied_timestamp":500 Loading @@ -254,14 +265,14 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonDeserialization_noTimestamp_gotExpectedObject() { val expected = ClockSettings("ID", null, null) val expected = ClockSettings("ID", null) val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}") assertEquals(expected, actual) } @Test fun jsonDeserialization_nullTimestamp_gotExpectedObject() { val expected = ClockSettings("ID", null, null) val expected = ClockSettings("ID", null) val actual = ClockSettings.deserialize("""{ "clockId":"ID", "_applied_timestamp":null Loading @@ -271,7 +282,7 @@ class ClockRegistryTest : SysuiTestCase() { @Test(expected = JSONException::class) fun jsonDeserialization_noId_threwException() { val expected = ClockSettings("ID", null, 500) val expected = ClockSettings(null, null).apply { _applied_timestamp = 500 } val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}") assertEquals(expected, actual) } Loading @@ -279,14 +290,15 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun jsonSerialization_gotExpectedString() { val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}" val actual = ClockSettings.serialize(ClockSettings("ID", null, 500)) val actual = ClockSettings.serialize(ClockSettings("ID", null) .apply { _applied_timestamp = 500 }) assertEquals(expected, actual) } @Test fun jsonSerialization_noTimestamp_gotExpectedString() { val expected = "{\"clockId\":\"ID\"}" val actual = ClockSettings.serialize(ClockSettings("ID", null, null)) val actual = ClockSettings.serialize(ClockSettings("ID", null)) assertEquals(expected, actual) } }