diff options
| author | 2022-07-28 00:59:37 +0000 | |
|---|---|---|
| committer | 2022-07-28 00:59:37 +0000 | |
| commit | 799777249003ddacb7432168e5c6f1a263eafa84 (patch) | |
| tree | eb1d8f952c78f7e337787c5d54c86f1662768e09 | |
| parent | 4898a3538d2dfd63494a204e70e3dbce8c1a51cc (diff) | |
| parent | 9158a60bce7bf5983058d1be158f1aa80b9aa0df (diff) | |
DefaultClockProvider implementation using AnimatableClockView am: 9158a60bce
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19430369
Change-Id: I231f711efee75cc888dd2880d667ad434af68898
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
13 files changed, 490 insertions, 72 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 190d7bddfa17..24ea5a2bc14d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4347,6 +4347,9 @@ <!-- Name of a font family to use for medium body text. --> <string name="config_bodyFontFamilyMedium" translatable="false">sans-serif-medium</string> + <!-- Name of the font family to use in the default lockscreen clock --> + <string name="config_clockFontFamily" translatable="false">monospace</string> + <!-- Size of icon shown beside a preference locked by admin --> <dimen name="config_restrictedIconSize">@dimen/restricted_icon_size_material</dimen> diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml index d0867e9aa546..0137dc39921f 100644 --- a/packages/SystemUI/res-keyguard/font/clock.xml +++ b/packages/SystemUI/res-keyguard/font/clock.xml @@ -22,6 +22,7 @@ ** Should include all numeric glyphs in all supported locales. ** Recommended: font with variable width to support AOD => LS animations --> +<!-- TODO: Remove when clock migration complete --> <font-family xmlns:android="http://schemas.android.com/apk/res/android"> <font android:typeface="monospace"/> </font-family>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0a4c24d0eaa6..3fb00a33e6b6 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -666,6 +666,7 @@ <!-- With the large clock, move up slightly from the center --> <dimen name="keyguard_large_clock_top_margin">-60dp</dimen> + <!-- TODO: Remove during migration --> <!-- Default line spacing multiplier between hours and minutes of the keyguard clock --> <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item> <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock --> @@ -890,6 +891,7 @@ <dimen name="burn_in_prevention_offset_y_clock">42dp</dimen> <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) --> + <!-- TODO: Remove when clock migration complete --> <dimen name="large_clock_text_size">150dp</dimen> <dimen name="clock_text_size">86dp</dimen> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 81297169d610..18bd6b42386a 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -56,6 +56,9 @@ android_library { "dagger2", "jsr330", ], + resource_dirs: [ + "res", + ], java_version: "1.8", min_sdk_version: "current", plugins: ["dagger2-compiler"], diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml new file mode 100644 index 000000000000..be72d0b82dbb --- /dev/null +++ b/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#FFFF00FF" /> +</shape> diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml new file mode 100644 index 000000000000..8510a0a8b550 --- /dev/null +++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> +<com.android.systemui.shared.clocks.AnimatableClockView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/animatable_clock_view_large" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center_horizontal" + android:textSize="@dimen/large_clock_text_size" + android:fontFamily="@*android:string/config_clockFontFamily" + android:typeface="monospace" + android:elegantTextHeight="false" + chargeAnimationDelay="200" + dozeWeight="200" + lockScreenWeight="400" /> diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml new file mode 100644 index 000000000000..ec0e427e6a4d --- /dev/null +++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> +<com.android.systemui.shared.clocks.AnimatableClockView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/animatable_clock_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:gravity="start" + android:textSize="@dimen/small_clock_text_size" + android:fontFamily="@*android:string/config_clockFontFamily" + android:elegantTextHeight="false" + android:singleLine="true" + android:fontFeatureSettings="pnum" + chargeAnimationDelay="350" + dozeWeight="200" + lockScreenWeight="400" /> diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/shared/res/values/dimens.xml new file mode 100644 index 000000000000..8f90f0fefdb5 --- /dev/null +++ b/packages/SystemUI/shared/res/values/dimens.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +*/ +--> +<resources> + <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) --> + <dimen name="large_clock_text_size">150sp</dimen> + <dimen name="small_clock_text_size">86sp</dimen> + + <!-- Default line spacing multiplier between hours and minutes of the keyguard clock --> + <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item> + <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock --> + <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 5b1a23d4f364..2739d59dbf00 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -331,9 +331,9 @@ class AnimatableClockView @JvmOverloads constructor( ) } - fun refreshFormat() { + fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context)) + fun refreshFormat(use24HourFormat: Boolean) { Patterns.update(context) - val use24HourFormat = DateFormat.is24HourFormat(context) format = when { isSingleLineInternal && use24HourFormat -> Patterns.sClockView24 diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 2cac44a27e89..a4c03b0b57c8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -34,16 +34,23 @@ import javax.inject.Inject private val TAG = ClockRegistry::class.simpleName private val DEBUG = true -const val DEFAULT_CLOCK_ID = "DEFAULT" typealias ClockChangeListener = () -> Unit /** ClockRegistry aggregates providers and plugins */ -open class ClockRegistry @Inject constructor( +open class ClockRegistry( val context: Context, val pluginManager: PluginManager, - @Main val handler: Handler + val handler: Handler, + defaultClockProvider: ClockProvider ) { + @Inject constructor( + context: Context, + pluginManager: PluginManager, + @Main handler: Handler, + defaultClockProvider: DefaultClockProvider + ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { } + private val gson = Gson() private val availableClocks = mutableMapOf<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() @@ -53,60 +60,71 @@ open class ClockRegistry @Inject constructor( } 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] - 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) - - 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) + override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) = + connectClocks(plugin) - if (currentId == clock.clockId) { - Log.w(TAG, "Current clock ($currentId) was disconnected") - clockChangeListeners.forEach { it() } - } - } - } + override fun onPluginDisconnected(plugin: ClockProviderPlugin) = + disconnectClocks(plugin) } open var currentClockId: ClockId get() { - val json = Settings.Secure.getString(context.contentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE) + 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) + Settings.Secure.putString( + context.contentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json + ) } init { + connectClocks(defaultClockProvider) pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java) context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), false, settingObserver, - UserHandle.USER_ALL) + UserHandle.USER_ALL + ) + } + + private fun connectClocks(provider: ClockProvider) { + val currentId = currentClockId + for (clock in provider.getClocks()) { + val id = clock.clockId + val current = availableClocks[id] + if (current != null) { + Log.e(TAG, "Clock Id conflict: $id is registered by both " + + "${provider::class.simpleName} and ${current.provider::class.simpleName}") + return + } + + availableClocks[id] = ClockInfo(clock, provider) + if (currentId == id) { + if (DEBUG) { + Log.i(TAG, "Current clock ($currentId) was connected") + } + clockChangeListeners.forEach { it() } + } + } + } + + private fun disconnectClocks(provider: ClockProvider) { + val currentId = currentClockId + for (clock in provider.getClocks()) { + availableClocks.remove(clock.clockId) + + if (currentId == clock.clockId) { + Log.w(TAG, "Current clock ($currentId) was disconnected") + clockChangeListeners.forEach { it() } + } + } } fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata } @@ -148,4 +166,4 @@ open class ClockRegistry @Inject constructor( val clockId: ClockId, val _applied_timestamp: Long ) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt new file mode 100644 index 000000000000..5d8da5985768 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -0,0 +1,183 @@ +/* + * 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.shared.clocks + +import android.content.res.Resources +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.icu.text.NumberFormat +import android.util.TypedValue +import android.view.LayoutInflater +import com.android.internal.colorextraction.ColorExtractor +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.Clock +import com.android.systemui.plugins.ClockAnimation +import com.android.systemui.plugins.ClockEvents +import com.android.systemui.plugins.ClockId +import com.android.systemui.plugins.ClockMetadata +import com.android.systemui.plugins.ClockProvider +import com.android.systemui.shared.R +import java.io.PrintWriter +import java.util.Locale +import java.util.TimeZone +import javax.inject.Inject + +private val TAG = DefaultClockProvider::class.simpleName +const val DEFAULT_CLOCK_NAME = "Default Clock" +const val DEFAULT_CLOCK_ID = "DEFAULT" + +/** Provides the default system clock */ +class DefaultClockProvider @Inject constructor( + val layoutInflater: LayoutInflater, + @Main val resources: Resources +) : ClockProvider { + override fun getClocks(): List<ClockMetadata> = + listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME)) + + override fun createClock(id: ClockId): Clock { + if (id != DEFAULT_CLOCK_ID) { + throw IllegalArgumentException("$id is unsupported by $TAG") + } + return DefaultClock(layoutInflater, resources) + } + + override fun getClockThumbnail(id: ClockId): Drawable? { + if (id != DEFAULT_CLOCK_ID) { + throw IllegalArgumentException("$id is unsupported by $TAG") + } + + // TODO: Update placeholder to actual resource + return resources.getDrawable(R.drawable.clock_default_thumbnail, null) + } +} + +/** + * Controls the default clock visuals. + * + * This serves as an adapter between the clock interface and the + * AnimatableClockView used by the existing lockscreen clock. + */ +class DefaultClock( + private val layoutInflater: LayoutInflater, + private val resources: Resources +) : Clock { + override val smallClock = + layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView + override val largeClock = + layoutInflater.inflate(R.layout.clock_default_large, null) as AnimatableClockView + private val clocks = listOf(smallClock, largeClock) + + private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my")) + private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong()) + private val burmeseLineSpacing = + resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese) + private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale) + + override val events = object : ClockEvents { + override fun onTimeTick() = clocks.forEach { it.refreshTime() } + + override fun onTimeFormatChanged(is24Hr: Boolean) = + clocks.forEach { it.refreshFormat(is24Hr) } + + override fun onTimeZoneChanged(timeZone: TimeZone) = + clocks.forEach { it.onTimeZoneChanged(timeZone) } + + override fun onFontSettingChanged() { + smallClock.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() + ) + largeClock.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() + ) + } + + override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) = + clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) } + + override fun onLocaleChanged(locale: Locale) { + val nf = NumberFormat.getInstance(locale) + if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) { + clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) } + } else { + clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) } + } + + clocks.forEach { it.refreshFormat() } + } + } + + override val animation = object : ClockAnimation { + override fun initialize(dozeFraction: Float, foldFraction: Float) { + dozeState = AnimationState(dozeFraction) + foldState = AnimationState(foldFraction) + + if (foldState.isActive) { + clocks.forEach { it.animateFoldAppear(false) } + } else { + clocks.forEach { it.animateDoze(dozeState.isActive, false) } + } + } + + override fun enter() { + if (dozeState.isActive) { + clocks.forEach { it.animateAppearOnLockscreen() } + } + } + + override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } } + + private var foldState = AnimationState(0f) + override fun fold(fraction: Float) { + val (hasChanged, hasJumped) = foldState.update(fraction) + if (hasChanged) { + clocks.forEach { it.animateFoldAppear(!hasJumped) } + } + } + + private var dozeState = AnimationState(0f) + override fun doze(fraction: Float) { + val (hasChanged, hasJumped) = dozeState.update(fraction) + if (hasChanged) { + clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) } + } + } + } + + private class AnimationState( + var fraction: Float + ) { + var isActive: Boolean = fraction < 0.5f + fun update(newFraction: Float): Pair<Boolean, Boolean> { + val wasActive = isActive + val hasJumped = (fraction == 0f && newFraction == 1f) || + (fraction == 1f && newFraction == 0f) + isActive = newFraction > fraction + fraction = newFraction + return Pair(wasActive != isActive, hasJumped) + } + } + + init { + events.onLocaleChanged(Locale.getDefault()) + } + + override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) } + + companion object { + private const val DOZE_COLOR = Color.WHITE + private const val FORMAT_NUMBER = 1234567890 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 9d82c249c635..d61989fc3128 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -15,20 +15,19 @@ */ package com.android.systemui.shared.clocks -import org.mockito.Mockito.`when` as whenever -import android.content.Context import android.content.ContentResolver +import android.content.Context import android.graphics.drawable.Drawable import android.os.Handler import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.plugins.PluginListener -import com.android.systemui.shared.plugins.PluginManager 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 @@ -39,6 +38,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @@ -49,9 +49,11 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context @Mock private lateinit var mockPluginManager: PluginManager @Mock private lateinit var mockClock: Clock + @Mock private lateinit var mockDefaultClock: Clock @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 @@ -83,42 +85,52 @@ class ClockRegistryTest : SysuiTestCase() { name: String, create: () -> Clock = ::failFactory, getThumbnail: () -> Drawable? = ::failThumbnail - ) { + ): FakeClockPlugin { metadata.add(ClockMetadata(id, name)) createCallbacks[id] = create thumbnailCallbacks[id] = getThumbnail + return this } } @Before fun setUp() { + fakeDefaultProvider = FakeClockPlugin() + .addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail }) whenever(mockContext.contentResolver).thenReturn(mockContentResolver) val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>() - registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) { + registry = object : ClockRegistry( + mockContext, + mockPluginManager, + mockHandler, + fakeDefaultProvider + ) { override var currentClockId: ClockId get() = settingValue set(value) { settingValue = value } } - verify(mockPluginManager).addPluginListener(captor.capture(), - eq(ClockProviderPlugin::class.java)) + + 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") - plugin1.addClock("clock_2", "clock 2") + .addClock("clock_1", "clock 1") + .addClock("clock_2", "clock 2") val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_3", "clock 3") - plugin2.addClock("clock_4", "clock 4") + .addClock("clock_3", "clock 3") + .addClock("clock_4", "clock 4") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) val list = registry.getClocks() assertEquals(list, listOf( + ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), ClockMetadata("clock_1", "clock 1"), ClockMetadata("clock_2", "clock 2"), ClockMetadata("clock_3", "clock 3"), @@ -127,19 +139,26 @@ class ClockRegistryTest : SysuiTestCase() { } @Test + fun noPlugins_createDefaultClock() { + val clock = registry.createCurrentClock() + assertEquals(clock, mockDefaultClock) + } + + @Test fun clockIdConflict_ErrorWithoutCrash() { val plugin1 = FakeClockPlugin() - plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail }) - plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) + .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail }) + .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_1", "clock 1") - plugin2.addClock("clock_2", "clock 2") + .addClock("clock_1", "clock 1") + .addClock("clock_2", "clock 2") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) val list = registry.getClocks() assertEquals(list, listOf( + ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), ClockMetadata("clock_1", "clock 1"), ClockMetadata("clock_2", "clock 2") )) @@ -153,13 +172,13 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun createCurrentClock_pluginConnected() { val plugin1 = FakeClockPlugin() - plugin1.addClock("clock_1", "clock 1") - plugin1.addClock("clock_2", "clock 2") + .addClock("clock_1", "clock 1") + .addClock("clock_2", "clock 2") settingValue = "clock_3" val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_3", "clock 3", { mockClock }) - plugin2.addClock("clock_4", "clock 4") + .addClock("clock_3", "clock 3", { mockClock }) + .addClock("clock_4", "clock 4") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) @@ -171,32 +190,32 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun createDefaultClock_pluginDisconnected() { val plugin1 = FakeClockPlugin() - plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock }) - plugin1.addClock("clock_2", "clock 2") + .addClock("clock_1", "clock 1") + .addClock("clock_2", "clock 2") settingValue = "clock_3" val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_3", "clock 3") - plugin2.addClock("clock_4", "clock 4") + .addClock("clock_3", "clock 3") + .addClock("clock_4", "clock 4") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) pluginListener.onPluginDisconnected(plugin2) val clock = registry.createCurrentClock() - assertEquals(clock, mockClock) + assertEquals(clock, mockDefaultClock) } @Test fun pluginRemoved_clockChanged() { val plugin1 = FakeClockPlugin() - plugin1.addClock("clock_1", "clock 1") - plugin1.addClock("clock_2", "clock 2") + .addClock("clock_1", "clock 1") + .addClock("clock_2", "clock 2") settingValue = "clock_3" val plugin2 = FakeClockPlugin() - plugin2.addClock("clock_3", "clock 3", { mockClock }) - plugin2.addClock("clock_4", "clock 4") + .addClock("clock_3", "clock 3", { mockClock }) + .addClock("clock_4", "clock 4") pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt new file mode 100644 index 000000000000..78694480ad5b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -0,0 +1,77 @@ +/* + * 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.shared.clocks + +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.testing.AndroidTestingRunner +import android.view.LayoutInflater +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertNotNull +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.`when` as whenever +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class DefaultClockProviderTest : SysuiTestCase() { + + @JvmField @Rule val mockito = MockitoJUnit.rule() + + @Mock private lateinit var mockClockView: AnimatableClockView + @Mock private lateinit var layoutInflater: LayoutInflater + @Mock private lateinit var mockClockThumbnail: Drawable + @Mock private lateinit var resources: Resources + private lateinit var provider: DefaultClockProvider + + @Before + fun setUp() { + whenever(layoutInflater.inflate(R.layout.clock_default_small, null)) + .thenReturn(mockClockView) + whenever(layoutInflater.inflate(R.layout.clock_default_large, null)) + .thenReturn(mockClockView) + whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null)) + .thenReturn(mockClockThumbnail) + + provider = DefaultClockProvider(layoutInflater, resources) + } + + @Test + fun providedClocks_matchesFactory() { + // All providers need to provide clocks & thumbnails for exposed clocks + for (metadata in provider.getClocks()) { + assertNotNull(provider.createClock(metadata.clockId)) + assertNotNull(provider.getClockThumbnail(metadata.clockId)) + } + } + + @Test + fun defaultClock_alwaysProvided() { + // Default clock provider must always provide the default clock + val clock = provider.createClock(DEFAULT_CLOCK_ID) + assertNotNull(clock) + assertEquals(clock.smallClock, mockClockView) + assertEquals(clock.largeClock, mockClockView) + } +} |