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>
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ android_library {
|
||||
"dagger2",
|
||||
"jsr330",
|
||||
],
|
||||
resource_dirs: [
|
||||
"res",
|
||||
],
|
||||
java_version: "1.8",
|
||||
min_sdk_version: "current",
|
||||
plugins: ["dagger2-compiler"],
|
||||
|
||||
@@ -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>
|
||||
32
packages/SystemUI/shared/res/layout/clock_default_large.xml
Normal file
32
packages/SystemUI/shared/res/layout/clock_default_large.xml
Normal file
@@ -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" />
|
||||
33
packages/SystemUI/shared/res/layout/clock_default_small.xml
Normal file
33
packages/SystemUI/shared/res/layout/clock_default_small.xml
Normal file
@@ -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" />
|
||||
27
packages/SystemUI/shared/res/values/dimens.xml
Normal file
27
packages/SystemUI/shared/res/values/dimens.xml
Normal file
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
|
||||
connectClocks(plugin)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
@@ -126,20 +138,27 @@ 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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user