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 3058d94661210..bef61b867f7d7 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 @@ import java.util.TimeZone; /** * 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 0000000000000..032f946faead2 --- /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 + + /** 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 013cdac94ab82..9a0bfc19f8482 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 @@ import javax.inject.Inject; /** * 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 0000000000000..c168a466e20dc --- /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 { + 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() + + init { + pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java) + // TODO: Register Settings ContentObserver + } + + fun getClocks(): List = 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 0000000000000..dc28e4d67af12 --- /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 + 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() + private val createCallbacks = mutableMapOf Clock>() + private val thumbnailCallbacks = mutableMapOf 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>() + 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) + } +}