Initial ClockPlugin interfaces and ClockRegistry functions
Bug: 229771520
Test: Automated
Change-Id: I2bc8be56f9fb91734aa7d430270c3e9ad7328ed0
(cherry picked from commit c225417205)
Merged-In: I2bc8be56f9fb91734aa7d430270c3e9ad7328ed0
This commit is contained in:
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user