DefaultClockProvider implementation using AnimatableClockView
Bug: 229771520
Test: Automated
Change-Id: Iba6c81f8b469ef2d0a5bc38dd89454d75bfe8a43
(cherry picked from commit c742a9cb68)
Merged-In: Iba6c81f8b469ef2d0a5bc38dd89454d75bfe8a43
This commit is contained in:
@@ -4390,6 +4390,9 @@
|
|||||||
<!-- Name of a font family to use for medium body text. -->
|
<!-- Name of a font family to use for medium body text. -->
|
||||||
<string name="config_bodyFontFamilyMedium" translatable="false">sans-serif-medium</string>
|
<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 -->
|
<!-- Size of icon shown beside a preference locked by admin -->
|
||||||
<dimen name="config_restrictedIconSize">@dimen/restricted_icon_size_material</dimen>
|
<dimen name="config_restrictedIconSize">@dimen/restricted_icon_size_material</dimen>
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
** Should include all numeric glyphs in all supported locales.
|
** Should include all numeric glyphs in all supported locales.
|
||||||
** Recommended: font with variable width to support AOD => LS animations
|
** 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-family xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<font android:typeface="monospace"/>
|
<font android:typeface="monospace"/>
|
||||||
</font-family>
|
</font-family>
|
||||||
@@ -666,6 +666,7 @@
|
|||||||
<!-- With the large clock, move up slightly from the center -->
|
<!-- With the large clock, move up slightly from the center -->
|
||||||
<dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
|
<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 -->
|
<!-- 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>
|
<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 -->
|
<!-- 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>
|
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
|
||||||
|
|
||||||
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
|
<!-- 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="large_clock_text_size">150dp</dimen>
|
||||||
<dimen name="clock_text_size">86dp</dimen>
|
<dimen name="clock_text_size">86dp</dimen>
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ android_library {
|
|||||||
"dagger2",
|
"dagger2",
|
||||||
"jsr330",
|
"jsr330",
|
||||||
],
|
],
|
||||||
|
resource_dirs: [
|
||||||
|
"res",
|
||||||
|
],
|
||||||
java_version: "1.8",
|
java_version: "1.8",
|
||||||
min_sdk_version: "current",
|
min_sdk_version: "current",
|
||||||
plugins: ["dagger2-compiler"],
|
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)
|
Patterns.update(context)
|
||||||
val use24HourFormat = DateFormat.is24HourFormat(context)
|
|
||||||
|
|
||||||
format = when {
|
format = when {
|
||||||
isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
|
isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
|
||||||
|
|||||||
@@ -34,16 +34,23 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
private val TAG = ClockRegistry::class.simpleName
|
private val TAG = ClockRegistry::class.simpleName
|
||||||
private val DEBUG = true
|
private val DEBUG = true
|
||||||
const val DEFAULT_CLOCK_ID = "DEFAULT"
|
|
||||||
|
|
||||||
typealias ClockChangeListener = () -> Unit
|
typealias ClockChangeListener = () -> Unit
|
||||||
|
|
||||||
/** ClockRegistry aggregates providers and plugins */
|
/** ClockRegistry aggregates providers and plugins */
|
||||||
open class ClockRegistry @Inject constructor(
|
open class ClockRegistry(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val pluginManager: PluginManager,
|
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 gson = Gson()
|
||||||
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
|
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
|
||||||
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
|
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
|
||||||
@@ -53,19 +60,52 @@ open class ClockRegistry @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
|
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
|
||||||
override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
|
override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
|
||||||
|
connectClocks(plugin)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectClocks(provider: ClockProvider) {
|
||||||
val currentId = currentClockId
|
val currentId = currentClockId
|
||||||
for (clock in plugin.getClocks()) {
|
for (clock in provider.getClocks()) {
|
||||||
val id = clock.clockId
|
val id = clock.clockId
|
||||||
val current = availableClocks[id]
|
val current = availableClocks[id]
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
Log.e(TAG, "Clock Id conflict: $id is registered by both " +
|
Log.e(TAG, "Clock Id conflict: $id is registered by both " +
|
||||||
"${plugin::class.simpleName} and ${current.provider::class.simpleName}")
|
"${provider::class.simpleName} and ${current.provider::class.simpleName}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
availableClocks[id] = ClockInfo(clock, plugin)
|
availableClocks[id] = ClockInfo(clock, provider)
|
||||||
|
|
||||||
if (currentId == id) {
|
if (currentId == id) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.i(TAG, "Current clock ($currentId) was connected")
|
Log.i(TAG, "Current clock ($currentId) was connected")
|
||||||
@@ -75,9 +115,9 @@ open class ClockRegistry @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
|
private fun disconnectClocks(provider: ClockProvider) {
|
||||||
val currentId = currentClockId
|
val currentId = currentClockId
|
||||||
for (clock in plugin.getClocks()) {
|
for (clock in provider.getClocks()) {
|
||||||
availableClocks.remove(clock.clockId)
|
availableClocks.remove(clock.clockId)
|
||||||
|
|
||||||
if (currentId == clock.clockId) {
|
if (currentId == clock.clockId) {
|
||||||
@@ -86,28 +126,6 @@ open class ClockRegistry @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
open var currentClockId: ClockId
|
|
||||||
get() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
|
|
||||||
context.contentResolver.registerContentObserver(
|
|
||||||
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
|
|
||||||
false,
|
|
||||||
settingObserver,
|
|
||||||
UserHandle.USER_ALL)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
|
fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.shared.clocks
|
||||||
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
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.Clock
|
||||||
import com.android.systemui.plugins.ClockId
|
import com.android.systemui.plugins.ClockId
|
||||||
import com.android.systemui.plugins.ClockMetadata
|
import com.android.systemui.plugins.ClockMetadata
|
||||||
import com.android.systemui.plugins.ClockProviderPlugin
|
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.argumentCaptor
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
@@ -39,6 +38,7 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -49,9 +49,11 @@ class ClockRegistryTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var mockContext: Context
|
@Mock private lateinit var mockContext: Context
|
||||||
@Mock private lateinit var mockPluginManager: PluginManager
|
@Mock private lateinit var mockPluginManager: PluginManager
|
||||||
@Mock private lateinit var mockClock: Clock
|
@Mock private lateinit var mockClock: Clock
|
||||||
|
@Mock private lateinit var mockDefaultClock: Clock
|
||||||
@Mock private lateinit var mockThumbnail: Drawable
|
@Mock private lateinit var mockThumbnail: Drawable
|
||||||
@Mock private lateinit var mockHandler: Handler
|
@Mock private lateinit var mockHandler: Handler
|
||||||
@Mock private lateinit var mockContentResolver: ContentResolver
|
@Mock private lateinit var mockContentResolver: ContentResolver
|
||||||
|
private lateinit var fakeDefaultProvider: FakeClockPlugin
|
||||||
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
|
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
|
||||||
private lateinit var registry: ClockRegistry
|
private lateinit var registry: ClockRegistry
|
||||||
|
|
||||||
@@ -83,42 +85,52 @@ class ClockRegistryTest : SysuiTestCase() {
|
|||||||
name: String,
|
name: String,
|
||||||
create: () -> Clock = ::failFactory,
|
create: () -> Clock = ::failFactory,
|
||||||
getThumbnail: () -> Drawable? = ::failThumbnail
|
getThumbnail: () -> Drawable? = ::failThumbnail
|
||||||
) {
|
): FakeClockPlugin {
|
||||||
metadata.add(ClockMetadata(id, name))
|
metadata.add(ClockMetadata(id, name))
|
||||||
createCallbacks[id] = create
|
createCallbacks[id] = create
|
||||||
thumbnailCallbacks[id] = getThumbnail
|
thumbnailCallbacks[id] = getThumbnail
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
fakeDefaultProvider = FakeClockPlugin()
|
||||||
|
.addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail })
|
||||||
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
|
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
|
||||||
|
|
||||||
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
|
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
|
||||||
registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) {
|
registry = object : ClockRegistry(
|
||||||
|
mockContext,
|
||||||
|
mockPluginManager,
|
||||||
|
mockHandler,
|
||||||
|
fakeDefaultProvider
|
||||||
|
) {
|
||||||
override var currentClockId: ClockId
|
override var currentClockId: ClockId
|
||||||
get() = settingValue
|
get() = settingValue
|
||||||
set(value) { settingValue = value }
|
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
|
pluginListener = captor.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun pluginRegistration_CorrectState() {
|
fun pluginRegistration_CorrectState() {
|
||||||
val plugin1 = FakeClockPlugin()
|
val plugin1 = FakeClockPlugin()
|
||||||
plugin1.addClock("clock_1", "clock 1")
|
.addClock("clock_1", "clock 1")
|
||||||
plugin1.addClock("clock_2", "clock 2")
|
.addClock("clock_2", "clock 2")
|
||||||
|
|
||||||
val plugin2 = FakeClockPlugin()
|
val plugin2 = FakeClockPlugin()
|
||||||
plugin2.addClock("clock_3", "clock 3")
|
.addClock("clock_3", "clock 3")
|
||||||
plugin2.addClock("clock_4", "clock 4")
|
.addClock("clock_4", "clock 4")
|
||||||
|
|
||||||
pluginListener.onPluginConnected(plugin1, mockContext)
|
pluginListener.onPluginConnected(plugin1, mockContext)
|
||||||
pluginListener.onPluginConnected(plugin2, mockContext)
|
pluginListener.onPluginConnected(plugin2, mockContext)
|
||||||
val list = registry.getClocks()
|
val list = registry.getClocks()
|
||||||
assertEquals(list, listOf(
|
assertEquals(list, listOf(
|
||||||
|
ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
|
||||||
ClockMetadata("clock_1", "clock 1"),
|
ClockMetadata("clock_1", "clock 1"),
|
||||||
ClockMetadata("clock_2", "clock 2"),
|
ClockMetadata("clock_2", "clock 2"),
|
||||||
ClockMetadata("clock_3", "clock 3"),
|
ClockMetadata("clock_3", "clock 3"),
|
||||||
@@ -126,20 +138,27 @@ class ClockRegistryTest : SysuiTestCase() {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun noPlugins_createDefaultClock() {
|
||||||
|
val clock = registry.createCurrentClock()
|
||||||
|
assertEquals(clock, mockDefaultClock)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun clockIdConflict_ErrorWithoutCrash() {
|
fun clockIdConflict_ErrorWithoutCrash() {
|
||||||
val plugin1 = FakeClockPlugin()
|
val plugin1 = FakeClockPlugin()
|
||||||
plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
|
.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
|
||||||
plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
|
.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
|
||||||
|
|
||||||
val plugin2 = FakeClockPlugin()
|
val plugin2 = FakeClockPlugin()
|
||||||
plugin2.addClock("clock_1", "clock 1")
|
.addClock("clock_1", "clock 1")
|
||||||
plugin2.addClock("clock_2", "clock 2")
|
.addClock("clock_2", "clock 2")
|
||||||
|
|
||||||
pluginListener.onPluginConnected(plugin1, mockContext)
|
pluginListener.onPluginConnected(plugin1, mockContext)
|
||||||
pluginListener.onPluginConnected(plugin2, mockContext)
|
pluginListener.onPluginConnected(plugin2, mockContext)
|
||||||
val list = registry.getClocks()
|
val list = registry.getClocks()
|
||||||
assertEquals(list, listOf(
|
assertEquals(list, listOf(
|
||||||
|
ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
|
||||||
ClockMetadata("clock_1", "clock 1"),
|
ClockMetadata("clock_1", "clock 1"),
|
||||||
ClockMetadata("clock_2", "clock 2")
|
ClockMetadata("clock_2", "clock 2")
|
||||||
))
|
))
|
||||||
@@ -153,13 +172,13 @@ class ClockRegistryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun createCurrentClock_pluginConnected() {
|
fun createCurrentClock_pluginConnected() {
|
||||||
val plugin1 = FakeClockPlugin()
|
val plugin1 = FakeClockPlugin()
|
||||||
plugin1.addClock("clock_1", "clock 1")
|
.addClock("clock_1", "clock 1")
|
||||||
plugin1.addClock("clock_2", "clock 2")
|
.addClock("clock_2", "clock 2")
|
||||||
|
|
||||||
settingValue = "clock_3"
|
settingValue = "clock_3"
|
||||||
val plugin2 = FakeClockPlugin()
|
val plugin2 = FakeClockPlugin()
|
||||||
plugin2.addClock("clock_3", "clock 3", { mockClock })
|
.addClock("clock_3", "clock 3", { mockClock })
|
||||||
plugin2.addClock("clock_4", "clock 4")
|
.addClock("clock_4", "clock 4")
|
||||||
|
|
||||||
pluginListener.onPluginConnected(plugin1, mockContext)
|
pluginListener.onPluginConnected(plugin1, mockContext)
|
||||||
pluginListener.onPluginConnected(plugin2, mockContext)
|
pluginListener.onPluginConnected(plugin2, mockContext)
|
||||||
@@ -171,32 +190,32 @@ class ClockRegistryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun createDefaultClock_pluginDisconnected() {
|
fun createDefaultClock_pluginDisconnected() {
|
||||||
val plugin1 = FakeClockPlugin()
|
val plugin1 = FakeClockPlugin()
|
||||||
plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock })
|
.addClock("clock_1", "clock 1")
|
||||||
plugin1.addClock("clock_2", "clock 2")
|
.addClock("clock_2", "clock 2")
|
||||||
|
|
||||||
settingValue = "clock_3"
|
settingValue = "clock_3"
|
||||||
val plugin2 = FakeClockPlugin()
|
val plugin2 = FakeClockPlugin()
|
||||||
plugin2.addClock("clock_3", "clock 3")
|
.addClock("clock_3", "clock 3")
|
||||||
plugin2.addClock("clock_4", "clock 4")
|
.addClock("clock_4", "clock 4")
|
||||||
|
|
||||||
pluginListener.onPluginConnected(plugin1, mockContext)
|
pluginListener.onPluginConnected(plugin1, mockContext)
|
||||||
pluginListener.onPluginConnected(plugin2, mockContext)
|
pluginListener.onPluginConnected(plugin2, mockContext)
|
||||||
pluginListener.onPluginDisconnected(plugin2)
|
pluginListener.onPluginDisconnected(plugin2)
|
||||||
|
|
||||||
val clock = registry.createCurrentClock()
|
val clock = registry.createCurrentClock()
|
||||||
assertEquals(clock, mockClock)
|
assertEquals(clock, mockDefaultClock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun pluginRemoved_clockChanged() {
|
fun pluginRemoved_clockChanged() {
|
||||||
val plugin1 = FakeClockPlugin()
|
val plugin1 = FakeClockPlugin()
|
||||||
plugin1.addClock("clock_1", "clock 1")
|
.addClock("clock_1", "clock 1")
|
||||||
plugin1.addClock("clock_2", "clock 2")
|
.addClock("clock_2", "clock 2")
|
||||||
|
|
||||||
settingValue = "clock_3"
|
settingValue = "clock_3"
|
||||||
val plugin2 = FakeClockPlugin()
|
val plugin2 = FakeClockPlugin()
|
||||||
plugin2.addClock("clock_3", "clock 3", { mockClock })
|
.addClock("clock_3", "clock 3", { mockClock })
|
||||||
plugin2.addClock("clock_4", "clock 4")
|
.addClock("clock_4", "clock 4")
|
||||||
|
|
||||||
pluginListener.onPluginConnected(plugin1, mockContext)
|
pluginListener.onPluginConnected(plugin1, mockContext)
|
||||||
pluginListener.onPluginConnected(plugin2, 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