diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index d427a57f3b87e..ff64c78911285 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt @@ -485,13 +485,7 @@ class TextInterpolator( val out = mutableListOf>() for (lineNo in 0 until layout.lineCount) { // Shape all lines. val lineStart = layout.getLineStart(lineNo) - var count = layout.getLineEnd(lineNo) - lineStart - // Do not render the last character in the line if it's a newline and unprintable - val last = lineStart + count - 1 - if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') { - count-- - } - + val count = layout.getLineEnd(lineNo) - lineStart val runs = mutableListOf() TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic, paint) { _, _, glyphs, _ -> 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 cafdc86761733..0c1916074e0c5 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,15 +57,7 @@ interface Clock { val events: ClockEvents /** Triggers for various animations */ - val animations: ClockAnimations - - /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ - fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) { - events.onColorPaletteChanged(resources) - animations.doze(dozeFraction) - animations.fold(foldFraction) - events.onTimeTick() - } + val animation: ClockAnimation /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) { } @@ -88,12 +80,15 @@ interface ClockEvents { /** Call whenever font settings change */ fun onFontSettingChanged() { } - /** Call whenever the color palette should update */ - fun onColorPaletteChanged(resources: Resources) { } + /** Call whenever the color pallete should update */ + fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { } } /** Methods which trigger various clock animations */ -interface ClockAnimations { +interface ClockAnimation { + /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */ + fun initialize(dozeFraction: Float, foldFraction: Float) { } + /** 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 new file mode 100644 index 0000000000000..0137dc39921fe --- /dev/null +++ b/packages/SystemUI/res-keyguard/font/clock.xml @@ -0,0 +1,28 @@ + + + + + + + + \ 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 8b8ebf00e190d..6a38507b2ad75 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -31,14 +31,42 @@ android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:paddingStart="@dimen/clock_padding_start"> + + 12dp - 100dp + -60dp + + + + .7 + + 1 32dp @@ -884,6 +890,11 @@ 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 0139d50dcfba2..8510a0a8b5500 100644 --- a/packages/SystemUI/shared/res/layout/clock_default_large.xml +++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml @@ -18,6 +18,7 @@ --> Unit + /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( val context: Context, @@ -49,11 +51,6 @@ 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() @@ -61,7 +58,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.onClockChanged() } + clockChangeListeners.forEach { it() } } private val pluginListener = object : PluginListener { @@ -120,11 +117,8 @@ 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 } @@ -133,7 +127,7 @@ open class ClockRegistry( if (DEBUG) { Log.i(TAG, "Current clock ($currentId) was connected") } - clockChangeListeners.forEach { it.onClockChanged() } + clockChangeListeners.forEach { it() } } } } @@ -145,7 +139,7 @@ open class ClockRegistry( if (currentId == clock.clockId) { Log.w(TAG, "Current clock ($currentId) was disconnected") - clockChangeListeners.forEach { it.onClockChanged() } + clockChangeListeners.forEach { it() } } } } 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 1d8abe3fdf420..5d8da59857686 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 @@ -19,9 +19,10 @@ 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.ClockAnimations +import com.android.systemui.plugins.ClockAnimation import com.android.systemui.plugins.ClockEvents import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata @@ -101,13 +102,10 @@ class DefaultClock( TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() ) - recomputePadding() } - override fun onColorPaletteChanged(resources: Resources) { - val color = resources.getColor(android.R.color.system_accent1_100) - clocks.forEach { it.setColors(DOZE_COLOR, color) } - } + override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) = + clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) } override fun onLocaleChanged(locale: Locale) { val nf = NumberFormat.getInstance(locale) @@ -121,17 +119,8 @@ class DefaultClock( } } - override var animations = DefaultClockAnimations(0f, 0f) - private set - - inner class DefaultClockAnimations( - dozeFraction: Float, - foldFraction: Float - ) : ClockAnimations { - private var foldState = AnimationState(0f) - private var dozeState = AnimationState(0f) - - init { + override val animation = object : ClockAnimation { + override fun initialize(dozeFraction: Float, foldFraction: Float) { dozeState = AnimationState(dozeFraction) foldState = AnimationState(foldFraction) @@ -143,13 +132,14 @@ 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) { @@ -157,6 +147,7 @@ class DefaultClock( } } + private var dozeState = AnimationState(0f) override fun doze(fraction: Float) { val (hasChanged, hasJumped) = dozeState.update(fraction) if (hasChanged) { @@ -181,19 +172,6 @@ class DefaultClock( init { events.onLocaleChanged(Locale.getDefault()) - 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) - events.onTimeTick() - } - - private fun recomputePadding() { - val topPadding = -1 * (largeClock.bottom.toInt() - 180) - largeClock.setPadding(0, topPadding, 0, 0) } override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) } diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index e0b11d83bf75e..c69ff7ee1cd80 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -182,6 +182,18 @@ public class AnimatableClockController extends ViewController 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) - } - - fun unregisterListeners() { - broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver) - configurationController.removeCallback(configListener) - batteryController.removeCallback(batteryCallback) - keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) - statusBarStateController.removeCallback(statusBarStateListener) - } - - /** - * Dump information for debugging - */ - fun dump(pw: PrintWriter) { - pw.println(this) - clock?.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 e1fabdef36517..206b8bee323c8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -5,8 +5,10 @@ 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.Log; +import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -15,14 +17,19 @@ 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.Clock; +import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.shared.clocks.AnimatableClockView; 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. */ @@ -42,11 +49,18 @@ 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 mSmallClockFrame; + private FrameLayout mClockFrame; private FrameLayout mLargeClockFrame; + private AnimatableClockView mClockView; + private AnimatableClockView mLargeClockView; private View mStatusArea; private int mSmartspaceTopOffset; @@ -66,6 +80,12 @@ 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; @@ -77,38 +97,97 @@ 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(); - mSmallClockFrame = findViewById(R.id.lockscreen_clock_view); + mClockFrame = findViewById(R.id.lockscreen_clock_view); + mClockView = findViewById(R.id.animatable_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 setClock(Clock clock, int statusBarState) { + void setClockPlugin(ClockPlugin plugin, int statusBarState) { // Disconnect from existing plugin. - mSmallClockFrame.removeAllViews(); - mLargeClockFrame.removeAllViews(); - - if (clock == null) { - Log.e(TAG, "No clock being shown"); + 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); return; } - // Attach small and big clock views to hierarchy. - mSmallClockFrame.addView(clock.getSmallClock(), -1, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - mLargeClockFrame.addView(clock.getLargeClock()); + 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); + } } private void updateClockViews(boolean useLargeClock, boolean animate) { @@ -124,14 +203,14 @@ public class KeyguardClockSwitch extends RelativeLayout { int direction = 1; float statusAreaYTranslation; if (useLargeClock) { - out = mSmallClockFrame; + out = mClockFrame; in = mLargeClockFrame; if (indexOfChild(in) == -1) addView(in); direction = -1; - statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop() + statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop() + mSmartspaceTopOffset; } else { - in = mSmallClockFrame; + in = mClockFrame; out = mLargeClockFrame; statusAreaYTranslation = 0f; @@ -189,6 +268,18 @@ 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 * @@ -220,11 +311,64 @@ 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(" mClockFrame: " + mSmallClockFrame); + pw.println(" mClockPlugin: " + mClockPlugin); + pw.println(" mClockFrame: " + mClockFrame); 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 dd78f1cef78ca..ad06e05c2d6e9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -22,6 +22,8 @@ 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; @@ -30,30 +32,36 @@ 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.Clock; +import com.android.systemui.plugins.ClockPlugin; 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; @@ -63,24 +71,48 @@ 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 ClockRegistry mClockRegistry; + private final SysuiColorExtractor mColorExtractor; + private final ClockManager mClockManager; 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 frames for both small and large sizes */ - private FrameLayout mSmallClockFrame; // top aligned clock + /** + * Clock for both small and large sizes + */ + private AnimatableClockController mClockViewController; + private FrameLayout mClockFrame; // top aligned clock + private AnimatableClockController mLargeClockViewController; private FrameLayout mLargeClockFrame; // centered clock @KeyguardClockSwitch.ClockSize private int mCurrentClockSize = SMALL; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private int mKeyguardClockTopMargin = 0; - private final ClockRegistry.ClockChangeListener mClockChangedListener; + + /** + * 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 ViewGroup mStatusArea; // If set will replace keyguard_slice_view @@ -89,9 +121,9 @@ public class KeyguardClockSwitchController extends ViewController { - setClock(mClockRegistry.createCurrentClock()); - }; } /** @@ -154,18 +188,40 @@ public class KeyguardClockSwitchController extends ViewController { - mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()); }).start(); + + mKeyguardStatusViewController.animateFoldToAod(); } /** @@ -4625,6 +4637,7 @@ public final class NotificationPanelViewController extends PanelViewController { public void onDozeAmountChanged(float linearAmount, float amount) { mInterpolatedDarkAmount = amount; mLinearDarkAmount = linearAmount; + mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount); mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount); positionClockAndNotifications(); } @@ -4734,8 +4747,11 @@ public final class NotificationPanelViewController extends PanelViewController { updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount()); setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); - // Update Clock Pivot (used by anti-burnin transformations) - mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); + // Update Clock Pivot + mKeyguardStatusViewController.setPivotX(((float) mView.getWidth()) / 2f); + mKeyguardStatusViewController.setPivotY( + (FONT_HEIGHT - CAP_HEIGHT) / 2048f + * mKeyguardStatusViewController.getClockTextSize()); // 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 b94f33a8c0459..29b4d5bfbc827 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 @@ -439,6 +439,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_CLOCK; } + if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) { if (mNetworkController.hasEmergencyCryptKeeperText()) { state |= DISABLE_NOTIFICATION_ICONS; @@ -448,6 +449,13 @@ 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 deleted file mode 100644 index 4f3995252b544..0000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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.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 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 - - 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 - ) - } - - @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 - clockEventController.registerListeners() - - val captor = argumentCaptor() - verify(configurationController).addCallback(capture(captor)) - captor.value.onThemeChanged() - - verify(events).onColorPaletteChanged(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.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 635ee9ea1a2f5..b2d9219d44e8e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -19,6 +19,7 @@ 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; @@ -40,19 +41,24 @@ 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.Clock; +import com.android.systemui.plugins.ClockPlugin; 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; @@ -74,12 +80,22 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock - private ClockRegistry mClockRegistry; + private SysuiColorExtractor mColorExtractor; + @Mock + private ClockManager mClockManager; @Mock KeyguardSliceViewController mKeyguardSliceViewController; @Mock NotificationIconAreaController mNotificationIconAreaController; @Mock + BroadcastDispatcher mBroadcastDispatcher; + @Mock + BatteryController mBatteryController; + @Mock + KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + KeyguardBypassController mBypassController; + @Mock LockscreenSmartspaceController mSmartspaceController; @Mock @@ -87,11 +103,11 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock - private Clock mClock; + private ClockPlugin mClockPlugin; + @Mock + ColorExtractor.GradientColors mGradientColors; @Mock DumpManager mDumpManager; - @Mock - ClockEventController mClockEventController; @Mock private NotificationIconContainer mNotificationIcons; @@ -123,6 +139,8 @@ 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()); @@ -133,20 +151,23 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { mController = new KeyguardClockSwitchController( mView, mStatusBarStateController, - mClockRegistry, + mColorExtractor, + mClockManager, mKeyguardSliceViewController, mNotificationIconAreaController, + mBroadcastDispatcher, + mBatteryController, + mKeyguardUpdateMonitor, mSmartspaceController, mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, - mDumpManager, - mClockEventController, - mFeatureFlags + mResources, + mDumpManager ); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - when(mClockRegistry.createCurrentClock()).thenReturn(mClock); + when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors); mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); @@ -193,20 +214,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { verifyAttachment(times(1)); listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView); - verify(mClockEventController).unregisterListeners(); + verify(mColorExtractor).removeOnColorsChangedListener( + any(ColorExtractor.OnColorsChangedListener.class)); } @Test public void testPluginPassesStatusBarState() { - ArgumentCaptor listenerArgumentCaptor = - ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); + ArgumentCaptor listenerArgumentCaptor = + ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class); mController.init(); - verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture()); + verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture()); - listenerArgumentCaptor.getValue().onClockChanged(); - verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE); - verify(mClockEventController, times(2)).setClock(mClock); + listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin); + verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE); } @Test @@ -263,8 +284,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { } private void verifyAttachment(VerificationMode times) { - verify(mClockRegistry, times).registerClockChangeListener( - any(ClockRegistry.ClockChangeListener.class)); - verify(mClockEventController, times).registerListeners(); + verify(mClockManager, times).addOnClockChangedListener( + any(ClockManager.ClockChangedListener.class)); + verify(mColorExtractor, times).addOnColorsChangedListener( + any(ColorExtractor.OnColorsChangedListener.class)); + verify(mView, times).updateColors(mGradientColors); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index a0295d09826f0..6c6f0acd70859 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; @@ -23,61 +24,56 @@ 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.TextView; +import android.widget.TextClock; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.Clock; +import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.shared.clocks.AnimatableClockView; 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_clock_switch layout is inflated. +// the keyguard_clcok_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) public class KeyguardClockSwitchTest extends SysuiTestCase { - @Mock - ViewGroup mMockKeyguardSliceView; - - @Mock - Clock mClock; - - private FrameLayout mSmallClockFrame; + private FrameLayout mClockFrame; private FrameLayout mLargeClockFrame; + private TextClock mBigClock; + private AnimatableClockView mClockView; + private AnimatableClockView mLargeClockView; + View mMockKeyguardSliceView; KeyguardClockSwitch mKeyguardClockSwitch; @Before public void setUp() { - MockitoAnnotations.initMocks(this); + mMockKeyguardSliceView = mock(KeyguardSliceView.class); 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() { @@ -97,68 +93,164 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { }); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); - mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); + mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); + mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_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 noPluginConnected_showNothing() { - mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); - assertEquals(mLargeClockFrame.getChildCount(), 0); - assertEquals(mSmallClockFrame.getChildCount(), 0); + 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); } @Test - 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); + 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); } @Test - public void onPluginConnected_showClock() { - mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); - - assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame); - assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame); + public void onPluginConnected_nullView() { + ClockPlugin plugin = mock(ClockPlugin.class); + mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); + assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); } @Test public void onPluginConnected_showSecondPluginClock() { // GIVEN a plugin has already connected - 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); - + 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); // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. - assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame); - assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame); - assertThat(mClock.getSmallClock().getParent()).isNull(); - assertThat(mClock.getLargeClock().getParent()).isNull(); + 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); } @Test public void onPluginDisconnected_secondOfTwoDisconnected() { // GIVEN two plugins are connected - 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); + 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); // WHEN the second plugin is disconnected - 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(); + 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); } @Test @@ -170,7 +262,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); - assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); + assertThat(mClockFrame.getAlpha()).isEqualTo(0); } @Test @@ -179,7 +271,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); - assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); + assertThat(mClockFrame.getAlpha()).isEqualTo(0); } @Test @@ -189,8 +281,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { mKeyguardClockSwitch.mClockInAnim.end(); mKeyguardClockSwitch.mClockOutAnim.end(); - assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1); - assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mClockFrame.getAlpha()).isEqualTo(1); + assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE); // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); @@ -200,8 +292,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { public void switchingToSmallClockNoAnimation_makesBigClockDisappear() { mKeyguardClockSwitch.switchToClock(SMALL, false); - assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1); - assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mClockFrame.getAlpha()).isEqualTo(1); + assertThat(mClockFrame.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/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 131eac668af3b..1cbb8d00cb21e 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,16 +130,13 @@ 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 @@ -161,14 +158,11 @@ 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) @@ -228,7 +222,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/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index f7a4314835154..52a573f24afb2 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 @@ -329,9 +329,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_isDozing_clockAndSystemInfoVisible() { + public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -340,9 +341,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_NotDozing_clockAndSystemInfoVisible() { + public void disable_customClockButNotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -350,6 +352,40 @@ 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();