Initial ClockPlugin interfaces and ClockRegistry functions
Bug: 229771520
Test: Automated
Change-Id: I2bc8be56f9fb91734aa7d430270c3e9ad7328ed0
(cherry picked from commit c225417205b8f44086556681a10457ae4c7db917)
Merged-In: I2bc8be56f9fb91734aa7d430270c3e9ad7328ed0
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
index 3058d94..bef61b8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
@@ -23,7 +23,9 @@
/**
* Plugin used to replace main clock in keyguard.
+ * @deprecated Migrating to ClockProviderPlugin
*/
+@Deprecated
@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
public interface ClockPlugin extends Plugin {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
new file mode 100644
index 0000000..032f946
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins
+
+import com.android.systemui.plugins.annotations.ProvidesInterface
+import android.annotation.FloatRange
+import android.graphics.drawable.Drawable
+import android.view.View
+
+/** Identifies a clock design */
+typealias ClockId = String
+
+/** A Plugin which exposes the ClockProvider interface */
+@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
+interface ClockProviderPlugin : Plugin, ClockProvider {
+ companion object {
+ const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
+ const val VERSION = 1
+ }
+}
+
+/** Interface for building clocks and providing information about those clocks */
+interface ClockProvider {
+ /** Returns metadata for all clocks this provider knows about */
+ fun getClocks(): List<ClockMetadata>
+
+ /** Initializes and returns the target clock design */
+ fun createClock(id: ClockId): Clock
+
+ /** A static thumbnail for rendering in some examples */
+ fun getClockThumbnail(id: ClockId): Drawable?
+}
+
+/** Interface for controlling an active clock */
+interface Clock {
+ /** A small version of the clock, appropriate for smaller viewports */
+ val smallClock: View
+
+ /** A large version of the clock, appropriate when a bigger viewport is available */
+ val largeClock: View
+
+ /** Callback to update the clock view to the current time */
+ fun onTimeTick()
+
+ /** Sets the level of the AOD transition */
+ fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float)
+}
+
+/** Some data about a clock design */
+data class ClockMetadata(
+ val clockId: ClockId,
+ val name: String
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 013cdac..9a0bfc1 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -53,8 +53,11 @@
/**
* Manages custom clock faces for AOD and lock screen.
+ *
+ * @deprecated Migrate to ClockRegistry
*/
@SysUISingleton
+@Deprecated
public final class ClockManager {
private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockRegistry.kt b/packages/SystemUI/src/com/android/keyguard/clock/ClockRegistry.kt
new file mode 100644
index 0000000..c168a46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockRegistry.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.keyguard.clock
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import javax.inject.Inject
+
+private val TAG = ClockRegistry::class.simpleName
+private const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+typealias ClockChangeListener = () -> Unit
+
+/** ClockRegistry aggregates providers and plugins */
+// TODO: Is this the right place for this?
+@SysUISingleton
+class ClockRegistry @Inject constructor(
+ val context: Context,
+ val pluginManager: PluginManager
+) {
+ val pluginListener = object : PluginListener<ClockProviderPlugin> {
+ override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
+ for (clock in plugin.getClocks()) {
+ val id = clock.clockId
+ val current = availableClocks[id]
+ if (current != null) {
+ Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+ "${plugin::class.simpleName} and ${current.provider::class.simpleName}")
+ return
+ }
+
+ availableClocks[id] = ClockInfo(clock, plugin)
+ }
+ }
+
+ override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
+ for (clock in plugin.getClocks()) {
+ availableClocks.remove(clock.clockId)
+ }
+ }
+ }
+
+ private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+
+ init {
+ pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+ // TODO: Register Settings ContentObserver
+ }
+
+ fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+
+ fun getClockThumbnail(clockId: ClockId): Drawable? =
+ availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+
+ fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+
+ fun getCurrentClock(): Clock {
+ val clockId = "" // TODO: Load setting
+ if (!clockId.isNullOrEmpty()) {
+ val clock = createClock(clockId)
+ if (clock != null) {
+ return clock
+ } else {
+ Log.e(TAG, "Clock $clockId not found; using default")
+ }
+ }
+
+ return createClock(DEFAULT_CLOCK_ID)!!
+ }
+
+ 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
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockRegistryTest.kt
new file mode 100644
index 0000000..dc28e4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockRegistryTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.keyguard.clock
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+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 org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockRegistryTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockPluginManager: PluginManager
+ @Mock private lateinit var mockClock: Clock
+ @Mock private lateinit var mockThumbnail: Drawable
+ private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
+ private lateinit var registry: ClockRegistry
+
+ 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 {
+ private val metadata = mutableListOf<ClockMetadata>()
+ private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+ private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+
+ override fun getClocks() = metadata
+ override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+ override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+
+ fun addClock(
+ id: ClockId,
+ name: String,
+ create: () -> Clock,
+ getThumbnail: () -> Drawable?
+ ) {
+ metadata.add(ClockMetadata(id, name))
+ createCallbacks[id] = create
+ thumbnailCallbacks[id] = getThumbnail
+ }
+ }
+
+ @Before
+ fun setUp() {
+ val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
+ registry = ClockRegistry(mockContext, mockPluginManager)
+ verify(mockPluginManager).addPluginListener(captor.capture(),
+ eq(ClockProviderPlugin::class.java))
+ pluginListener = captor.value
+ }
+
+ @Test
+ fun pluginRegistration_CorrectState() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock("clock_1", "clock 1", ::failFactory, ::failThumbnail)
+ plugin1.addClock("clock_2", "clock 2", ::failFactory, ::failThumbnail)
+
+ val plugin2 = FakeClockPlugin()
+ plugin2.addClock("clock_3", "clock 3", ::failFactory, ::failThumbnail)
+ plugin2.addClock("clock_4", "clock 4", ::failFactory, ::failThumbnail)
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ val list = registry.getClocks()
+ assertEquals(list, listOf(
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ ))
+ }
+
+ @Test
+ fun clockIdConflict_ErrorWithoutCrash() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
+ 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)
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ val list = registry.getClocks()
+ assertEquals(list, listOf(
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ ))
+
+ assertEquals(registry.createExampleClock("clock_1"), mockClock)
+ assertEquals(registry.createExampleClock("clock_2"), mockClock)
+ assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
+ assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ }
+}