diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 3ca8dfe4fd382..51cc1954299fc 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -39,6 +39,7 @@ -packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt -packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt -packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt +-packages/SystemUI/src/com/android/keyguard/ClockEventController.kt -packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt -packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt -packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt @@ -156,6 +157,8 @@ -packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt -packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt -packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +-packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt +-packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt -packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt -packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt -packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -246,6 +249,7 @@ -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt @@ -575,6 +579,7 @@ -packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt -packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt -packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt -packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt -packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt -packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -633,6 +638,7 @@ -packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt -packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt -packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +-packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt -packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt -packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt -packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt @@ -720,6 +726,7 @@ -packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt -packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt -packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 0c1916074e0c5..01e5d86549ebd 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -13,9 +13,9 @@ */ package com.android.systemui.plugins +import android.content.res.Resources import android.graphics.drawable.Drawable import android.view.View -import com.android.internal.colorextraction.ColorExtractor import com.android.systemui.plugins.annotations.ProvidesInterface import java.io.PrintWriter import java.util.Locale @@ -57,7 +57,15 @@ interface Clock { val events: ClockEvents /** Triggers for various animations */ - val animation: ClockAnimation + val animations: ClockAnimations + + /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ + fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) { + events.onColorPaletteChanged(resources, true, true) + animations.doze(dozeFraction) + animations.fold(foldFraction) + events.onTimeTick() + } /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) { } @@ -80,15 +88,16 @@ interface ClockEvents { /** Call whenever font settings change */ fun onFontSettingChanged() { } - /** Call whenever the color pallete should update */ - fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { } + /** Call whenever the color palette should update */ + fun onColorPaletteChanged( + resources: Resources, + smallClockIsDark: Boolean, + largeClockIsDark: Boolean + ) { } } /** Methods which trigger various clock animations */ -interface ClockAnimation { - /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */ - fun initialize(dozeFraction: Float, foldFraction: Float) { } - +interface ClockAnimations { /** Runs an enter animation (if any) */ fun enter() { } diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml deleted file mode 100644 index 0137dc39921fe..0000000000000 --- a/packages/SystemUI/res-keyguard/font/clock.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 6a38507b2ad75..3ad7c8c4369ca 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -23,6 +23,7 @@ android:id="@+id/keyguard_clock_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:clipChildren="false" android:layout_gravity="center_horizontal|top"> - - + android:paddingStart="@dimen/clock_padding_start" /> - - + android:layout_height="match_parent" + android:layout_marginTop="@dimen/keyguard_large_clock_top_margin" + android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index 6ba88a66977a3..acb47f7a037c7 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -19,7 +19,7 @@ android:id="@+id/time_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@font/clock" + android:fontFamily="@*android:string/config_clockFontFamily" android:textColor="@android:color/white" android:format12Hour="@string/dream_time_complication_12_hr_time_format" android:format24Hour="@string/dream_time_complication_24_hr_time_format" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 98202371d98fd..e3be36503c873 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -671,12 +671,6 @@ -60dp - - - .7 - - 1 - 32dp @@ -895,11 +889,6 @@ burn-in on AOD. --> 42dp - - - 150dp - 86dp - 15dp diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml index 8510a0a8b5500..0139d50dcfba2 100644 --- a/packages/SystemUI/shared/res/layout/clock_default_large.xml +++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml @@ -18,7 +18,6 @@ --> Unit - /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( val context: Context, @@ -51,6 +49,11 @@ open class ClockRegistry( defaultClockProvider: DefaultClockProvider ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { } + // Usually this would be a typealias, but a SAM provides better java interop + fun interface ClockChangeListener { + fun onClockChanged() + } + var isEnabled: Boolean = false private val gson = Gson() @@ -58,7 +61,7 @@ open class ClockRegistry( private val clockChangeListeners = mutableListOf() private val settingObserver = object : ContentObserver(handler) { override fun onChange(selfChange: Boolean, uris: Collection, flags: Int, userId: Int) = - clockChangeListeners.forEach { it() } + clockChangeListeners.forEach { it.onClockChanged() } } private val pluginListener = object : PluginListener { @@ -117,8 +120,11 @@ open class ClockRegistry( 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}") + Log.e( + TAG, + "Clock Id conflict: $id is registered by both " + + "${provider::class.simpleName} and ${current.provider::class.simpleName}" + ) return } @@ -127,7 +133,7 @@ open class ClockRegistry( if (DEBUG) { Log.i(TAG, "Current clock ($currentId) was connected") } - clockChangeListeners.forEach { it() } + clockChangeListeners.forEach { it.onClockChanged() } } } } @@ -139,7 +145,7 @@ open class ClockRegistry( if (currentId == clock.clockId) { Log.w(TAG, "Current clock ($currentId) was disconnected") - clockChangeListeners.forEach { it() } + clockChangeListeners.forEach { it.onClockChanged() } } } } 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 index 5d8da59857686..b5e57667fbb83 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -13,16 +13,18 @@ */ package com.android.systemui.shared.clocks +import android.content.Context 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 android.widget.FrameLayout +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.Clock -import com.android.systemui.plugins.ClockAnimation +import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockEvents import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata @@ -39,6 +41,7 @@ const val DEFAULT_CLOCK_ID = "DEFAULT" /** Provides the default system clock */ class DefaultClockProvider @Inject constructor( + val ctx: Context, val layoutInflater: LayoutInflater, @Main val resources: Resources ) : ClockProvider { @@ -49,7 +52,7 @@ class DefaultClockProvider @Inject constructor( if (id != DEFAULT_CLOCK_ID) { throw IllegalArgumentException("$id is unsupported by $TAG") } - return DefaultClock(layoutInflater, resources) + return DefaultClock(ctx, layoutInflater, resources) } override fun getClockThumbnail(id: ClockId): Drawable? { @@ -69,14 +72,13 @@ class DefaultClockProvider @Inject constructor( * AnimatableClockView used by the existing lockscreen clock. */ class DefaultClock( - private val layoutInflater: LayoutInflater, - private val resources: Resources + ctx: Context, + 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) + override val smallClock: AnimatableClockView + override val largeClock: AnimatableClockView + private val clocks get() = listOf(smallClock, largeClock) private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my")) private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong()) @@ -84,7 +86,43 @@ class DefaultClock( 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 val events: ClockEvents + override lateinit var animations: ClockAnimations + private set + + init { + val parent = FrameLayout(ctx) + + smallClock = layoutInflater.inflate( + R.layout.clock_default_small, + parent, + false + ) as AnimatableClockView + + largeClock = layoutInflater.inflate( + R.layout.clock_default_large, + parent, + false + ) as AnimatableClockView + + events = DefaultClockEvents() + animations = DefaultClockAnimations(0f, 0f) + + events.onLocaleChanged(Locale.getDefault()) + + // DOZE_COLOR is a placeholder, and will be assigned correctly in initialize + clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) } + } + + override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) { + recomputePadding() + animations = DefaultClockAnimations(dozeFraction, foldFraction) + events.onColorPaletteChanged(resources, true, true) + events.onTimeZoneChanged(TimeZone.getDefault()) + events.onTimeTick() + } + + inner class DefaultClockEvents() : ClockEvents { override fun onTimeTick() = clocks.forEach { it.refreshTime() } override fun onTimeFormatChanged(is24Hr: Boolean) = @@ -102,10 +140,17 @@ class DefaultClock( TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() ) + recomputePadding() } - override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) = - clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) } + override fun onColorPaletteChanged( + resources: Resources, + smallClockIsDark: Boolean, + largeClockIsDark: Boolean + ) { + updateClockColor(smallClock, smallClockIsDark) + updateClockColor(largeClock, largeClockIsDark) + } override fun onLocaleChanged(locale: Locale) { val nf = NumberFormat.getInstance(locale) @@ -119,8 +164,14 @@ class DefaultClock( } } - override val animation = object : ClockAnimation { - override fun initialize(dozeFraction: Float, foldFraction: Float) { + inner class DefaultClockAnimations( + dozeFraction: Float, + foldFraction: Float + ) : ClockAnimations { + private var foldState = AnimationState(0f) + private var dozeState = AnimationState(0f) + + init { dozeState = AnimationState(dozeFraction) foldState = AnimationState(foldFraction) @@ -132,14 +183,13 @@ class DefaultClock( } override fun enter() { - if (dozeState.isActive) { + 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) { @@ -147,7 +197,6 @@ class DefaultClock( } } - private var dozeState = AnimationState(0f) override fun doze(fraction: Float) { val (hasChanged, hasJumped) = dozeState.update(fraction) if (hasChanged) { @@ -170,14 +219,26 @@ class DefaultClock( } } - init { - events.onLocaleChanged(Locale.getDefault()) + private fun updateClockColor(clock: AnimatableClockView, isRegionDark: Boolean) { + val color = if (isRegionDark) { + resources.getColor(android.R.color.system_accent1_100) + } else { + resources.getColor(android.R.color.system_accent2_600) + } + clock.setColors(DOZE_COLOR, color) + clock.animateAppearOnLockscreen() + } + + private fun recomputePadding() { + val lp = largeClock.getLayoutParams() as FrameLayout.LayoutParams + lp.topMargin = (-0.5f * largeClock.bottom).toInt() + largeClock.setLayoutParams(lp) } override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) } companion object { - private const val DOZE_COLOR = Color.WHITE + @VisibleForTesting const val DOZE_COLOR = Color.WHITE private const val FORMAT_NUMBER = 1234567890 } } diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 3c68c0daf4894..6064be94bc0a2 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -22,7 +22,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Rect; import android.icu.text.NumberFormat; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -30,16 +32,24 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; import java.io.PrintWriter; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.TimeZone; +import java.util.concurrent.Executor; + +import javax.inject.Inject; /** * Controller for an AnimatableClockView on the keyguard. Instantiated by @@ -54,7 +64,10 @@ public class AnimatableClockController extends ViewController mRegionSamplingHelper = Optional.empty(); + private Rect mSamplingBounds = new Rect(); private int mLockScreenColor; + private final boolean mRegionSamplingEnabled; private boolean mIsDozing; private boolean mIsCharging; @@ -67,13 +80,17 @@ public class AnimatableClockController extends ViewController { + regionSamplingHelper.setWindowVisible(true); + }); } private void reset() { @@ -169,6 +220,10 @@ public class AnimatableClockController extends ViewController { + regionSamplingHelper.start(mSamplingBounds); + }); + mView.onTimeZoneChanged(TimeZone.getDefault()); initColors(); mView.animateDoze(mIsDozing, false); @@ -180,18 +235,9 @@ public class AnimatableClockController extends ViewController { + regionSamplingHelper.stop(); + }); } /** Animate the clock appearance */ @@ -235,8 +281,10 @@ public class AnimatableClockController extends ViewController { + regionSamplingHelper.dump(pw); + }); } } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt new file mode 100644 index 0000000000000..0e1e0cb2adcf0 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.res.Resources +import android.text.format.DateFormat +import android.util.TypedValue +import android.view.View +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.plugins.Clock +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shared.regionsampling.RegionSamplingInstance +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback +import com.android.systemui.statusbar.policy.ConfigurationController +import java.io.PrintWriter +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by + * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. + */ +open class ClockEventController @Inject constructor( + private val statusBarStateController: StatusBarStateController, + private val broadcastDispatcher: BroadcastDispatcher, + private val batteryController: BatteryController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val configurationController: ConfigurationController, + @Main private val resources: Resources, + private val context: Context, + @Main private val mainExecutor: Executor, + @Background private val bgExecutor: Executor, + private val featureFlags: FeatureFlags +) { + var clock: Clock? = null + set(value) { + field = value + if (value != null) { + value.initialize(resources, dozeAmount, 0f) + updateRegionSamplers(value) + } + } + + private var isDozing = false + private set + + private var isCharging = false + private var dozeAmount = 0f + private var isKeyguardShowing = false + + private val regionSamplingEnabled = + featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING) + + private val updateFun = object : RegionSamplingInstance.UpdateColorCallback { + override fun updateColors() { + if (regionSamplingEnabled) { + smallClockIsDark = smallRegionSamplingInstance.currentRegionDarkness().isDark + largeClockIsDark = largeRegionSamplingInstance.currentRegionDarkness().isDark + } else { + val isLightTheme = TypedValue() + context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true) + smallClockIsDark = isLightTheme.data == 0 + largeClockIsDark = isLightTheme.data == 0 + } + clock?.events?.onColorPaletteChanged(resources, smallClockIsDark, largeClockIsDark) + } + } + + fun updateRegionSamplers(currentClock: Clock?) { + smallRegionSamplingInstance = createRegionSampler( + currentClock?.smallClock, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + updateFun + ) + + largeRegionSamplingInstance = createRegionSampler( + currentClock?.largeClock, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + updateFun + ) + + smallRegionSamplingInstance.startRegionSampler() + largeRegionSamplingInstance.startRegionSampler() + + updateFun.updateColors() + } + + protected open fun createRegionSampler( + sampledView: View?, + mainExecutor: Executor?, + bgExecutor: Executor?, + regionSamplingEnabled: Boolean, + updateFun: RegionSamplingInstance.UpdateColorCallback + ): RegionSamplingInstance { + return RegionSamplingInstance( + sampledView, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + updateFun) + } + + lateinit var smallRegionSamplingInstance: RegionSamplingInstance + lateinit var largeRegionSamplingInstance: RegionSamplingInstance + + private var smallClockIsDark = true + private var largeClockIsDark = true + + private val configListener = object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + updateFun.updateColors() + } + } + + private val batteryCallback = object : BatteryStateChangeCallback { + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + if (isKeyguardShowing && !isCharging && charging) { + clock?.animations?.charge() + } + isCharging = charging + } + } + + private val localeBroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + clock?.events?.onLocaleChanged(Locale.getDefault()) + } + } + + private val statusBarStateListener = object : StatusBarStateController.StateListener { + override fun onDozeAmountChanged(linear: Float, eased: Float) { + clock?.animations?.doze(linear) + + isDozing = linear > dozeAmount + dozeAmount = linear + } + } + + private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { + override fun onKeyguardVisibilityChanged(showing: Boolean) { + isKeyguardShowing = showing + if (!isKeyguardShowing) { + clock?.animations?.doze(if (isDozing) 1f else 0f) + } + } + + override fun onTimeFormatChanged(timeFormat: String) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + clock?.events?.onTimeZoneChanged(timeZone) + } + + override fun onUserSwitchComplete(userId: Int) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + } + + init { + isDozing = statusBarStateController.isDozing + } + + fun registerListeners() { + dozeAmount = statusBarStateController.dozeAmount + isDozing = statusBarStateController.isDozing || dozeAmount != 0f + + broadcastDispatcher.registerReceiver( + localeBroadcastReceiver, + IntentFilter(Intent.ACTION_LOCALE_CHANGED) + ) + configurationController.addCallback(configListener) + batteryController.addCallback(batteryCallback) + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + statusBarStateController.addCallback(statusBarStateListener) + smallRegionSamplingInstance.startRegionSampler() + largeRegionSamplingInstance.startRegionSampler() + } + + fun unregisterListeners() { + broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver) + configurationController.removeCallback(configListener) + batteryController.removeCallback(batteryCallback) + keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) + statusBarStateController.removeCallback(statusBarStateListener) + smallRegionSamplingInstance.stopRegionSampler() + largeRegionSamplingInstance.stopRegionSampler() + } + + /** + * Dump information for debugging + */ + fun dump(pw: PrintWriter) { + pw.println(this) + clock?.dump(pw) + smallRegionSamplingInstance.dump(pw) + largeRegionSamplingInstance.dump(pw) + } + + companion object { + private val TAG = ClockEventController::class.simpleName + private const val FORMAT_NUMBER = 1234567890 + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 916410856b20c..d7cd1d0dbc6f0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -5,31 +5,23 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; -import android.graphics.Paint; -import android.graphics.Paint.Style; import android.util.AttributeSet; -import android.util.TypedValue; +import android.util.Log; import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.RelativeLayout; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.dagger.KeyguardStatusViewScope; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; -import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.plugins.Clock; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.TimeZone; - /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ @@ -49,18 +41,11 @@ public class KeyguardClockSwitch extends RelativeLayout { public static final int LARGE = 0; public static final int SMALL = 1; - /** - * Optional/alternative clock injected via plugin. - */ - private ClockPlugin mClockPlugin; - /** * Frame for small/large clocks */ - private FrameLayout mClockFrame; + private FrameLayout mSmallClockFrame; private FrameLayout mLargeClockFrame; - private AnimatableClockView mClockView; - private AnimatableClockView mLargeClockView; private View mStatusArea; private int mSmartspaceTopOffset; @@ -80,12 +65,6 @@ public class KeyguardClockSwitch extends RelativeLayout { @VisibleForTesting AnimatorSet mClockOutAnim = null; private ObjectAnimator mStatusAreaAnim = null; - /** - * If the Keyguard Slice has a header (big center-aligned text.) - */ - private boolean mSupportsDarkText; - private int[] mColorPalette; - private int mClockSwitchYAmount; @VisibleForTesting boolean mChildrenAreLaidOut = false; @VisibleForTesting boolean mAnimateOnLayout = true; @@ -98,97 +77,36 @@ public class KeyguardClockSwitch extends RelativeLayout { * Apply dp changes on font/scale change */ public void onDensityOrFontScaleChanged() { - mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources() - .getDimensionPixelSize(R.dimen.large_clock_text_size)); - mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources() - .getDimensionPixelSize(R.dimen.clock_text_size)); - mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_clock_switch_y_shift); - mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_smartspace_top_offset); } - /** - * Returns if this view is presenting a custom clock, or the default implementation. - */ - public boolean hasCustomClock() { - return mClockPlugin != null; - } - @Override protected void onFinishInflate() { super.onFinishInflate(); - mClockFrame = findViewById(R.id.lockscreen_clock_view); - mClockView = findViewById(R.id.animatable_clock_view); + mSmallClockFrame = findViewById(R.id.lockscreen_clock_view); mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large); - mLargeClockView = findViewById(R.id.animatable_clock_view_large); mStatusArea = findViewById(R.id.keyguard_status_area); onDensityOrFontScaleChanged(); } - void setClockPlugin(ClockPlugin plugin, int statusBarState) { + void setClock(Clock clock, int statusBarState) { // Disconnect from existing plugin. - if (mClockPlugin != null) { - View smallClockView = mClockPlugin.getView(); - if (smallClockView != null && smallClockView.getParent() == mClockFrame) { - mClockFrame.removeView(smallClockView); - } - View bigClockView = mClockPlugin.getBigClockView(); - if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) { - mLargeClockFrame.removeView(bigClockView); - } - mClockPlugin.onDestroyView(); - mClockPlugin = null; - } - if (plugin == null) { - mClockView.setVisibility(View.VISIBLE); - mLargeClockView.setVisibility(View.VISIBLE); + mSmallClockFrame.removeAllViews(); + mLargeClockFrame.removeAllViews(); + + if (clock == null) { + Log.e(TAG, "No clock being shown"); return; } + // Attach small and big clock views to hierarchy. - View smallClockView = plugin.getView(); - if (smallClockView != null) { - mClockFrame.addView(smallClockView, -1, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - mClockView.setVisibility(View.GONE); - } - View bigClockView = plugin.getBigClockView(); - if (bigClockView != null) { - mLargeClockFrame.addView(bigClockView); - mLargeClockView.setVisibility(View.GONE); - } - - // Initialize plugin parameters. - mClockPlugin = plugin; - mClockPlugin.setStyle(getPaint().getStyle()); - mClockPlugin.setTextColor(getCurrentTextColor()); - mClockPlugin.setDarkAmount(mDarkAmount); - if (mColorPalette != null) { - mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette); - } - } - - /** - * It will also update plugin setStyle if plugin is connected. - */ - public void setStyle(Style style) { - if (mClockPlugin != null) { - mClockPlugin.setStyle(style); - } - } - - /** - * It will also update plugin setTextColor if plugin is connected. - */ - public void setTextColor(int color) { - if (mClockPlugin != null) { - mClockPlugin.setTextColor(color); - } + mSmallClockFrame.addView(clock.getSmallClock()); + mLargeClockFrame.addView(clock.getLargeClock()); } private void updateClockViews(boolean useLargeClock, boolean animate) { @@ -204,14 +122,14 @@ public class KeyguardClockSwitch extends RelativeLayout { int direction = 1; float statusAreaYTranslation; if (useLargeClock) { - out = mClockFrame; + out = mSmallClockFrame; in = mLargeClockFrame; if (indexOfChild(in) == -1) addView(in); direction = -1; - statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop() + statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop() + mSmartspaceTopOffset; } else { - in = mClockFrame; + in = mSmallClockFrame; out = mLargeClockFrame; statusAreaYTranslation = 0f; @@ -269,18 +187,6 @@ public class KeyguardClockSwitch extends RelativeLayout { mStatusAreaAnim.start(); } - /** - * Set the amount (ratio) that the device has transitioned to doze. - * - * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. - */ - public void setDarkAmount(float darkAmount) { - mDarkAmount = darkAmount; - if (mClockPlugin != null) { - mClockPlugin.setDarkAmount(darkAmount); - } - } - /** * Display the desired clock and hide the other one * @@ -312,64 +218,11 @@ public class KeyguardClockSwitch extends RelativeLayout { mChildrenAreLaidOut = true; } - public Paint getPaint() { - return mClockView.getPaint(); - } - - public int getCurrentTextColor() { - return mClockView.getCurrentTextColor(); - } - - public float getTextSize() { - return mClockView.getTextSize(); - } - - /** - * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm. - */ - public void refresh() { - if (mClockPlugin != null) { - mClockPlugin.onTimeTick(); - } - } - - /** - * Notifies that the time zone has changed. - */ - public void onTimeZoneChanged(TimeZone timeZone) { - if (mClockPlugin != null) { - mClockPlugin.onTimeZoneChanged(timeZone); - } - } - - /** - * Notifies that the time format has changed. - * - * @param timeFormat "12" for 12-hour format, "24" for 24-hour format - */ - public void onTimeFormatChanged(String timeFormat) { - if (mClockPlugin != null) { - mClockPlugin.onTimeFormatChanged(timeFormat); - } - } - - void updateColors(ColorExtractor.GradientColors colors) { - mSupportsDarkText = colors.supportsDarkText(); - mColorPalette = colors.getColorPalette(); - if (mClockPlugin != null) { - mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette); - } - } - public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardClockSwitch:"); - pw.println(" mClockPlugin: " + mClockPlugin); - pw.println(" mClockFrame: " + mClockFrame); + pw.println(" mSmallClockFrame: " + mSmallClockFrame); pw.println(" mLargeClockFrame: " + mLargeClockFrame); pw.println(" mStatusArea: " + mStatusArea); - pw.println(" mDarkAmount: " + mDarkAmount); - pw.println(" mSupportsDarkText: " + mSupportsDarkText); - pw.println(" mColorPalette: " + Arrays.toString(mColorPalette)); pw.println(" mDisplayedClockSize: " + mDisplayedClockSize); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index ad06e05c2d6e9..2165099b474e3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -22,8 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; -import android.app.WallpaperManager; -import android.content.res.Resources; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; @@ -32,36 +30,30 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import androidx.annotation.NonNull; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.keyguard.clock.ClockManager; import com.android.systemui.Dumpable; import com.android.systemui.R; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.plugins.Clock; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.Locale; -import java.util.TimeZone; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -71,48 +63,23 @@ import javax.inject.Inject; */ public class KeyguardClockSwitchController extends ViewController implements Dumpable { - private static final boolean CUSTOM_CLOCKS_ENABLED = true; - private final StatusBarStateController mStatusBarStateController; - private final SysuiColorExtractor mColorExtractor; - private final ClockManager mClockManager; + private final ClockRegistry mClockRegistry; private final KeyguardSliceViewController mKeyguardSliceViewController; private final NotificationIconAreaController mNotificationIconAreaController; - private final BroadcastDispatcher mBroadcastDispatcher; - private final BatteryController mBatteryController; private final LockscreenSmartspaceController mSmartspaceController; - private final Resources mResources; private final SecureSettings mSecureSettings; private final DumpManager mDumpManager; + private final ClockEventController mClockEventController; - /** - * Clock for both small and large sizes - */ - private AnimatableClockController mClockViewController; - private FrameLayout mClockFrame; // top aligned clock - private AnimatableClockController mLargeClockViewController; + private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @KeyguardClockSwitch.ClockSize private int mCurrentClockSize = SMALL; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private int mKeyguardClockTopMargin = 0; - - /** - * Listener for changes to the color palette. - * - * The color palette changes when the wallpaper is changed. - */ - private final ColorExtractor.OnColorsChangedListener mColorsListener = - (extractor, which) -> { - if ((which & WallpaperManager.FLAG_LOCK) != 0) { - mView.updateColors(getGradientColors()); - } - }; - - private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; + private final ClockRegistry.ClockChangeListener mClockChangedListener; private ViewGroup mStatusArea; // If set will replace keyguard_slice_view @@ -121,9 +88,9 @@ public class KeyguardClockSwitchController extends ViewController { + setClock(mClockRegistry.createCurrentClock()); + }; } /** @@ -185,43 +150,21 @@ public class KeyguardClockSwitchController extends ViewController { + mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()); }).start(); - - mKeyguardStatusViewController.animateFoldToAod(); } /** @@ -4686,7 +4674,6 @@ public final class NotificationPanelViewController extends PanelViewController { public void onDozeAmountChanged(float linearAmount, float amount) { mInterpolatedDarkAmount = amount; mLinearDarkAmount = linearAmount; - mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount); mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount); positionClockAndNotifications(); } @@ -4796,11 +4783,8 @@ public final class NotificationPanelViewController extends PanelViewController { updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount()); setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); - // Update Clock Pivot - mKeyguardStatusViewController.setPivotX(((float) mView.getWidth()) / 2f); - mKeyguardStatusViewController.setPivotY( - (FONT_HEIGHT - CAP_HEIGHT) / 2048f - * mKeyguardStatusViewController.getClockTextSize()); + // Update Clock Pivot (used by anti-burnin transformations) + mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); // Calculate quick setting heights. int oldMaxHeight = mQsMaxExpansionHeight; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 84bb82beee1cc..ce04fb5999635 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -442,7 +442,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_CLOCK; } - if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) { if (mNetworkController.hasEmergencyCryptKeeperText()) { state |= DISABLE_NOTIFICATION_ICONS; @@ -452,13 +451,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } } - // The shelf will be hidden when dozing with a custom clock, we must show notification - // icons in this occasion. - if (mStatusBarStateController.isDozing() - && mNotificationPanelViewController.hasCustomClock()) { - state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO; - } - if (mOngoingCallController.hasOngoingCall()) { state &= ~DISABLE_ONGOING_CALL_CHIP; } else { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt new file mode 100644 index 0000000000000..914d94522e83f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.keyguard + +import android.content.BroadcastReceiver +import android.testing.AndroidTestingRunner +import android.widget.TextView +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.plugins.Clock +import com.android.systemui.plugins.ClockAnimations +import com.android.systemui.plugins.ClockEvents +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import java.util.TimeZone +import java.util.concurrent.Executor +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ClockEventControllerTest : SysuiTestCase() { + + @JvmField @Rule val mockito = MockitoJUnit.rule() + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var batteryController: BatteryController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var animations: ClockAnimations + @Mock private lateinit var events: ClockEvents + @Mock private lateinit var clock: Clock + @Mock private lateinit var mainExecutor: Executor + @Mock private lateinit var bgExecutor: Executor + @Mock private lateinit var featureFlags: FeatureFlags + + private lateinit var clockEventController: ClockEventController + + @Before + fun setUp() { + whenever(clock.smallClock).thenReturn(TextView(context)) + whenever(clock.largeClock).thenReturn(TextView(context)) + whenever(clock.events).thenReturn(events) + whenever(clock.animations).thenReturn(animations) + + clockEventController = ClockEventController( + statusBarStateController, + broadcastDispatcher, + batteryController, + keyguardUpdateMonitor, + configurationController, + context.resources, + context, + mainExecutor, + bgExecutor, + featureFlags + ) + } + + @Test + fun clockSet_validateInitialization() { + clockEventController.clock = clock + + verify(clock).initialize(any(), anyFloat(), anyFloat()) + } + + @Test + fun clockUnset_validateState() { + clockEventController.clock = clock + clockEventController.clock = null + + assertEquals(clockEventController.clock, null) + } + + @Test + fun themeChanged_verifyClockPaletteUpdated() { + clockEventController.clock = clock + verify(events).onColorPaletteChanged(any(), any(), any()) + + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(configurationController).addCallback(capture(captor)) + captor.value.onThemeChanged() + + verify(events, times(2)).onColorPaletteChanged(any(), any(), any()) + } + + @Test + fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations).charge() + } + + @Test + fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations, times(1)).charge() + } + + @Test + fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(false) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations, never()).charge() + } + + @Test + fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, false) + + verify(animations, never()).charge() + } + + @Test + fun localeCallback_verifyClockNotified() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(broadcastDispatcher).registerReceiver( + capture(captor), any(), eq(null), eq(null), anyInt(), eq(null) + ) + captor.value.onReceive(context, mock()) + + verify(events).onLocaleChanged(any()) + } + + @Test + fun keyguardCallback_visibilityChanged_clockDozeCalled() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + + captor.value.onKeyguardVisibilityChanged(true) + verify(animations, never()).doze(0f) + + captor.value.onKeyguardVisibilityChanged(false) + verify(animations, times(1)).doze(0f) + } + + @Test + fun keyguardCallback_timeFormat_clockNotified() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + captor.value.onTimeFormatChanged("12h") + + verify(events).onTimeFormatChanged(false) + } + + @Test + fun keyguardCallback_timezoneChanged_clockNotified() { + val mockTimeZone = mock() + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + captor.value.onTimeZoneChanged(mockTimeZone) + + verify(events).onTimeZoneChanged(mockTimeZone) + } + + @Test + fun keyguardCallback_userSwitched_clockNotified() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + captor.value.onUserSwitchComplete(10) + + verify(events).onTimeFormatChanged(false) + } + + @Test + fun keyguardCallback_verifyKeyguardChanged() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor() + verify(statusBarStateController).addCallback(capture(captor)) + captor.value.onDozeAmountChanged(0.4f, 0.6f) + + verify(animations).doze(0.4f) + } + + @Test + fun unregisterListeners_validate() { + clockEventController.clock = clock + clockEventController.unregisterListeners() + verify(broadcastDispatcher).unregisterReceiver(any()) + verify(configurationController).removeCallback(any()) + verify(batteryController).removeCallback(any()) + verify(keyguardUpdateMonitor).removeCallback(any()) + verify(statusBarStateController).removeCallback(any()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index b2d9219d44e8e..635ee9ea1a2f5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -19,7 +19,6 @@ package com.android.keyguard; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -41,24 +40,19 @@ import android.widget.RelativeLayout; import androidx.test.filters.SmallTest; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.keyguard.clock.ClockManager; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.plugins.Clock; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -80,22 +74,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock - private SysuiColorExtractor mColorExtractor; - @Mock - private ClockManager mClockManager; + private ClockRegistry mClockRegistry; @Mock KeyguardSliceViewController mKeyguardSliceViewController; @Mock NotificationIconAreaController mNotificationIconAreaController; @Mock - BroadcastDispatcher mBroadcastDispatcher; - @Mock - BatteryController mBatteryController; - @Mock - KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - KeyguardBypassController mBypassController; - @Mock LockscreenSmartspaceController mSmartspaceController; @Mock @@ -103,11 +87,11 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock - private ClockPlugin mClockPlugin; - @Mock - ColorExtractor.GradientColors mGradientColors; + private Clock mClock; @Mock DumpManager mDumpManager; + @Mock + ClockEventController mClockEventController; @Mock private NotificationIconContainer mNotificationIcons; @@ -139,8 +123,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mView.getContext()).thenReturn(getContext()); when(mView.getResources()).thenReturn(mResources); - when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView); - when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView); when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame); when(mClockView.getContext()).thenReturn(getContext()); when(mLargeClockView.getContext()).thenReturn(getContext()); @@ -151,23 +133,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { mController = new KeyguardClockSwitchController( mView, mStatusBarStateController, - mColorExtractor, - mClockManager, + mClockRegistry, mKeyguardSliceViewController, mNotificationIconAreaController, - mBroadcastDispatcher, - mBatteryController, - mKeyguardUpdateMonitor, mSmartspaceController, mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, - mResources, - mDumpManager + mDumpManager, + mClockEventController, + mFeatureFlags ); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors); + when(mClockRegistry.createCurrentClock()).thenReturn(mClock); mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); @@ -214,20 +193,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { verifyAttachment(times(1)); listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView); - verify(mColorExtractor).removeOnColorsChangedListener( - any(ColorExtractor.OnColorsChangedListener.class)); + verify(mClockEventController).unregisterListeners(); } @Test public void testPluginPassesStatusBarState() { - ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class); + ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); mController.init(); - verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture()); + verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture()); - listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin); - verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE); + listenerArgumentCaptor.getValue().onClockChanged(); + verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE); + verify(mClockEventController, times(2)).setClock(mClock); } @Test @@ -284,10 +263,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { } private void verifyAttachment(VerificationMode times) { - verify(mClockManager, times).addOnClockChangedListener( - any(ClockManager.ClockChangedListener.class)); - verify(mColorExtractor, times).addOnColorsChangedListener( - any(ColorExtractor.OnColorsChangedListener.class)); - verify(mView, times).updateColors(mGradientColors); + verify(mClockRegistry, times).registerClockChangeListener( + any(ClockRegistry.ClockChangeListener.class)); + verify(mClockEventController, times).registerListeners(); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 6c6f0acd70859..a0295d09826f0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; @@ -24,56 +23,61 @@ import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertEquals; + import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.graphics.Color; -import android.graphics.Paint.Style; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.TextClock; +import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.plugins.Clock; import com.android.systemui.statusbar.StatusBarState; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) // Need to run on the main thread because KeyguardSliceView$Row init checks for // the main thread before acquiring a wake lock. This class is constructed when -// the keyguard_clcok_switch layout is inflated. +// the keyguard_clock_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) public class KeyguardClockSwitchTest extends SysuiTestCase { - private FrameLayout mClockFrame; - private FrameLayout mLargeClockFrame; - private TextClock mBigClock; + @Mock + ViewGroup mMockKeyguardSliceView; + + @Mock + Clock mClock; + + private FrameLayout mSmallClockFrame; + private FrameLayout mLargeClockFrame; - private AnimatableClockView mClockView; - private AnimatableClockView mLargeClockView; - View mMockKeyguardSliceView; KeyguardClockSwitch mKeyguardClockSwitch; @Before public void setUp() { - mMockKeyguardSliceView = mock(KeyguardSliceView.class); + MockitoAnnotations.initMocks(this); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) .thenReturn(mMockKeyguardSliceView); + when(mClock.getSmallClock()).thenReturn(new TextView(getContext())); + when(mClock.getLargeClock()).thenReturn(new TextView(getContext())); + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() { @@ -93,164 +97,68 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { }); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); - mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); - mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view); + mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large); - mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large); - mBigClock = new TextClock(getContext()); mKeyguardClockSwitch.mChildrenAreLaidOut = true; - MockitoAnnotations.initMocks(this); } @Test - public void onPluginConnected_showPluginClock() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - - assertThat(mClockView.getVisibility()).isEqualTo(GONE); - assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame); + public void noPluginConnected_showNothing() { + mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); + assertEquals(mLargeClockFrame.getChildCount(), 0); + assertEquals(mSmallClockFrame.getChildCount(), 0); } @Test - public void onPluginConnected_showPluginBigClock() { - // GIVEN the plugin returns a view for the big clock - ClockPlugin plugin = mock(ClockPlugin.class); - when(plugin.getBigClockView()).thenReturn(mBigClock); - // WHEN the plugin is connected - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - // THEN the big clock container is visible and it is the parent of the - // big clock view. - assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE); - assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame); + public void pluginConnectedThenDisconnected_showNothing() { + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); + assertEquals(mLargeClockFrame.getChildCount(), 1); + assertEquals(mSmallClockFrame.getChildCount(), 1); + + mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); + assertEquals(mLargeClockFrame.getChildCount(), 0); + assertEquals(mSmallClockFrame.getChildCount(), 0); } @Test - public void onPluginConnected_nullView() { - ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); + public void onPluginConnected_showClock() { + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); + + assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame); + assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame); } @Test public void onPluginConnected_showSecondPluginClock() { // GIVEN a plugin has already connected - ClockPlugin plugin1 = mock(ClockPlugin.class); - when(plugin1.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD); - // WHEN a second plugin is connected - ClockPlugin plugin2 = mock(ClockPlugin.class); - when(plugin2.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD); + Clock otherClock = mock(Clock.class); + when(otherClock.getSmallClock()).thenReturn(new TextView(getContext())); + when(otherClock.getLargeClock()).thenReturn(new TextView(getContext())); + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); + mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD); + // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. - assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame); - assertThat(plugin1.getView().getParent()).isNull(); - } - - @Test - public void onPluginConnected_darkAmountInitialized() { - // GIVEN that the dark amount has already been set - mKeyguardClockSwitch.setDarkAmount(0.5f); - // WHEN a plugin is connected - ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - // THEN dark amount should be initalized on the plugin. - verify(plugin).setDarkAmount(0.5f); - } - - @Test - public void onPluginDisconnected_showDefaultClock() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(GONE); - - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); - - assertThat(plugin.getView().getParent()).isNull(); - } - - @Test - public void onPluginDisconnected_hidePluginBigClock() { - // GIVEN the plugin returns a view for the big clock - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getBigClockView()).thenReturn(pluginView); - // WHEN the plugin is connected and then disconnected - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - // THEN the big lock container is GONE and the big clock view doesn't have - // a parent. - assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE); - assertThat(pluginView.getParent()).isNull(); - } - - @Test - public void onPluginDisconnected_nullView() { - ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); + assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame); + assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame); + assertThat(mClock.getSmallClock().getParent()).isNull(); + assertThat(mClock.getLargeClock().getParent()).isNull(); } @Test public void onPluginDisconnected_secondOfTwoDisconnected() { // GIVEN two plugins are connected - ClockPlugin plugin1 = mock(ClockPlugin.class); - when(plugin1.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD); - ClockPlugin plugin2 = mock(ClockPlugin.class); - when(plugin2.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD); + Clock otherClock = mock(Clock.class); + when(otherClock.getSmallClock()).thenReturn(new TextView(getContext())); + when(otherClock.getLargeClock()).thenReturn(new TextView(getContext())); + mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD); + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); // WHEN the second plugin is disconnected - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - // THEN the default clock should be shown. - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); - assertThat(plugin1.getView().getParent()).isNull(); - assertThat(plugin2.getView().getParent()).isNull(); - } - - @Test - public void onPluginDisconnected_onDestroyView() { - // GIVEN a plugin is connected - ClockPlugin clockPlugin = mock(ClockPlugin.class); - when(clockPlugin.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD); - // WHEN the plugin is disconnected - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - // THEN onDestroyView is called on the plugin - verify(clockPlugin).onDestroyView(); - } - - @Test - public void setTextColor_pluginClockSetTextColor() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - - mKeyguardClockSwitch.setTextColor(Color.WHITE); - - verify(plugin).setTextColor(Color.WHITE); - } - - - @Test - public void setStyle_pluginClockSetStyle() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - Style style = mock(Style.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - - mKeyguardClockSwitch.setStyle(style); - - verify(plugin).setStyle(style); + mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); + // THEN nothing should be shown + assertThat(otherClock.getSmallClock().getParent()).isNull(); + assertThat(otherClock.getLargeClock().getParent()).isNull(); + assertThat(mClock.getSmallClock().getParent()).isNull(); + assertThat(mClock.getLargeClock().getParent()).isNull(); } @Test @@ -262,7 +170,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); - assertThat(mClockFrame.getAlpha()).isEqualTo(0); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); } @Test @@ -271,7 +179,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); - assertThat(mClockFrame.getAlpha()).isEqualTo(0); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); } @Test @@ -281,8 +189,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { mKeyguardClockSwitch.mClockInAnim.end(); mKeyguardClockSwitch.mClockOutAnim.end(); - assertThat(mClockFrame.getAlpha()).isEqualTo(1); - assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1); + assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE); // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); @@ -292,8 +200,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { public void switchingToSmallClockNoAnimation_makesBigClockDisappear() { mKeyguardClockSwitch.switchToClock(SMALL, false); - assertThat(mClockFrame.getAlpha()).isEqualTo(1); - assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1); + assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE); // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java index df506b479b88a..b5e9e8decb4c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java @@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.Utils; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.policy.BatteryController; @@ -53,6 +54,8 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import java.util.concurrent.Executor; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -69,6 +72,12 @@ public class AnimatableClockControllerTest extends SysuiTestCase { private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private Resources mResources; + @Mock + private Executor mMainExecutor; + @Mock + private Executor mBgExecutor; + @Mock + private FeatureFlags mFeatureFlags; private MockitoSession mStaticMockSession; private AnimatableClockController mAnimatableClockController; @@ -96,7 +105,10 @@ public class AnimatableClockControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBatteryController, mKeyguardUpdateMonitor, - mResources + mResources, + mMainExecutor, + mBgExecutor, + mFeatureFlags ); mAnimatableClockController.init(); captureAttachListener(); 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 1cbb8d00cb21e..131eac668af3b 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 @@ -130,13 +130,16 @@ class ClockRegistryTest : SysuiTestCase() { 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"), - ClockMetadata("clock_4", "clock 4") - )) + 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"), + ClockMetadata("clock_4", "clock 4") + ) + ) } @Test @@ -158,11 +161,14 @@ class ClockRegistryTest : SysuiTestCase() { 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") - )) + assertEquals( + list, + listOf( + ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), + ClockMetadata("clock_1", "clock 1"), + ClockMetadata("clock_2", "clock 2") + ) + ) assertEquals(registry.createExampleClock("clock_1"), mockClock) assertEquals(registry.createExampleClock("clock_2"), mockClock) @@ -222,7 +228,7 @@ class ClockRegistryTest : SysuiTestCase() { pluginListener.onPluginConnected(plugin2, mockContext) var changeCallCount = 0 - registry.registerClockChangeListener({ changeCallCount++ }) + registry.registerClockChangeListener { changeCallCount++ } pluginListener.onPluginDisconnected(plugin1) assertEquals(0, changeCallCount) 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 index 78694480ad5b6..2b4a109282ce1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -19,17 +19,31 @@ package com.android.systemui.shared.clocks import android.content.res.Resources import android.graphics.drawable.Drawable import android.testing.AndroidTestingRunner +import android.util.TypedValue import android.view.LayoutInflater +import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.clocks.DefaultClock.Companion.DOZE_COLOR +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import java.util.Locale +import java.util.TimeZone 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.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.notNull import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @@ -39,7 +53,8 @@ class DefaultClockProviderTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() - @Mock private lateinit var mockClockView: AnimatableClockView + @Mock private lateinit var mockSmallClockView: AnimatableClockView + @Mock private lateinit var mockLargeClockView: AnimatableClockView @Mock private lateinit var layoutInflater: LayoutInflater @Mock private lateinit var mockClockThumbnail: Drawable @Mock private lateinit var resources: Resources @@ -47,14 +62,16 @@ class DefaultClockProviderTest : SysuiTestCase() { @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(layoutInflater.inflate(eq(R.layout.clock_default_small), any(), anyBoolean())) + .thenReturn(mockSmallClockView) + whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean())) + .thenReturn(mockLargeClockView) whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null)) .thenReturn(mockClockThumbnail) + whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10)) + whenever(mockLargeClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10)) - provider = DefaultClockProvider(layoutInflater, resources) + provider = DefaultClockProvider(context, layoutInflater, resources) } @Test @@ -71,7 +88,79 @@ class DefaultClockProviderTest : SysuiTestCase() { // 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) + assertEquals(clock.smallClock, mockSmallClockView) + assertEquals(clock.largeClock, mockLargeClockView) + } + + @Test + fun defaultClock_initialize() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.initialize(resources, 0f, 0f) + + verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt()) + verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt()) + verify(mockSmallClockView).onTimeZoneChanged(notNull()) + verify(mockLargeClockView).onTimeZoneChanged(notNull()) + verify(mockSmallClockView).refreshTime() + verify(mockLargeClockView).refreshTime() + verify(mockLargeClockView).setLayoutParams(any()) + } + + @Test + fun defaultClock_events_onTimeTick() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.events.onTimeTick() + + verify(mockSmallClockView).refreshTime() + verify(mockLargeClockView).refreshTime() + } + + @Test + fun defaultClock_events_onTimeFormatChanged() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.events.onTimeFormatChanged(true) + + verify(mockSmallClockView).refreshFormat(true) + verify(mockLargeClockView).refreshFormat(true) + } + + @Test + fun defaultClock_events_onTimeZoneChanged() { + val timeZone = mock() + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.events.onTimeZoneChanged(timeZone) + + verify(mockSmallClockView).onTimeZoneChanged(timeZone) + verify(mockLargeClockView).onTimeZoneChanged(timeZone) + } + + @Test + fun defaultClock_events_onFontSettingChanged() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.events.onFontSettingChanged() + + verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat()) + verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat()) + verify(mockLargeClockView).setLayoutParams(any()) + } + + @Test + fun defaultClock_events_onColorPaletteChanged() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.events.onColorPaletteChanged(resources, true, true) + + verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt()) + verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt()) + } + + @Test + fun defaultClock_events_onLocaleChanged() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + clock.events.onLocaleChanged(Locale.getDefault()) + + verify(mockSmallClockView, times(2)).setLineSpacingScale(anyFloat()) + verify(mockLargeClockView, times(2)).setLineSpacingScale(anyFloat()) + verify(mockSmallClockView, times(2)).refreshFormat() + verify(mockLargeClockView, times(2)).refreshFormat() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 20bf50e41f49d..37c8f6285970c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -333,10 +333,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() { + public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -345,10 +344,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_customClockButNotDozing_clockAndSystemInfoVisible() { + public void disable_NotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -356,40 +354,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } - @Test - public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() { - CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); - - // Make sure they start out as visible - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); - assertEquals(View.VISIBLE, getClockView().getVisibility()); - - fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - - assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); - assertEquals(View.GONE, getClockView().getVisibility()); - } - - @Test - public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() { - CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); - - // Make sure they start out as visible - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); - assertEquals(View.VISIBLE, getClockView().getVisibility()); - - fragment.onDozingChanged(true); - - // When this callback is triggered, we want to make sure the clock and system info - // visibilities are recalculated. Since dozing=true, they shouldn't be visible. - assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); - assertEquals(View.GONE, getClockView().getVisibility()); - } - @Test public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment();