[DO NOT MERGE]Revert "Migrate KeyguardClockSwitch to new shared Clock library"

Revert "Expose the clock version of google-sans"

Revert "Changes to SystemUIGoogle to support clock migration"

Revert submission 19451291-cherrypick-clock_migration-tvip0m638i

Reason for revert: b/241084042
Reverted Changes:
Ia053aaec6:Migrate KeyguardClockSwitch to new shared Clock li...
Ia3e067b93:Changes to SystemUIGoogle to support clock migrati...
I5dbf01a83:Add clock version of google-sans
Ia654d4f41:Expose the clock version of google-sans

Change-Id: I432d3d814c85b9ba20bbb7a88d2e52bd2337c10c
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 d427a57..ff64c78 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 @@
         val out = mutableListOf<List<PositionedGlyphs>>()
         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<PositionedGlyphs>()
             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 cafdc86..0c191607 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 @@
     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 @@
     /** 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 0000000..0137dc3
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/font/clock.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2020, 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.
+*/
+-->
+
+<!--
+** AOD/LockScreen Clock font.
+** Should include all numeric glyphs in all supported locales.
+** Recommended: font with variable width to support AOD => LS animations
+-->
+<!-- TODO: Remove when clock migration complete -->
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+    <font android:typeface="monospace"/>
+</font-family>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 8b8ebf0..6a38507 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">
+        <com.android.systemui.shared.clocks.AnimatableClockView
+            android:id="@+id/animatable_clock_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="start"
+            android:gravity="start"
+            android:textSize="@dimen/clock_text_size"
+            android:fontFamily="@font/clock"
+            android:elegantTextHeight="false"
+            android:singleLine="true"
+            android:fontFeatureSettings="pnum"
+            chargeAnimationDelay="350"
+            dozeWeight="200"
+            lockScreenWeight="400"
+        />
     </FrameLayout>
     <FrameLayout
         android:id="@+id/lockscreen_clock_view_large"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:layout_below="@id/keyguard_slice_view"
-        android:paddingTop="@dimen/keyguard_large_clock_top_padding"
         android:visibility="gone">
+        <com.android.systemui.shared.clocks.AnimatableClockView
+            android:id="@+id/animatable_clock_view_large"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center_horizontal"
+            android:textSize="@dimen/large_clock_text_size"
+            android:fontFamily="@font/clock"
+            android:typeface="monospace"
+            android:elegantTextHeight="false"
+            chargeAnimationDelay="200"
+            dozeWeight="200"
+            lockScreenWeight="400"
+        />
     </FrameLayout>
 
     <!-- Not quite optimal but needed to translate these items as a group. The
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 5f4e310..7a57293 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="@*android:string/config_clockFontFamily"
+    android:fontFamily="@font/clock"
     android:includeFontPadding="false"
     android:textColor="@android:color/white"
     android:format12Hour="@string/dream_time_complication_12_hr_time_format"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 205b117..3fb00a3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -664,7 +664,13 @@
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
     <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_padding">100dp</dimen>
+    <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+
+    <!-- TODO: Remove during migration -->
+    <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+    <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
 
     <dimen name="notification_scrim_corner_radius">32dp</dimen>
 
@@ -884,6 +890,11 @@
          burn-in on AOD. -->
     <dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
 
+    <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+     <!-- TODO: Remove when clock migration complete -->
+    <dimen name="large_clock_text_size">150dp</dimen>
+    <dimen name="clock_text_size">86dp</dimen>
+
     <!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
     <dimen name="default_burn_in_prevention_offset">15dp</dimen>
 
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
index 0139d50..8510a0a 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 @@
 -->
 <com.android.systemui.shared.clocks.AnimatableClockView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/animatable_clock_view_large"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center"
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
index 390ff5e..ec0e427 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_small.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -18,6 +18,7 @@
 -->
 <com.android.systemui.shared.clocks.AnimatableClockView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/animatable_clock_view"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="start"
@@ -25,7 +26,6 @@
     android:textSize="@dimen/small_clock_text_size"
     android:fontFamily="@*android:string/config_clockFontFamily"
     android:elegantTextHeight="false"
-    android:ellipsize="none"
     android:singleLine="true"
     android:fontFeatureSettings="pnum"
     chargeAnimationDelay="350"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 8f1959e..2739d59 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,15 +20,12 @@
 import android.annotation.FloatRange
 import android.annotation.IntRange
 import android.annotation.SuppressLint
-import android.app.compat.ChangeIdStateCache.invalidate
 import android.content.Context
 import android.graphics.Canvas
 import android.text.TextUtils
 import android.text.format.DateFormat
 import android.util.AttributeSet
 import android.widget.TextView
-import com.android.internal.R.attr.contentDescription
-import com.android.internal.R.attr.format
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
@@ -78,12 +75,6 @@
     val lockScreenWeight: Int
         get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
 
-    /**
-     * The number of pixels below the baseline. For fonts that support languages such as
-     * Burmese, this space can be significant and should be accounted for when computing layout.
-     */
-    val bottom get() = paint?.fontMetrics?.bottom ?: 0f
-
     init {
         val animatableClockViewAttributes = context.obtainStyledAttributes(
             attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
@@ -142,15 +133,6 @@
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
-
-            // Because the TextLayout may mutate under the hood as a result of the new text, we
-            // notify the TextAnimator that it may have changed and request a measure/layout. A
-            // crash will occur on the next invocation of setTextStyle if the layout is mutated
-            // without being notified TextInterpolator being notified.
-            if (layout != null) {
-                textAnimator?.updateLayout(layout)
-            }
-            requestLayout()
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 835d6e9..e707d4d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -35,6 +35,8 @@
 private val TAG = ClockRegistry::class.simpleName
 private val DEBUG = true
 
+typealias ClockChangeListener = () -> Unit
+
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
     val context: Context,
@@ -49,11 +51,6 @@
         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 @@
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver = object : ContentObserver(handler) {
         override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
-            clockChangeListeners.forEach { it.onClockChanged() }
+            clockChangeListeners.forEach { it() }
     }
 
     private val pluginListener = object : PluginListener<ClockProviderPlugin> {
@@ -120,11 +117,8 @@
             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 @@
                 if (DEBUG) {
                     Log.i(TAG, "Current clock ($currentId) was connected")
                 }
-                clockChangeListeners.forEach { it.onClockChanged() }
+                clockChangeListeners.forEach { it() }
             }
         }
     }
@@ -145,7 +139,7 @@
 
             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 1d8abe3..5d8da59 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.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 @@
                 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 @@
         }
     }
 
-    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 @@
         }
 
         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 @@
             }
         }
 
+        private var dozeState = AnimationState(0f)
         override fun doze(fraction: Float) {
             val (hasChanged, hasJumped) = dozeState.update(fraction)
             if (hasChanged) {
@@ -181,19 +172,6 @@
 
     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 e0b11d8..c69ff7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -182,6 +182,18 @@
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
     }
 
+    /**
+     * @return the number of pixels below the baseline. For fonts that support languages such as
+     * Burmese, this space can be significant.
+     */
+    public float getBottom() {
+        if (mView.getPaint() != null && mView.getPaint().getFontMetrics() != null) {
+            return mView.getPaint().getFontMetrics().bottom;
+        }
+
+        return 0f;
+    }
+
     /** Animate the clock appearance */
     public void animateAppear() {
         if (!mIsDozing) mView.animateAppearOnLockscreen();
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
deleted file mode 100644
index efd7bcf..0000000
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ /dev/null
@@ -1,153 +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.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.res.Resources
-import android.text.format.DateFormat
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-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 javax.inject.Inject
-
-/**
- * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
- * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
- */
-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
-) {
-    var clock: Clock? = null
-        set(value) {
-            field = value
-            if (value != null) {
-                value.initialize(resources, dozeAmount, 0f)
-            }
-        }
-
-    private var isDozing = false
-        private set
-
-    private var isCharging = false
-    private var dozeAmount = 0f
-    private var isKeyguardShowing = false
-
-    private val configListener = object : ConfigurationController.ConfigurationListener {
-        override fun onThemeChanged() {
-            clock?.events?.onColorPaletteChanged(resources)
-        }
-    }
-
-    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)
-    }
-
-    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 e1fabde..206b8be 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.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 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.
  */
@@ -43,10 +50,17 @@
     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 @@
     @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 @@
      * 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 @@
         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;
 
@@ -190,6 +269,18 @@
     }
 
     /**
+     * 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
      *
      * @return true if desired clock appeared and false if it was already visible
@@ -220,11 +311,64 @@
         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 dd78f1c..ad06e05 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 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.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 @@
  */
 public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
         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 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
     private boolean mOnlyClock = false;
-    private final Executor mUiExecutor;
+    private Executor mUiExecutor;
     private boolean mCanShowDoubleLineClock = true;
-    private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+    private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
             updateDoubleLineClock();
@@ -112,32 +144,34 @@
     public KeyguardClockSwitchController(
             KeyguardClockSwitch keyguardClockSwitch,
             StatusBarStateController statusBarStateController,
-            ClockRegistry clockRegistry,
+            SysuiColorExtractor colorExtractor,
+            ClockManager clockManager,
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
+            BroadcastDispatcher broadcastDispatcher,
+            BatteryController batteryController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
             LockscreenSmartspaceController smartspaceController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SecureSettings secureSettings,
             @Main Executor uiExecutor,
-            DumpManager dumpManager,
-            ClockEventController clockEventController,
-            FeatureFlags featureFlags) {
+            @Main Resources resources,
+            DumpManager dumpManager) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
-        mClockRegistry = clockRegistry;
+        mColorExtractor = colorExtractor;
+        mClockManager = clockManager;
         mKeyguardSliceViewController = keyguardSliceViewController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mBatteryController = batteryController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mSmartspaceController = smartspaceController;
+        mResources = resources;
         mSecureSettings = secureSettings;
         mUiExecutor = uiExecutor;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mDumpManager = dumpManager;
-        mClockEventController = clockEventController;
-
-        mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
-        mClockChangedListener = () -> {
-            setClock(mClockRegistry.createCurrentClock());
-        };
     }
 
     /**
@@ -154,18 +188,40 @@
     public void onInit() {
         mKeyguardSliceViewController.init();
 
-        mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+        mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
         mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
 
+        mClockViewController =
+                new AnimatableClockController(
+                        mView.findViewById(R.id.animatable_clock_view),
+                        mStatusBarStateController,
+                        mBroadcastDispatcher,
+                        mBatteryController,
+                        mKeyguardUpdateMonitor,
+                        mResources);
+        mClockViewController.init();
+
+        mLargeClockViewController =
+                new AnimatableClockController(
+                        mView.findViewById(R.id.animatable_clock_view_large),
+                        mStatusBarStateController,
+                        mBroadcastDispatcher,
+                        mBatteryController,
+                        mKeyguardUpdateMonitor,
+                        mResources);
+        mLargeClockViewController.init();
+
         mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
         mDumpManager.registerDumpable(getClass().toString(), this);
     }
 
     @Override
     protected void onViewAttached() {
-        mClockRegistry.registerClockChangeListener(mClockChangedListener);
-        setClock(mClockRegistry.createCurrentClock());
-        mClockEventController.registerListeners();
+        if (CUSTOM_CLOCKS_ENABLED) {
+            mClockManager.addOnClockChangedListener(mClockChangedListener);
+        }
+        mColorExtractor.addOnColorsChangedListener(mColorsListener);
+        mView.updateColors(getGradientColors());
         mKeyguardClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
@@ -188,6 +244,7 @@
             ksv.setVisibility(View.GONE);
 
             addSmartspaceView(ksvIndex);
+            updateClockLayout();
         }
 
         mSecureSettings.registerContentObserverForUser(
@@ -209,9 +266,11 @@
 
     @Override
     protected void onViewDetached() {
-        mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
-        mClockEventController.unregisterListeners();
-        setClock(null);
+        if (CUSTOM_CLOCKS_ENABLED) {
+            mClockManager.removeOnClockChangedListener(mClockChangedListener);
+        }
+        mColorExtractor.removeOnColorsChangedListener(mColorsListener);
+        mView.setClockPlugin(null, mStatusBarStateController.getState());
 
         mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
 
@@ -250,6 +309,18 @@
         mView.onDensityOrFontScaleChanged();
         mKeyguardClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+
+        updateClockLayout();
+    }
+
+    private void updateClockLayout() {
+        int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
+                R.dimen.keyguard_large_clock_top_margin)
+                - (int) mLargeClockViewController.getBottom();
+        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
+                MATCH_PARENT);
+        lp.topMargin = largeClockTopMargin;
+        mLargeClockFrame.setLayoutParams(lp);
     }
 
     /**
@@ -263,34 +334,46 @@
 
         mCurrentClockSize = clockSize;
 
-        Clock clock = getClock();
         boolean appeared = mView.switchToClock(clockSize, animate);
-        if (clock != null && animate && appeared && clockSize == LARGE) {
-            clock.getAnimations().enter();
+        if (animate && appeared && clockSize == LARGE) {
+            mLargeClockViewController.animateAppear();
+        }
+    }
+
+    public void animateFoldToAod() {
+        if (mClockViewController != null) {
+            mClockViewController.animateFoldAppear();
+            mLargeClockViewController.animateFoldAppear();
         }
     }
 
     /**
-     * Animates the clock view between folded and unfolded states
+     * If we're presenting a custom clock of just the default one.
      */
-    public void animateFoldToAod(float foldFraction) {
-        Clock clock = getClock();
-        if (clock != null) {
-            clock.getAnimations().fold(foldFraction);
-        }
+    public boolean hasCustomClock() {
+        return mView.hasCustomClock();
+    }
+
+    /**
+     * Get the clock text size.
+     */
+    public float getClockTextSize() {
+        return mView.getTextSize();
     }
 
     /**
      * Refresh clock. Called in response to TIME_TICK broadcasts.
      */
     void refresh() {
+        if (mClockViewController != null) {
+            mClockViewController.refreshTime();
+            mLargeClockViewController.refreshTime();
+        }
         if (mSmartspaceController != null) {
             mSmartspaceController.requestSmartspaceUpdate();
         }
-        Clock clock = getClock();
-        if (clock != null) {
-            clock.getEvents().onTimeTick();
-        }
+
+        mView.refresh();
     }
 
     /**
@@ -302,7 +385,7 @@
     void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
         x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
 
-        PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
+        PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
                 x, props, animate);
         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
                 scale, props, animate);
@@ -315,39 +398,25 @@
         }
     }
 
+    void updateTimeZone(TimeZone timeZone) {
+        mView.onTimeZoneChanged(timeZone);
+    }
+
     /**
      * Get y-bottom position of the currently visible clock on the keyguard.
      * We can't directly getBottom() because clock changes positions in AOD for burn-in
      */
     int getClockBottom(int statusBarHeaderHeight) {
-        Clock clock = getClock();
-        if (clock == null) {
-            return 0;
-        }
-
         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+            View clock = mLargeClockFrame.findViewById(
+                    com.android.systemui.R.id.animatable_clock_view_large);
             int frameHeight = mLargeClockFrame.getHeight();
-            int clockHeight = clock.getLargeClock().getHeight();
+            int clockHeight = clock.getHeight();
             return frameHeight / 2 + clockHeight / 2;
         } else {
-            int clockHeight = clock.getSmallClock().getHeight();
-            return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
-        }
-    }
-
-    /**
-     * Get the height of the currently visible clock on the keyguard.
-     */
-    int getClockHeight() {
-        Clock clock = getClock();
-        if (clock == null) {
-            return 0;
-        }
-
-        if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
-            return clock.getLargeClock().getHeight();
-        } else {
-            return clock.getSmallClock().getHeight();
+            return mClockFrame.findViewById(
+                    com.android.systemui.R.id.animatable_clock_view).getHeight()
+                    + statusBarHeaderHeight + mKeyguardClockTopMargin;
         }
     }
 
@@ -362,13 +431,12 @@
         mNotificationIconAreaController.setupAodIcons(nic);
     }
 
-    private void setClock(Clock clock) {
-        mClockEventController.setClock(clock);
-        mView.setClock(clock, mStatusBarStateController.getState());
+    private void setClockPlugin(ClockPlugin plugin) {
+        mView.setClockPlugin(plugin, mStatusBarStateController.getState());
     }
 
-    private Clock getClock() {
-        return mClockEventController.getClock();
+    private ColorExtractor.GradientColors getGradientColors() {
+        return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
     }
 
     private int getCurrentLayoutDirection() {
@@ -401,10 +469,8 @@
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
         pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
-        Clock clock = getClock();
-        if (clock != null) {
-            clock.dump(pw);
-        }
+        mClockViewController.dump(pw);
+        mLargeClockViewController.dump(pw);
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 83e23bd..cb3172d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,11 +17,14 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.graphics.Color;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.GridLayout;
 
+import androidx.core.graphics.ColorUtils;
+
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 
@@ -43,6 +46,7 @@
     private View mMediaHostContainer;
 
     private float mDarkAmount = 0;
+    private int mTextColor;
 
     public KeyguardStatusView(Context context) {
         this(context, null, 0);
@@ -67,6 +71,7 @@
         }
 
         mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
+        mTextColor = mClockView.getCurrentTextColor();
 
         mMediaHostContainer = findViewById(R.id.status_view_media_container);
 
@@ -78,12 +83,15 @@
             return;
         }
         mDarkAmount = darkAmount;
+        mClockView.setDarkAmount(darkAmount);
         CrossFadeHelper.fadeOut(mMediaHostContainer, darkAmount);
         updateDark();
     }
 
     void updateDark() {
+        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
         mKeyguardSlice.setDarkAmount(mDarkAmount);
+        mClockView.setTextColor(blendedTextColor);
     }
 
     /** Sets a translationY value on every child view except for the media view. */
@@ -105,6 +113,7 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mDarkAmount: " + mDarkAmount);
+        pw.println("  mTextColor: " + Integer.toHexString(mTextColor));
         if (mClockView != null) {
             mClockView.dump(pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c715a4e..014d082 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -30,6 +30,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
+import java.util.TimeZone;
+
 import javax.inject.Inject;
 
 /**
@@ -94,6 +96,13 @@
     }
 
     /**
+     * The amount we're in doze.
+     */
+    public void setDarkAmount(float darkAmount) {
+        mView.setDarkAmount(darkAmount);
+    }
+
+    /**
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
      */
@@ -105,10 +114,16 @@
      * Performs fold to aod animation of the clocks (changes font weight from bold to thin).
      * This animation is played when AOD is enabled and foldable device is fully folded, it is
      * displayed on the outer screen
-     * @param foldFraction current fraction of fold animation complete
      */
-    public void animateFoldToAod(float foldFraction) {
-        mKeyguardClockSwitchController.animateFoldToAod(foldFraction);
+    public void animateFoldToAod() {
+        mKeyguardClockSwitchController.animateFoldToAod();
+    }
+
+    /**
+     * If we're presenting a custom clock of just the default one.
+     */
+    public boolean hasCustomClock() {
+        return mKeyguardClockSwitchController.hasCustomClock();
     }
 
     /**
@@ -128,11 +143,24 @@
     }
 
     /**
-     * Update the pivot position based on the parent view
+     * Set pivot x.
      */
-    public void updatePivot(float parentWidth, float parentHeight) {
-        mView.setPivotX(parentWidth / 2f);
-        mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f);
+    public void setPivotX(float pivot) {
+        mView.setPivotX(pivot);
+    }
+
+    /**
+     * Set pivot y.
+     */
+    public void setPivotY(float pivot) {
+        mView.setPivotY(pivot);
+    }
+
+    /**
+     * Get the clock text size.
+     */
+    public float getClockTextSize() {
+        return mKeyguardClockSwitchController.getClockTextSize();
     }
 
     /**
@@ -212,6 +240,11 @@
         }
 
         @Override
+        public void onTimeZoneChanged(TimeZone timeZone) {
+            mKeyguardClockSwitchController.updateTimeZone(timeZone);
+        }
+
+        @Override
         public void onKeyguardVisibilityChanged(boolean showing) {
             if (showing) {
                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 58e9bb8..88e4d90 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -294,6 +294,11 @@
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     private final NotificationIconAreaController mNotificationIconAreaController;
 
+    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+    // changed.
+    private static final int CAP_HEIGHT = 1456;
+    private static final int FONT_HEIGHT = 2163;
+
     /**
      * Maximum time before which we will expand the panel even for slow motions when getting a
      * touch passed over from launcher.
@@ -1122,6 +1127,13 @@
         }
     }
 
+    /**
+     * Returns if there's a custom clock being presented.
+     */
+    public boolean hasCustomClock() {
+        return mKeyguardStatusViewController.hasCustomClock();
+    }
+
     private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
         // TODO: this can be injected.
         mCentralSurfaces = centralSurfaces;
@@ -3909,9 +3921,9 @@
                 public void onAnimationEnd(Animator animation) {
                     endAction.run();
                 }
-            }).setUpdateListener(anim -> {
-                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
             }).start();
+
+        mKeyguardStatusViewController.animateFoldToAod();
     }
 
     /**
@@ -4624,6 +4636,7 @@
         public void onDozeAmountChanged(float linearAmount, float amount) {
             mInterpolatedDarkAmount = amount;
             mLinearDarkAmount = linearAmount;
+            mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
             mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
             positionClockAndNotifications();
         }
@@ -4733,8 +4746,11 @@
             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 fb26600..0848729 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 @@
             state |= DISABLE_CLOCK;
         }
 
+
         if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
             if (mNetworkController.hasEmergencyCryptKeeperText()) {
                 state |= DISABLE_NOTIFICATION_ICONS;
@@ -448,6 +449,13 @@
             }
         }
 
+        // 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 4f39952..0000000
--- 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<ConfigurationController.ConfigurationListener>()
-        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<BatteryController.BatteryStateChangeCallback>()
-        verify(batteryController).addCallback(capture(batteryCaptor))
-        val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        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<BatteryController.BatteryStateChangeCallback>()
-        verify(batteryController).addCallback(capture(batteryCaptor))
-        val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        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<BatteryController.BatteryStateChangeCallback>()
-        verify(batteryController).addCallback(capture(batteryCaptor))
-        val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        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<BatteryController.BatteryStateChangeCallback>()
-        verify(batteryController).addCallback(capture(batteryCaptor))
-        val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        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<BroadcastReceiver>()
-        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<KeyguardUpdateMonitorCallback>()
-        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<KeyguardUpdateMonitorCallback>()
-        verify(keyguardUpdateMonitor).registerCallback(capture(captor))
-        captor.value.onTimeFormatChanged("12h")
-
-        verify(events).onTimeFormatChanged(false)
-    }
-
-    @Test
-    fun keyguardCallback_timezoneChanged_clockNotified() {
-        val mockTimeZone = mock<TimeZone>()
-        clockEventController.clock = clock
-        clockEventController.registerListeners()
-
-        val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-        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<KeyguardUpdateMonitorCallback>()
-        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<StatusBarStateController.StateListener>()
-        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 635ee9e..b2d9219 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 @@
 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 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 @@
     @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 @@
     @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 @@
         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 @@
         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 @@
         verifyAttachment(times(1));
 
         listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
-        verify(mClockEventController).unregisterListeners();
+        verify(mColorExtractor).removeOnColorsChangedListener(
+                any(ColorExtractor.OnColorsChangedListener.class));
     }
 
     @Test
     public void testPluginPassesStatusBarState() {
-        ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
-                ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+        ArgumentCaptor<ClockManager.ClockChangedListener> 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 @@
     }
 
     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 a0295d0..6c6f0ac 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.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 @@
         });
         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 @@
 
         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 @@
 
         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 @@
         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 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 131eac6..1cbb8d0 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 @@
         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 @@
         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 @@
         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 25348f3..56804d5 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
@@ -230,9 +230,10 @@
     }
 
     @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);
 
@@ -241,9 +242,10 @@
     }
 
     @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);
 
@@ -252,6 +254,40 @@
     }
 
     @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();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);