Merge changes from topic "clock_cherrypick_fixes" into tm-qpr-dev

* changes:
  [DO NOT MERGE] Initialize TimeZone during DefaultClockProvider init
  [DO NOT MERGE] Correct large clock position
  [DO NOT MERGE] Additional Tests for DefaultClock
  [DO NOT MERGE] Update DefaultClockProvider colors
  [DO NOT MERGE] Moving Text Protection to Clock Event Controller
  [DO NOT MERGE] Adding Region Sampler to Animatable Clocks
  [DO NOT MERGE] Revert "[DO NOT MERGE]Revert "Migrate KeyguardClockSwitch to new..."
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 3ca8dfe..51cc195 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -39,6 +39,7 @@
 -packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
 -packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
 -packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt
+-packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
 -packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
 -packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
 -packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
@@ -156,6 +157,8 @@
 -packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
 -packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
 -packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+-packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt
+-packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
 -packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
 -packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
 -packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -246,6 +249,7 @@
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
 -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -575,6 +579,7 @@
 -packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt
 -packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
 -packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+-packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
 -packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
 -packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -633,6 +638,7 @@
 -packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
 -packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
@@ -720,6 +726,7 @@
 -packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 0c191607..01e5d86 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -13,9 +13,9 @@
  */
 package com.android.systemui.plugins
 
+import android.content.res.Resources
 import android.graphics.drawable.Drawable
 import android.view.View
-import com.android.internal.colorextraction.ColorExtractor
 import com.android.systemui.plugins.annotations.ProvidesInterface
 import java.io.PrintWriter
 import java.util.Locale
@@ -57,7 +57,15 @@
     val events: ClockEvents
 
     /** Triggers for various animations */
-    val animation: ClockAnimation
+    val animations: ClockAnimations
+
+    /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
+    fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+        events.onColorPaletteChanged(resources, true, true)
+        animations.doze(dozeFraction)
+        animations.fold(foldFraction)
+        events.onTimeTick()
+    }
 
     /** Optional method for dumping debug information */
     fun dump(pw: PrintWriter) { }
@@ -80,15 +88,16 @@
     /** Call whenever font settings change */
     fun onFontSettingChanged() { }
 
-    /** Call whenever the color pallete should update */
-    fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
+    /** Call whenever the color palette should update */
+    fun onColorPaletteChanged(
+            resources: Resources,
+            smallClockIsDark: Boolean,
+            largeClockIsDark: Boolean
+    ) { }
 }
 
 /** Methods which trigger various clock animations */
-interface ClockAnimation {
-    /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
-    fun initialize(dozeFraction: Float, foldFraction: Float) { }
-
+interface ClockAnimations {
     /** Runs an enter animation (if any) */
     fun enter() { }
 
diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
deleted file mode 100644
index 0137dc3..0000000
--- a/packages/SystemUI/res-keyguard/font/clock.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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 6a38507..3ad7c8c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -23,6 +23,7 @@
     android:id="@+id/keyguard_clock_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:clipChildren="false"
     android:layout_gravity="center_horizontal|top">
     <FrameLayout
         android:id="@+id/lockscreen_clock_view"
@@ -30,44 +31,13 @@
         android:layout_height="wrap_content"
         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>
+        android:paddingStart="@dimen/clock_padding_start" />
     <FrameLayout
         android:id="@+id/lockscreen_clock_view_large"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/keyguard_slice_view"
-        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>
+        android:layout_height="match_parent"
+        android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
+        android:visibility="gone" />
 
     <!-- Not quite optimal but needed to translate these items as a group. The
          NotificationIconContainer has its own logic for translation. -->
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 6ba88a6..acb47f7 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -19,7 +19,7 @@
     android:id="@+id/time_view"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:fontFamily="@font/clock"
+    android:fontFamily="@*android:string/config_clockFontFamily"
     android:textColor="@android:color/white"
     android:format12Hour="@string/dream_time_complication_12_hr_time_format"
     android:format24Hour="@string/dream_time_complication_24_hr_time_format"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9820237..e3be365 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -671,12 +671,6 @@
     <!-- With the large clock, move up slightly from the center -->
     <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
 
-    <!-- TODO: Remove during migration -->
-    <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
-    <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
-    <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
-    <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
-
     <dimen name="notification_scrim_corner_radius">32dp</dimen>
 
     <!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
@@ -895,11 +889,6 @@
          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 8510a0a..0139d50 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_large.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -18,7 +18,6 @@
 -->
 <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 ec0e427..ff6d7f9 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_small.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -18,14 +18,14 @@
 -->
 <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_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_gravity="start"
     android:gravity="start"
     android:textSize="@dimen/small_clock_text_size"
     android:fontFamily="@*android:string/config_clockFontFamily"
     android:elegantTextHeight="false"
+    android: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 c2fda82..b3b75f6 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,12 +20,16 @@
 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.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.TextAnimator
@@ -74,7 +78,8 @@
     private var textAnimator: TextAnimator? = null
     private var onTextAnimatorInitialized: Runnable? = null
 
-    var timeOverrideInMillis: Long? = null
+    @VisibleForTesting var isAnimationEnabled: Boolean = true
+    @VisibleForTesting var timeOverrideInMillis: Long? = null
 
     val dozingWeight: Int
         get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
@@ -82,6 +87,12 @@
     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
@@ -141,6 +152,15 @@
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
             lastTextUpdate = getTimestamp()
+
+            // 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()
         }
     }
 
@@ -200,7 +220,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        if (textAnimator == null) {
+        if (isAnimationEnabled && textAnimator == null) {
             return
         }
         setTextStyle(
@@ -216,7 +236,7 @@
             weight = lockScreenWeight,
             textSize = -1f,
             color = lockScreenColor,
-            animate = true,
+            animate = isAnimationEnabled,
             duration = APPEAR_ANIM_DURATION,
             delay = 0,
             onAnimationEnd = null
@@ -224,7 +244,7 @@
     }
 
     fun animateFoldAppear(animate: Boolean = true) {
-        if (textAnimator == null) {
+        if (isAnimationEnabled && textAnimator == null) {
             return
         }
         setTextStyle(
@@ -240,7 +260,7 @@
             weight = dozingWeightInternal,
             textSize = -1f,
             color = dozingColor,
-            animate = animate,
+            animate = animate && isAnimationEnabled,
             interpolator = Interpolators.EMPHASIZED_DECELERATE,
             duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
             delay = 0,
@@ -258,7 +278,7 @@
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
                 textSize = -1f,
                 color = null,
-                animate = true,
+                animate = isAnimationEnabled,
                 duration = CHARGE_ANIM_DURATION_PHASE_1,
                 delay = 0,
                 onAnimationEnd = null
@@ -268,7 +288,7 @@
             weight = if (isDozing()) lockScreenWeight else dozingWeight,
             textSize = -1f,
             color = null,
-            animate = true,
+            animate = isAnimationEnabled,
             duration = CHARGE_ANIM_DURATION_PHASE_0,
             delay = chargeAnimationDelay.toLong(),
             onAnimationEnd = startAnimPhase2
@@ -280,7 +300,7 @@
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
             color = if (isDozing) dozingColor else lockScreenColor,
-            animate = animate,
+            animate = animate && isAnimationEnabled,
             duration = DOZE_ANIM_DURATION,
             delay = 0,
             onAnimationEnd = null
@@ -314,7 +334,7 @@
                 weight = weight,
                 textSize = textSize,
                 color = color,
-                animate = animate,
+                animate = animate && isAnimationEnabled,
                 duration = duration,
                 interpolator = interpolator,
                 delay = delay,
@@ -352,7 +372,7 @@
             weight = weight,
             textSize = textSize,
             color = color,
-            animate = animate,
+            animate = animate && isAnimationEnabled,
             interpolator = null,
             duration = duration,
             delay = delay,
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 e707d4d..835d6e9 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,8 +35,6 @@
 private val TAG = ClockRegistry::class.simpleName
 private val DEBUG = true
 
-typealias ClockChangeListener = () -> Unit
-
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
     val context: Context,
@@ -51,6 +49,11 @@
         defaultClockProvider: DefaultClockProvider
     ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
 
+    // Usually this would be a typealias, but a SAM provides better java interop
+    fun interface ClockChangeListener {
+        fun onClockChanged()
+    }
+
     var isEnabled: Boolean = false
 
     private val gson = Gson()
@@ -58,7 +61,7 @@
     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() }
+            clockChangeListeners.forEach { it.onClockChanged() }
     }
 
     private val pluginListener = object : PluginListener<ClockProviderPlugin> {
@@ -117,8 +120,11 @@
             val id = clock.clockId
             val current = availableClocks[id]
             if (current != null) {
-                Log.e(TAG, "Clock Id conflict: $id is registered by both " +
-                    "${provider::class.simpleName} and ${current.provider::class.simpleName}")
+                Log.e(
+                    TAG,
+                    "Clock Id conflict: $id is registered by both " +
+                        "${provider::class.simpleName} and ${current.provider::class.simpleName}"
+                )
                 return
             }
 
@@ -127,7 +133,7 @@
                 if (DEBUG) {
                     Log.i(TAG, "Current clock ($currentId) was connected")
                 }
-                clockChangeListeners.forEach { it() }
+                clockChangeListeners.forEach { it.onClockChanged() }
             }
         }
     }
@@ -139,7 +145,7 @@
 
             if (currentId == clock.clockId) {
                 Log.w(TAG, "Current clock ($currentId) was disconnected")
-                clockChangeListeners.forEach { it() }
+                clockChangeListeners.forEach { it.onClockChanged() }
             }
         }
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 5d8da59..b5e5766 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -13,16 +13,18 @@
  */
 package com.android.systemui.shared.clocks
 
+import android.content.Context
 import android.content.res.Resources
 import android.graphics.Color
 import android.graphics.drawable.Drawable
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
-import com.android.internal.colorextraction.ColorExtractor
+import android.widget.FrameLayout
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimation
+import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockEvents
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -39,6 +41,7 @@
 
 /** Provides the default system clock */
 class DefaultClockProvider @Inject constructor(
+    val ctx: Context,
     val layoutInflater: LayoutInflater,
     @Main val resources: Resources
 ) : ClockProvider {
@@ -49,7 +52,7 @@
         if (id != DEFAULT_CLOCK_ID) {
             throw IllegalArgumentException("$id is unsupported by $TAG")
         }
-        return DefaultClock(layoutInflater, resources)
+        return DefaultClock(ctx, layoutInflater, resources)
     }
 
     override fun getClockThumbnail(id: ClockId): Drawable? {
@@ -69,14 +72,13 @@
  * AnimatableClockView used by the existing lockscreen clock.
  */
 class DefaultClock(
-    private val layoutInflater: LayoutInflater,
-    private val resources: Resources
+        ctx: Context,
+        private val layoutInflater: LayoutInflater,
+        private val resources: Resources
 ) : Clock {
-    override val smallClock =
-        layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
-    override val largeClock =
-        layoutInflater.inflate(R.layout.clock_default_large, null) as AnimatableClockView
-    private val clocks = listOf(smallClock, largeClock)
+    override val smallClock: AnimatableClockView
+    override val largeClock: AnimatableClockView
+    private val clocks get() = listOf(smallClock, largeClock)
 
     private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
     private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
@@ -84,7 +86,43 @@
         resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
     private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
 
-    override val events = object : ClockEvents {
+    override val events: ClockEvents
+    override lateinit var animations: ClockAnimations
+        private set
+
+    init {
+        val parent = FrameLayout(ctx)
+
+        smallClock = layoutInflater.inflate(
+            R.layout.clock_default_small,
+            parent,
+            false
+        ) as AnimatableClockView
+
+        largeClock = layoutInflater.inflate(
+            R.layout.clock_default_large,
+            parent,
+            false
+        ) as AnimatableClockView
+
+        events = DefaultClockEvents()
+        animations = DefaultClockAnimations(0f, 0f)
+
+        events.onLocaleChanged(Locale.getDefault())
+
+        // DOZE_COLOR is a placeholder, and will be assigned correctly in initialize
+        clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
+    }
+
+    override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+        recomputePadding()
+        animations = DefaultClockAnimations(dozeFraction, foldFraction)
+        events.onColorPaletteChanged(resources, true, true)
+        events.onTimeZoneChanged(TimeZone.getDefault())
+        events.onTimeTick()
+    }
+
+    inner class DefaultClockEvents() : ClockEvents {
         override fun onTimeTick() = clocks.forEach { it.refreshTime() }
 
         override fun onTimeFormatChanged(is24Hr: Boolean) =
@@ -102,10 +140,17 @@
                 TypedValue.COMPLEX_UNIT_PX,
                 resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
             )
+            recomputePadding()
         }
 
-        override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
-            clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
+        override fun onColorPaletteChanged(
+                resources: Resources,
+                smallClockIsDark: Boolean,
+                largeClockIsDark: Boolean
+        ) {
+            updateClockColor(smallClock, smallClockIsDark)
+            updateClockColor(largeClock, largeClockIsDark)
+        }
 
         override fun onLocaleChanged(locale: Locale) {
             val nf = NumberFormat.getInstance(locale)
@@ -119,8 +164,14 @@
         }
     }
 
-    override val animation = object : ClockAnimation {
-        override fun initialize(dozeFraction: Float, foldFraction: Float) {
+    inner class DefaultClockAnimations(
+        dozeFraction: Float,
+        foldFraction: Float
+    ) : ClockAnimations {
+        private var foldState = AnimationState(0f)
+        private var dozeState = AnimationState(0f)
+
+        init {
             dozeState = AnimationState(dozeFraction)
             foldState = AnimationState(foldFraction)
 
@@ -132,14 +183,13 @@
         }
 
         override fun enter() {
-            if (dozeState.isActive) {
+            if (!dozeState.isActive) {
                 clocks.forEach { it.animateAppearOnLockscreen() }
             }
         }
 
         override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
 
-        private var foldState = AnimationState(0f)
         override fun fold(fraction: Float) {
             val (hasChanged, hasJumped) = foldState.update(fraction)
             if (hasChanged) {
@@ -147,7 +197,6 @@
             }
         }
 
-        private var dozeState = AnimationState(0f)
         override fun doze(fraction: Float) {
             val (hasChanged, hasJumped) = dozeState.update(fraction)
             if (hasChanged) {
@@ -170,14 +219,26 @@
         }
     }
 
-    init {
-        events.onLocaleChanged(Locale.getDefault())
+    private fun updateClockColor(clock: AnimatableClockView, isRegionDark: Boolean) {
+        val color = if (isRegionDark) {
+            resources.getColor(android.R.color.system_accent1_100)
+        } else {
+            resources.getColor(android.R.color.system_accent2_600)
+        }
+        clock.setColors(DOZE_COLOR, color)
+        clock.animateAppearOnLockscreen()
+    }
+
+    private fun recomputePadding() {
+        val lp = largeClock.getLayoutParams() as FrameLayout.LayoutParams
+        lp.topMargin = (-0.5f * largeClock.bottom).toInt()
+        largeClock.setLayoutParams(lp)
     }
 
     override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
 
     companion object {
-        private const val DOZE_COLOR = Color.WHITE
+        @VisibleForTesting const val DOZE_COLOR = Color.WHITE
         private const val FORMAT_NUMBER = 1234567890
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 3c68c0d..6064be9 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -22,7 +22,9 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.icu.text.NumberFormat;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -30,16 +32,24 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.ViewController;
 
 import java.io.PrintWriter;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.TimeZone;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
 
 /**
  * Controller for an AnimatableClockView on the keyguard. Instantiated by
@@ -54,7 +64,10 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final BatteryController mBatteryController;
     private final int mDozingColor = Color.WHITE;
+    private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty();
+    private Rect mSamplingBounds = new Rect();
     private int mLockScreenColor;
+    private final boolean mRegionSamplingEnabled;
 
     private boolean mIsDozing;
     private boolean mIsCharging;
@@ -67,13 +80,17 @@
     private final float mBurmeseLineSpacing;
     private final float mDefaultLineSpacing;
 
+    @Inject
     public AnimatableClockController(
             AnimatableClockView view,
             StatusBarStateController statusBarStateController,
             BroadcastDispatcher broadcastDispatcher,
             BatteryController batteryController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @Main Resources resources
+            @Main Resources resources,
+            @Main Executor mainExecutor,
+            @Background Executor bgExecutor,
+            FeatureFlags featureFlags
     ) {
         super(view);
         mStatusBarStateController = statusBarStateController;
@@ -86,6 +103,40 @@
                 R.dimen.keyguard_clock_line_spacing_scale_burmese);
         mDefaultLineSpacing = resources.getFloat(
                 R.dimen.keyguard_clock_line_spacing_scale);
+
+        mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING);
+        if (!mRegionSamplingEnabled) {
+            return;
+        }
+
+        mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView,
+                new RegionSamplingHelper.SamplingCallback() {
+                    @Override
+                    public void onRegionDarknessChanged(boolean isRegionDark) {
+                        if (isRegionDark) {
+                            mLockScreenColor = Color.WHITE;
+                        } else {
+                            mLockScreenColor = Color.BLACK;
+                        }
+                        initColors();
+                    }
+
+                    @Override
+                    public Rect getSampledRegion(View sampledView) {
+                        mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(),
+                                sampledView.getRight(), sampledView.getBottom());
+                        return mSamplingBounds;
+                    }
+
+                    @Override
+                    public boolean isSamplingEnabled() {
+                        return mRegionSamplingEnabled;
+                    }
+                }, mainExecutor, bgExecutor)
+        );
+        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+            regionSamplingHelper.setWindowVisible(true);
+        });
     }
 
     private void reset() {
@@ -169,6 +220,10 @@
 
         mStatusBarStateController.addCallback(mStatusBarStateListener);
 
+        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+            regionSamplingHelper.start(mSamplingBounds);
+        });
+
         mView.onTimeZoneChanged(TimeZone.getDefault());
         initColors();
         mView.animateDoze(mIsDozing, false);
@@ -180,18 +235,9 @@
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mBatteryController.removeCallback(mBatteryCallback);
         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;
+        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+            regionSamplingHelper.stop();
+        });
     }
 
     /** Animate the clock appearance */
@@ -235,8 +281,10 @@
     }
 
     private void initColors() {
-        mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
-                com.android.systemui.R.attr.wallpaperTextColorAccent);
+        if (!mRegionSamplingEnabled) {
+            mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
+                    com.android.systemui.R.attr.wallpaperTextColorAccent);
+        }
         mView.setColors(mDozingColor, mLockScreenColor);
         mView.animateDoze(mIsDozing, false);
     }
@@ -247,5 +295,8 @@
     public void dump(@NonNull PrintWriter pw) {
         pw.println(this);
         mView.dump(pw);
+        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+            regionSamplingHelper.dump(pw);
+        });
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
new file mode 100644
index 0000000..0e1e0cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.text.format.DateFormat
+import android.util.TypedValue
+import android.view.View
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shared.regionsampling.RegionSamplingInstance
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
+import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
+ * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
+ */
+open class ClockEventController @Inject constructor(
+        private val statusBarStateController: StatusBarStateController,
+        private val broadcastDispatcher: BroadcastDispatcher,
+        private val batteryController: BatteryController,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val configurationController: ConfigurationController,
+        @Main private val resources: Resources,
+        private val context: Context,
+        @Main private val mainExecutor: Executor,
+        @Background private val bgExecutor: Executor,
+        private val featureFlags: FeatureFlags
+) {
+    var clock: Clock? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                value.initialize(resources, dozeAmount, 0f)
+                updateRegionSamplers(value)
+            }
+        }
+
+    private var isDozing = false
+        private set
+
+    private var isCharging = false
+    private var dozeAmount = 0f
+    private var isKeyguardShowing = false
+
+    private val regionSamplingEnabled =
+            featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
+
+    private val updateFun = object : RegionSamplingInstance.UpdateColorCallback {
+        override fun updateColors() {
+            if (regionSamplingEnabled) {
+                smallClockIsDark = smallRegionSamplingInstance.currentRegionDarkness().isDark
+                largeClockIsDark = largeRegionSamplingInstance.currentRegionDarkness().isDark
+            } else {
+                val isLightTheme = TypedValue()
+                context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
+                smallClockIsDark = isLightTheme.data == 0
+                largeClockIsDark = isLightTheme.data == 0
+            }
+            clock?.events?.onColorPaletteChanged(resources, smallClockIsDark, largeClockIsDark)
+        }
+    }
+
+    fun updateRegionSamplers(currentClock: Clock?) {
+        smallRegionSamplingInstance = createRegionSampler(
+                currentClock?.smallClock,
+                mainExecutor,
+                bgExecutor,
+                regionSamplingEnabled,
+                updateFun
+        )
+
+        largeRegionSamplingInstance = createRegionSampler(
+                currentClock?.largeClock,
+                mainExecutor,
+                bgExecutor,
+                regionSamplingEnabled,
+                updateFun
+        )
+
+        smallRegionSamplingInstance.startRegionSampler()
+        largeRegionSamplingInstance.startRegionSampler()
+
+        updateFun.updateColors()
+    }
+
+    protected open fun createRegionSampler(
+            sampledView: View?,
+            mainExecutor: Executor?,
+            bgExecutor: Executor?,
+            regionSamplingEnabled: Boolean,
+            updateFun: RegionSamplingInstance.UpdateColorCallback
+    ): RegionSamplingInstance {
+        return RegionSamplingInstance(
+            sampledView,
+            mainExecutor,
+            bgExecutor,
+            regionSamplingEnabled,
+            updateFun)
+    }
+
+    lateinit var smallRegionSamplingInstance: RegionSamplingInstance
+    lateinit var largeRegionSamplingInstance: RegionSamplingInstance
+
+    private var smallClockIsDark = true
+    private var largeClockIsDark = true
+
+    private val configListener = object : ConfigurationController.ConfigurationListener {
+        override fun onThemeChanged() {
+            updateFun.updateColors()
+        }
+    }
+
+    private val batteryCallback = object : BatteryStateChangeCallback {
+        override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+            if (isKeyguardShowing && !isCharging && charging) {
+                clock?.animations?.charge()
+            }
+            isCharging = charging
+        }
+    }
+
+    private val localeBroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            clock?.events?.onLocaleChanged(Locale.getDefault())
+        }
+    }
+
+    private val statusBarStateListener = object : StatusBarStateController.StateListener {
+        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+            clock?.animations?.doze(linear)
+
+            isDozing = linear > dozeAmount
+            dozeAmount = linear
+        }
+    }
+
+    private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+        override fun onKeyguardVisibilityChanged(showing: Boolean) {
+            isKeyguardShowing = showing
+            if (!isKeyguardShowing) {
+                clock?.animations?.doze(if (isDozing) 1f else 0f)
+            }
+        }
+
+        override fun onTimeFormatChanged(timeFormat: String) {
+            clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+        }
+
+        override fun onTimeZoneChanged(timeZone: TimeZone) {
+            clock?.events?.onTimeZoneChanged(timeZone)
+        }
+
+        override fun onUserSwitchComplete(userId: Int) {
+            clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+        }
+    }
+
+    init {
+        isDozing = statusBarStateController.isDozing
+    }
+
+    fun registerListeners() {
+        dozeAmount = statusBarStateController.dozeAmount
+        isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+
+        broadcastDispatcher.registerReceiver(
+            localeBroadcastReceiver,
+            IntentFilter(Intent.ACTION_LOCALE_CHANGED)
+        )
+        configurationController.addCallback(configListener)
+        batteryController.addCallback(batteryCallback)
+        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        statusBarStateController.addCallback(statusBarStateListener)
+        smallRegionSamplingInstance.startRegionSampler()
+        largeRegionSamplingInstance.startRegionSampler()
+    }
+
+    fun unregisterListeners() {
+        broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
+        configurationController.removeCallback(configListener)
+        batteryController.removeCallback(batteryCallback)
+        keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+        statusBarStateController.removeCallback(statusBarStateListener)
+        smallRegionSamplingInstance.stopRegionSampler()
+        largeRegionSamplingInstance.stopRegionSampler()
+    }
+
+    /**
+     * Dump information for debugging
+     */
+    fun dump(pw: PrintWriter) {
+        pw.println(this)
+        clock?.dump(pw)
+        smallRegionSamplingInstance.dump(pw)
+        largeRegionSamplingInstance.dump(pw)
+    }
+
+    companion object {
+        private val TAG = ClockEventController::class.simpleName
+        private const val FORMAT_NUMBER = 1234567890
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 9164108..d7cd1d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,31 +5,23 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
 import android.util.AttributeSet;
-import android.util.TypedValue;
+import android.util.Log;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.TimeZone;
-
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
  */
@@ -50,17 +42,10 @@
     public static final int SMALL = 1;
 
     /**
-     * Optional/alternative clock injected via plugin.
-     */
-    private ClockPlugin mClockPlugin;
-
-    /**
      * Frame for small/large clocks
      */
-    private FrameLayout mClockFrame;
+    private FrameLayout mSmallClockFrame;
     private FrameLayout mLargeClockFrame;
-    private AnimatableClockView mClockView;
-    private AnimatableClockView mLargeClockView;
 
     private View mStatusArea;
     private int mSmartspaceTopOffset;
@@ -80,12 +65,6 @@
     @VisibleForTesting AnimatorSet mClockOutAnim = null;
     private ObjectAnimator mStatusAreaAnim = null;
 
-    /**
-     * If the Keyguard Slice has a header (big center-aligned text.)
-     */
-    private boolean mSupportsDarkText;
-    private int[] mColorPalette;
-
     private int mClockSwitchYAmount;
     @VisibleForTesting boolean mChildrenAreLaidOut = false;
     @VisibleForTesting boolean mAnimateOnLayout = true;
@@ -98,97 +77,36 @@
      * Apply dp changes on font/scale change
      */
     public void onDensityOrFontScaleChanged() {
-        mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
-                .getDimensionPixelSize(R.dimen.large_clock_text_size));
-        mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
-                .getDimensionPixelSize(R.dimen.clock_text_size));
-
         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_clock_switch_y_shift);
-
         mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_smartspace_top_offset);
     }
 
-    /**
-     * Returns if this view is presenting a custom clock, or the default implementation.
-     */
-    public boolean hasCustomClock() {
-        return mClockPlugin != null;
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mClockFrame = findViewById(R.id.lockscreen_clock_view);
-        mClockView = findViewById(R.id.animatable_clock_view);
+        mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
         mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
-        mLargeClockView = findViewById(R.id.animatable_clock_view_large);
         mStatusArea = findViewById(R.id.keyguard_status_area);
 
         onDensityOrFontScaleChanged();
     }
 
-    void setClockPlugin(ClockPlugin plugin, int statusBarState) {
+    void setClock(Clock clock, int statusBarState) {
         // Disconnect from existing plugin.
-        if (mClockPlugin != null) {
-            View smallClockView = mClockPlugin.getView();
-            if (smallClockView != null && smallClockView.getParent() == mClockFrame) {
-                mClockFrame.removeView(smallClockView);
-            }
-            View bigClockView = mClockPlugin.getBigClockView();
-            if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) {
-                mLargeClockFrame.removeView(bigClockView);
-            }
-            mClockPlugin.onDestroyView();
-            mClockPlugin = null;
-        }
-        if (plugin == null) {
-            mClockView.setVisibility(View.VISIBLE);
-            mLargeClockView.setVisibility(View.VISIBLE);
+        mSmallClockFrame.removeAllViews();
+        mLargeClockFrame.removeAllViews();
+
+        if (clock == null) {
+            Log.e(TAG, "No clock being shown");
             return;
         }
+
         // Attach small and big clock views to hierarchy.
-        View smallClockView = plugin.getView();
-        if (smallClockView != null) {
-            mClockFrame.addView(smallClockView, -1,
-                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                            ViewGroup.LayoutParams.WRAP_CONTENT));
-            mClockView.setVisibility(View.GONE);
-        }
-        View bigClockView = plugin.getBigClockView();
-        if (bigClockView != null) {
-            mLargeClockFrame.addView(bigClockView);
-            mLargeClockView.setVisibility(View.GONE);
-        }
-
-        // Initialize plugin parameters.
-        mClockPlugin = plugin;
-        mClockPlugin.setStyle(getPaint().getStyle());
-        mClockPlugin.setTextColor(getCurrentTextColor());
-        mClockPlugin.setDarkAmount(mDarkAmount);
-        if (mColorPalette != null) {
-            mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
-        }
-    }
-
-    /**
-     * It will also update plugin setStyle if plugin is connected.
-     */
-    public void setStyle(Style style) {
-        if (mClockPlugin != null) {
-            mClockPlugin.setStyle(style);
-        }
-    }
-
-    /**
-     * It will also update plugin setTextColor if plugin is connected.
-     */
-    public void setTextColor(int color) {
-        if (mClockPlugin != null) {
-            mClockPlugin.setTextColor(color);
-        }
+        mSmallClockFrame.addView(clock.getSmallClock());
+        mLargeClockFrame.addView(clock.getLargeClock());
     }
 
     private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -204,14 +122,14 @@
         int direction = 1;
         float statusAreaYTranslation;
         if (useLargeClock) {
-            out = mClockFrame;
+            out = mSmallClockFrame;
             in = mLargeClockFrame;
             if (indexOfChild(in) == -1) addView(in);
             direction = -1;
-            statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+            statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
                     + mSmartspaceTopOffset;
         } else {
-            in = mClockFrame;
+            in = mSmallClockFrame;
             out = mLargeClockFrame;
             statusAreaYTranslation = 0f;
 
@@ -270,18 +188,6 @@
     }
 
     /**
-     * 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
@@ -312,64 +218,11 @@
         mChildrenAreLaidOut = true;
     }
 
-    public Paint getPaint() {
-        return mClockView.getPaint();
-    }
-
-    public int getCurrentTextColor() {
-        return mClockView.getCurrentTextColor();
-    }
-
-    public float getTextSize() {
-        return mClockView.getTextSize();
-    }
-
-    /**
-     * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
-     */
-    public void refresh() {
-        if (mClockPlugin != null) {
-            mClockPlugin.onTimeTick();
-        }
-    }
-
-    /**
-     * Notifies that the time zone has changed.
-     */
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        if (mClockPlugin != null) {
-            mClockPlugin.onTimeZoneChanged(timeZone);
-        }
-    }
-
-    /**
-     * Notifies that the time format has changed.
-     *
-     * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
-     */
-    public void onTimeFormatChanged(String timeFormat) {
-        if (mClockPlugin != null) {
-            mClockPlugin.onTimeFormatChanged(timeFormat);
-        }
-    }
-
-    void updateColors(ColorExtractor.GradientColors colors) {
-        mSupportsDarkText = colors.supportsDarkText();
-        mColorPalette = colors.getColorPalette();
-        if (mClockPlugin != null) {
-            mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
-        }
-    }
-
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardClockSwitch:");
-        pw.println("  mClockPlugin: " + mClockPlugin);
-        pw.println("  mClockFrame: " + mClockFrame);
+        pw.println("  mSmallClockFrame: " + mSmallClockFrame);
         pw.println("  mLargeClockFrame: " + mLargeClockFrame);
         pw.println("  mStatusArea: " + mStatusArea);
-        pw.println("  mDarkAmount: " + mDarkAmount);
-        pw.println("  mSupportsDarkText: " + mSupportsDarkText);
-        pw.println("  mColorPalette: " + Arrays.toString(mColorPalette));
         pw.println("  mDisplayedClockSize: " + mDisplayedClockSize);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ad06e05..2165099 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,8 +22,6 @@
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 
-import android.app.WallpaperManager;
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -32,36 +30,30 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
 import java.util.Locale;
-import java.util.TimeZone;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -71,48 +63,23 @@
  */
 public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
         implements Dumpable {
-    private static final boolean CUSTOM_CLOCKS_ENABLED = true;
-
     private final StatusBarStateController mStatusBarStateController;
-    private final SysuiColorExtractor mColorExtractor;
-    private final ClockManager mClockManager;
+    private final ClockRegistry mClockRegistry;
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final NotificationIconAreaController mNotificationIconAreaController;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final BatteryController mBatteryController;
     private final LockscreenSmartspaceController mSmartspaceController;
-    private final Resources mResources;
     private final SecureSettings mSecureSettings;
     private final DumpManager mDumpManager;
+    private final ClockEventController mClockEventController;
 
-    /**
-     * Clock for both small and large sizes
-     */
-    private AnimatableClockController mClockViewController;
-    private FrameLayout mClockFrame; // top aligned clock
-    private AnimatableClockController mLargeClockViewController;
+    private FrameLayout mSmallClockFrame; // top aligned clock
     private FrameLayout mLargeClockFrame; // centered clock
 
     @KeyguardClockSwitch.ClockSize
     private int mCurrentClockSize = SMALL;
 
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
     private int mKeyguardClockTopMargin = 0;
-
-    /**
-     * Listener for changes to the color palette.
-     *
-     * The color palette changes when the wallpaper is changed.
-     */
-    private final ColorExtractor.OnColorsChangedListener mColorsListener =
-            (extractor, which) -> {
-                if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-                    mView.updateColors(getGradientColors());
-                }
-            };
-
-    private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
+    private final ClockRegistry.ClockChangeListener mClockChangedListener;
 
     private ViewGroup mStatusArea;
     // If set will replace keyguard_slice_view
@@ -121,9 +88,9 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
     private boolean mOnlyClock = false;
-    private Executor mUiExecutor;
+    private final Executor mUiExecutor;
     private boolean mCanShowDoubleLineClock = true;
-    private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+    private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
             updateDoubleLineClock();
@@ -144,34 +111,32 @@
     public KeyguardClockSwitchController(
             KeyguardClockSwitch keyguardClockSwitch,
             StatusBarStateController statusBarStateController,
-            SysuiColorExtractor colorExtractor,
-            ClockManager clockManager,
+            ClockRegistry clockRegistry,
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
-            BroadcastDispatcher broadcastDispatcher,
-            BatteryController batteryController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
             LockscreenSmartspaceController smartspaceController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SecureSettings secureSettings,
             @Main Executor uiExecutor,
-            @Main Resources resources,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            ClockEventController clockEventController,
+            FeatureFlags featureFlags) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
-        mColorExtractor = colorExtractor;
-        mClockManager = clockManager;
+        mClockRegistry = clockRegistry;
         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());
+        };
     }
 
     /**
@@ -185,43 +150,21 @@
      * Attach the controller to the view it relates to.
      */
     @Override
-    public void onInit() {
+    protected void onInit() {
         mKeyguardSliceViewController.init();
 
-        mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+        mSmallClockFrame = 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() {
-        if (CUSTOM_CLOCKS_ENABLED) {
-            mClockManager.addOnClockChangedListener(mClockChangedListener);
-        }
-        mColorExtractor.addOnColorsChangedListener(mColorsListener);
-        mView.updateColors(getGradientColors());
+        mClockRegistry.registerClockChangeListener(mClockChangedListener);
+        setClock(mClockRegistry.createCurrentClock());
+        mClockEventController.registerListeners();
         mKeyguardClockTopMargin =
                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
@@ -244,7 +187,6 @@
             ksv.setVisibility(View.GONE);
 
             addSmartspaceView(ksvIndex);
-            updateClockLayout();
         }
 
         mSecureSettings.registerContentObserverForUser(
@@ -266,11 +208,9 @@
 
     @Override
     protected void onViewDetached() {
-        if (CUSTOM_CLOCKS_ENABLED) {
-            mClockManager.removeOnClockChangedListener(mClockChangedListener);
-        }
-        mColorExtractor.removeOnColorsChangedListener(mColorsListener);
-        mView.setClockPlugin(null, mStatusBarStateController.getState());
+        mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
+        mClockEventController.unregisterListeners();
+        setClock(null);
 
         mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
 
@@ -309,18 +249,6 @@
         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);
     }
 
     /**
@@ -334,46 +262,34 @@
 
         mCurrentClockSize = clockSize;
 
+        Clock clock = getClock();
         boolean appeared = mView.switchToClock(clockSize, animate);
-        if (animate && appeared && clockSize == LARGE) {
-            mLargeClockViewController.animateAppear();
-        }
-    }
-
-    public void animateFoldToAod() {
-        if (mClockViewController != null) {
-            mClockViewController.animateFoldAppear();
-            mLargeClockViewController.animateFoldAppear();
+        if (clock != null && animate && appeared && clockSize == LARGE) {
+            clock.getAnimations().enter();
         }
     }
 
     /**
-     * If we're presenting a custom clock of just the default one.
+     * Animates the clock view between folded and unfolded states
      */
-    public boolean hasCustomClock() {
-        return mView.hasCustomClock();
-    }
-
-    /**
-     * Get the clock text size.
-     */
-    public float getClockTextSize() {
-        return mView.getTextSize();
+    public void animateFoldToAod(float foldFraction) {
+        Clock clock = getClock();
+        if (clock != null) {
+            clock.getAnimations().fold(foldFraction);
+        }
     }
 
     /**
      * Refresh clock. Called in response to TIME_TICK broadcasts.
      */
     void refresh() {
-        if (mClockViewController != null) {
-            mClockViewController.refreshTime();
-            mLargeClockViewController.refreshTime();
-        }
         if (mSmartspaceController != null) {
             mSmartspaceController.requestSmartspaceUpdate();
         }
-
-        mView.refresh();
+        Clock clock = getClock();
+        if (clock != null) {
+            clock.getEvents().onTimeTick();
+        }
     }
 
     /**
@@ -385,7 +301,7 @@
     void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
         x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
 
-        PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
+        PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
                 x, props, animate);
         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
                 scale, props, animate);
@@ -398,25 +314,39 @@
         }
     }
 
-    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.getHeight();
+            int clockHeight = clock.getLargeClock().getHeight();
             return frameHeight / 2 + clockHeight / 2;
         } else {
-            return mClockFrame.findViewById(
-                    com.android.systemui.R.id.animatable_clock_view).getHeight()
-                    + statusBarHeaderHeight + mKeyguardClockTopMargin;
+            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();
         }
     }
 
@@ -431,12 +361,13 @@
         mNotificationIconAreaController.setupAodIcons(nic);
     }
 
-    private void setClockPlugin(ClockPlugin plugin) {
-        mView.setClockPlugin(plugin, mStatusBarStateController.getState());
+    private void setClock(Clock clock) {
+        mClockEventController.setClock(clock);
+        mView.setClock(clock, mStatusBarStateController.getState());
     }
 
-    private ColorExtractor.GradientColors getGradientColors() {
-        return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
+    private Clock getClock() {
+        return mClockEventController.getClock();
     }
 
     private int getCurrentLayoutDirection() {
@@ -458,8 +389,6 @@
      * bounds during the unlock transition.
      */
     private void setClipChildrenForUnlock(boolean clip) {
-        mView.setClipChildren(clip);
-
         if (mStatusArea != null) {
             mStatusArea.setClipChildren(clip);
         }
@@ -469,8 +398,10 @@
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
         pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
-        mClockViewController.dump(pw);
-        mLargeClockViewController.dump(pw);
+        Clock clock = getClock();
+        if (clock != null) {
+            clock.dump(pw);
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index cb3172d..83e23bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,14 +17,11 @@
 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;
 
@@ -46,7 +43,6 @@
     private View mMediaHostContainer;
 
     private float mDarkAmount = 0;
-    private int mTextColor;
 
     public KeyguardStatusView(Context context) {
         this(context, null, 0);
@@ -71,7 +67,6 @@
         }
 
         mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
-        mTextColor = mClockView.getCurrentTextColor();
 
         mMediaHostContainer = findViewById(R.id.status_view_media_container);
 
@@ -83,15 +78,12 @@
             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. */
@@ -113,7 +105,6 @@
     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 014d082..c715a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -30,8 +30,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
-import java.util.TimeZone;
-
 import javax.inject.Inject;
 
 /**
@@ -96,13 +94,6 @@
     }
 
     /**
-     * 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.
      */
@@ -114,16 +105,10 @@
      * 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() {
-        mKeyguardClockSwitchController.animateFoldToAod();
-    }
-
-    /**
-     * If we're presenting a custom clock of just the default one.
-     */
-    public boolean hasCustomClock() {
-        return mKeyguardClockSwitchController.hasCustomClock();
+    public void animateFoldToAod(float foldFraction) {
+        mKeyguardClockSwitchController.animateFoldToAod(foldFraction);
     }
 
     /**
@@ -143,24 +128,11 @@
     }
 
     /**
-     * Set pivot x.
+     * Update the pivot position based on the parent view
      */
-    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();
+    public void updatePivot(float parentWidth, float parentHeight) {
+        mView.setPivotX(parentWidth / 2f);
+        mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f);
     }
 
     /**
@@ -240,11 +212,6 @@
         }
 
         @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 67455d1..70c0cab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -290,11 +290,6 @@
     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.
@@ -1112,13 +1107,6 @@
         }
     }
 
-    /**
-     * 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;
@@ -3974,9 +3962,9 @@
                 public void onAnimationEnd(Animator animation) {
                     endAction.run();
                 }
+            }).setUpdateListener(anim -> {
+                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
             }).start();
-
-        mKeyguardStatusViewController.animateFoldToAod();
     }
 
     /**
@@ -4686,7 +4674,6 @@
         public void onDozeAmountChanged(float linearAmount, float amount) {
             mInterpolatedDarkAmount = amount;
             mLinearDarkAmount = linearAmount;
-            mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
             mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
             positionClockAndNotifications();
         }
@@ -4796,11 +4783,8 @@
             updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
             setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
 
-            // Update Clock Pivot
-            mKeyguardStatusViewController.setPivotX(((float) mView.getWidth()) / 2f);
-            mKeyguardStatusViewController.setPivotY(
-                    (FONT_HEIGHT - CAP_HEIGHT) / 2048f
-                            * mKeyguardStatusViewController.getClockTextSize());
+            // Update Clock Pivot (used by anti-burnin transformations)
+            mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
 
             // Calculate quick setting heights.
             int oldMaxHeight = mQsMaxExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 84bb82b..ce04fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -442,7 +442,6 @@
             state |= DISABLE_CLOCK;
         }
 
-
         if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
             if (mNetworkController.hasEmergencyCryptKeeperText()) {
                 state |= DISABLE_NOTIFICATION_ICONS;
@@ -452,13 +451,6 @@
             }
         }
 
-        // The shelf will be hidden when dozing with a custom clock, we must show notification
-        // icons in this occasion.
-        if (mStatusBarStateController.isDozing()
-                && mNotificationPanelViewController.hasCustomClock()) {
-            state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
-        }
-
         if (mOngoingCallController.hasOngoingCall()) {
             state &= ~DISABLE_ONGOING_CALL_CHIP;
         } else {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
new file mode 100644
index 0000000..914d945
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.keyguard
+
+import android.content.BroadcastReceiver
+import android.testing.AndroidTestingRunner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockEventControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var batteryController: BatteryController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var animations: ClockAnimations
+    @Mock private lateinit var events: ClockEvents
+    @Mock private lateinit var clock: Clock
+    @Mock private lateinit var mainExecutor: Executor
+    @Mock private lateinit var bgExecutor: Executor
+    @Mock private lateinit var featureFlags: FeatureFlags
+
+    private lateinit var clockEventController: ClockEventController
+
+    @Before
+    fun setUp() {
+        whenever(clock.smallClock).thenReturn(TextView(context))
+        whenever(clock.largeClock).thenReturn(TextView(context))
+        whenever(clock.events).thenReturn(events)
+        whenever(clock.animations).thenReturn(animations)
+
+        clockEventController = ClockEventController(
+            statusBarStateController,
+            broadcastDispatcher,
+            batteryController,
+            keyguardUpdateMonitor,
+            configurationController,
+            context.resources,
+            context,
+            mainExecutor,
+            bgExecutor,
+            featureFlags
+        )
+    }
+
+    @Test
+    fun clockSet_validateInitialization() {
+        clockEventController.clock = clock
+
+        verify(clock).initialize(any(), anyFloat(), anyFloat())
+    }
+
+    @Test
+    fun clockUnset_validateState() {
+        clockEventController.clock = clock
+        clockEventController.clock = null
+
+        assertEquals(clockEventController.clock, null)
+    }
+
+    @Test
+    fun themeChanged_verifyClockPaletteUpdated() {
+        clockEventController.clock = clock
+        verify(events).onColorPaletteChanged(any(), any(), any())
+
+        clockEventController.registerListeners()
+
+        val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+        verify(configurationController).addCallback(capture(captor))
+        captor.value.onThemeChanged()
+
+        verify(events, times(2)).onColorPaletteChanged(any(), any(), any())
+    }
+
+    @Test
+    fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
+        clockEventController.clock = clock
+        clockEventController.registerListeners()
+
+        val batteryCaptor = argumentCaptor<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.clock = clock
+        clockEventController.unregisterListeners()
+        verify(broadcastDispatcher).unregisterReceiver(any())
+        verify(configurationController).removeCallback(any())
+        verify(batteryController).removeCallback(any())
+        verify(keyguardUpdateMonitor).removeCallback(any())
+        verify(statusBarStateController).removeCallback(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index b2d9219..635ee9e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -41,24 +40,19 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -80,22 +74,12 @@
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
-    private SysuiColorExtractor mColorExtractor;
-    @Mock
-    private ClockManager mClockManager;
+    private ClockRegistry mClockRegistry;
     @Mock
     KeyguardSliceViewController mKeyguardSliceViewController;
     @Mock
     NotificationIconAreaController mNotificationIconAreaController;
     @Mock
-    BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    BatteryController mBatteryController;
-    @Mock
-    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    KeyguardBypassController mBypassController;
-    @Mock
     LockscreenSmartspaceController mSmartspaceController;
 
     @Mock
@@ -103,11 +87,11 @@
     @Mock
     KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
-    private ClockPlugin mClockPlugin;
-    @Mock
-    ColorExtractor.GradientColors mGradientColors;
+    private Clock mClock;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    ClockEventController mClockEventController;
 
     @Mock
     private NotificationIconContainer mNotificationIcons;
@@ -139,8 +123,6 @@
         when(mView.getContext()).thenReturn(getContext());
         when(mView.getResources()).thenReturn(mResources);
 
-        when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
-        when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
         when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
         when(mClockView.getContext()).thenReturn(getContext());
         when(mLargeClockView.getContext()).thenReturn(getContext());
@@ -151,23 +133,20 @@
         mController = new KeyguardClockSwitchController(
                 mView,
                 mStatusBarStateController,
-                mColorExtractor,
-                mClockManager,
+                mClockRegistry,
                 mKeyguardSliceViewController,
                 mNotificationIconAreaController,
-                mBroadcastDispatcher,
-                mBatteryController,
-                mKeyguardUpdateMonitor,
                 mSmartspaceController,
                 mKeyguardUnlockAnimationController,
                 mSecureSettings,
                 mExecutor,
-                mResources,
-                mDumpManager
+                mDumpManager,
+                mClockEventController,
+                mFeatureFlags
         );
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
+        when(mClockRegistry.createCurrentClock()).thenReturn(mClock);
 
         mSliceView = new View(getContext());
         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -214,20 +193,20 @@
         verifyAttachment(times(1));
 
         listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
-        verify(mColorExtractor).removeOnColorsChangedListener(
-                any(ColorExtractor.OnColorsChangedListener.class));
+        verify(mClockEventController).unregisterListeners();
     }
 
     @Test
     public void testPluginPassesStatusBarState() {
-        ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
-                ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
+        ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
 
         mController.init();
-        verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
+        verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
 
-        listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
-        verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
+        listenerArgumentCaptor.getValue().onClockChanged();
+        verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE);
+        verify(mClockEventController, times(2)).setClock(mClock);
     }
 
     @Test
@@ -284,10 +263,8 @@
     }
 
     private void verifyAttachment(VerificationMode times) {
-        verify(mClockManager, times).addOnClockChangedListener(
-                any(ClockManager.ClockChangedListener.class));
-        verify(mColorExtractor, times).addOnColorsChangedListener(
-                any(ColorExtractor.OnColorsChangedListener.class));
-        verify(mView, times).updateColors(mGradientColors);
+        verify(mClockRegistry, times).registerClockChangeListener(
+                any(ClockRegistry.ClockChangeListener.class));
+        verify(mClockEventController, times).registerListeners();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 6c6f0ac..a0295d0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,7 +16,6 @@
 
 package com.android.keyguard;
 
-import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -24,56 +23,61 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.TestCase.assertEquals;
+
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.TextClock;
+import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
 import com.android.systemui.statusbar.StatusBarState;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 // Need to run on the main thread because KeyguardSliceView$Row init checks for
 // the main thread before acquiring a wake lock. This class is constructed when
-// the keyguard_clcok_switch layout is inflated.
+// the keyguard_clock_switch layout is inflated.
 @RunWithLooper(setAsMainLooper = true)
 public class KeyguardClockSwitchTest extends SysuiTestCase {
-    private FrameLayout mClockFrame;
-    private FrameLayout mLargeClockFrame;
-    private TextClock mBigClock;
+    @Mock
+    ViewGroup mMockKeyguardSliceView;
 
-    private AnimatableClockView mClockView;
-    private AnimatableClockView mLargeClockView;
-    View mMockKeyguardSliceView;
+    @Mock
+    Clock mClock;
+
+    private FrameLayout mSmallClockFrame;
+    private FrameLayout mLargeClockFrame;
+
     KeyguardClockSwitch mKeyguardClockSwitch;
 
     @Before
     public void setUp() {
-        mMockKeyguardSliceView = mock(KeyguardSliceView.class);
+        MockitoAnnotations.initMocks(this);
         when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
         when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
                 .thenReturn(mMockKeyguardSliceView);
 
+        when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
+        when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
+
         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
         layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
 
@@ -93,164 +97,68 @@
         });
         mKeyguardClockSwitch =
                 (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
-        mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
-        mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view);
+        mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
         mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
-        mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large);
-        mBigClock = new TextClock(getContext());
         mKeyguardClockSwitch.mChildrenAreLaidOut = true;
-        MockitoAnnotations.initMocks(this);
     }
 
     @Test
-    public void onPluginConnected_showPluginClock() {
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        TextClock pluginView = new TextClock(getContext());
-        when(plugin.getView()).thenReturn(pluginView);
-
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
-        assertThat(mClockView.getVisibility()).isEqualTo(GONE);
-        assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame);
+    public void noPluginConnected_showNothing() {
+        mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+        assertEquals(mLargeClockFrame.getChildCount(), 0);
+        assertEquals(mSmallClockFrame.getChildCount(), 0);
     }
 
     @Test
-    public void onPluginConnected_showPluginBigClock() {
-        // GIVEN the plugin returns a view for the big clock
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        when(plugin.getBigClockView()).thenReturn(mBigClock);
-        // WHEN the plugin is connected
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-        // THEN the big clock container is visible and it is the parent of the
-        // big clock view.
-        assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame);
+    public void pluginConnectedThenDisconnected_showNothing() {
+        mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+        assertEquals(mLargeClockFrame.getChildCount(), 1);
+        assertEquals(mSmallClockFrame.getChildCount(), 1);
+
+        mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+        assertEquals(mLargeClockFrame.getChildCount(), 0);
+        assertEquals(mSmallClockFrame.getChildCount(), 0);
     }
 
     @Test
-    public void onPluginConnected_nullView() {
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-        assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+    public void onPluginConnected_showClock() {
+        mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+
+        assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
+        assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
     }
 
     @Test
     public void onPluginConnected_showSecondPluginClock() {
         // GIVEN a plugin has already connected
-        ClockPlugin plugin1 = mock(ClockPlugin.class);
-        when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
-        // WHEN a second plugin is connected
-        ClockPlugin plugin2 = mock(ClockPlugin.class);
-        when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
+        Clock otherClock = mock(Clock.class);
+        when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+        when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+        mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+        mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
+
         // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
-        assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame);
-        assertThat(plugin1.getView().getParent()).isNull();
-    }
-
-    @Test
-    public void onPluginConnected_darkAmountInitialized() {
-        // GIVEN that the dark amount has already been set
-        mKeyguardClockSwitch.setDarkAmount(0.5f);
-        // WHEN a plugin is connected
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-        // THEN dark amount should be initalized on the plugin.
-        verify(plugin).setDarkAmount(0.5f);
-    }
-
-    @Test
-    public void onPluginDisconnected_showDefaultClock() {
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        TextClock pluginView = new TextClock(getContext());
-        when(plugin.getView()).thenReturn(pluginView);
-
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-        assertThat(mClockView.getVisibility()).isEqualTo(GONE);
-
-        mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
-        assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
-
-        assertThat(plugin.getView().getParent()).isNull();
-    }
-
-    @Test
-    public void onPluginDisconnected_hidePluginBigClock() {
-        // GIVEN the plugin returns a view for the big clock
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        TextClock pluginView = new TextClock(getContext());
-        when(plugin.getBigClockView()).thenReturn(pluginView);
-        // WHEN the plugin is connected and then disconnected
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-        mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
-        // THEN the big lock container is GONE and the big clock view doesn't have
-        // a parent.
-        assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE);
-        assertThat(pluginView.getParent()).isNull();
-    }
-
-    @Test
-    public void onPluginDisconnected_nullView() {
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-        mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
-        assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+        assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
+        assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
+        assertThat(mClock.getSmallClock().getParent()).isNull();
+        assertThat(mClock.getLargeClock().getParent()).isNull();
     }
 
     @Test
     public void onPluginDisconnected_secondOfTwoDisconnected() {
         // GIVEN two plugins are connected
-        ClockPlugin plugin1 = mock(ClockPlugin.class);
-        when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
-        ClockPlugin plugin2 = mock(ClockPlugin.class);
-        when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
+        Clock otherClock = mock(Clock.class);
+        when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+        when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+        mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
+        mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
         // WHEN the second plugin is disconnected
-        mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
-        // THEN the default clock should be shown.
-        assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
-        assertThat(plugin1.getView().getParent()).isNull();
-        assertThat(plugin2.getView().getParent()).isNull();
-    }
-
-    @Test
-    public void onPluginDisconnected_onDestroyView() {
-        // GIVEN a plugin is connected
-        ClockPlugin clockPlugin = mock(ClockPlugin.class);
-        when(clockPlugin.getView()).thenReturn(new TextClock(getContext()));
-        mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD);
-        // WHEN the plugin is disconnected
-        mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
-        // THEN onDestroyView is called on the plugin
-        verify(clockPlugin).onDestroyView();
-    }
-
-    @Test
-    public void setTextColor_pluginClockSetTextColor() {
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        TextClock pluginView = new TextClock(getContext());
-        when(plugin.getView()).thenReturn(pluginView);
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
-        mKeyguardClockSwitch.setTextColor(Color.WHITE);
-
-        verify(plugin).setTextColor(Color.WHITE);
-    }
-
-
-    @Test
-    public void setStyle_pluginClockSetStyle() {
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        TextClock pluginView = new TextClock(getContext());
-        when(plugin.getView()).thenReturn(pluginView);
-        Style style = mock(Style.class);
-        mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
-        mKeyguardClockSwitch.setStyle(style);
-
-        verify(plugin).setStyle(style);
+        mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+        // THEN nothing should be shown
+        assertThat(otherClock.getSmallClock().getParent()).isNull();
+        assertThat(otherClock.getLargeClock().getParent()).isNull();
+        assertThat(mClock.getSmallClock().getParent()).isNull();
+        assertThat(mClock.getLargeClock().getParent()).isNull();
     }
 
     @Test
@@ -262,7 +170,7 @@
 
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
-        assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
     }
 
     @Test
@@ -271,7 +179,7 @@
 
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
-        assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
     }
 
     @Test
@@ -281,8 +189,8 @@
         mKeyguardClockSwitch.mClockInAnim.end();
         mKeyguardClockSwitch.mClockOutAnim.end();
 
-        assertThat(mClockFrame.getAlpha()).isEqualTo(1);
-        assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
@@ -292,8 +200,8 @@
     public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
         mKeyguardClockSwitch.switchToClock(SMALL, false);
 
-        assertThat(mClockFrame.getAlpha()).isEqualTo(1);
-        assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
index df506b4..b5e9e8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -38,6 +38,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -53,6 +54,8 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.util.concurrent.Executor;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -69,6 +72,12 @@
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
     private Resources mResources;
+    @Mock
+    private Executor mMainExecutor;
+    @Mock
+    private Executor mBgExecutor;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     private MockitoSession mStaticMockSession;
     private AnimatableClockController mAnimatableClockController;
@@ -96,7 +105,10 @@
                 mBroadcastDispatcher,
                 mBatteryController,
                 mKeyguardUpdateMonitor,
-                mResources
+                mResources,
+                mMainExecutor,
+                mBgExecutor,
+                mFeatureFlags
         );
         mAnimatableClockController.init();
         captureAttachListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 1cbb8d0..131eac6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -130,13 +130,16 @@
         pluginListener.onPluginConnected(plugin1, mockContext)
         pluginListener.onPluginConnected(plugin2, mockContext)
         val list = registry.getClocks()
-        assertEquals(list, listOf(
-            ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
-            ClockMetadata("clock_1", "clock 1"),
-            ClockMetadata("clock_2", "clock 2"),
-            ClockMetadata("clock_3", "clock 3"),
-            ClockMetadata("clock_4", "clock 4")
-        ))
+        assertEquals(
+            list,
+            listOf(
+                ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+                ClockMetadata("clock_1", "clock 1"),
+                ClockMetadata("clock_2", "clock 2"),
+                ClockMetadata("clock_3", "clock 3"),
+                ClockMetadata("clock_4", "clock 4")
+            )
+        )
     }
 
     @Test
@@ -158,11 +161,14 @@
         pluginListener.onPluginConnected(plugin1, mockContext)
         pluginListener.onPluginConnected(plugin2, mockContext)
         val list = registry.getClocks()
-        assertEquals(list, listOf(
-            ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
-            ClockMetadata("clock_1", "clock 1"),
-            ClockMetadata("clock_2", "clock 2")
-        ))
+        assertEquals(
+            list,
+            listOf(
+                ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+                ClockMetadata("clock_1", "clock 1"),
+                ClockMetadata("clock_2", "clock 2")
+            )
+        )
 
         assertEquals(registry.createExampleClock("clock_1"), mockClock)
         assertEquals(registry.createExampleClock("clock_2"), mockClock)
@@ -222,7 +228,7 @@
         pluginListener.onPluginConnected(plugin2, mockContext)
 
         var changeCallCount = 0
-        registry.registerClockChangeListener({ changeCallCount++ })
+        registry.registerClockChangeListener { changeCallCount++ }
 
         pluginListener.onPluginDisconnected(plugin1)
         assertEquals(0, changeCallCount)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 7869448..2b4a109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -19,17 +19,31 @@
 import android.content.res.Resources
 import android.graphics.drawable.Drawable
 import android.testing.AndroidTestingRunner
+import android.util.TypedValue
 import android.view.LayoutInflater
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.clocks.DefaultClock.Companion.DOZE_COLOR
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import java.util.Locale
+import java.util.TimeZone
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertNotNull
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.notNull
 import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
@@ -39,7 +53,8 @@
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
-    @Mock private lateinit var mockClockView: AnimatableClockView
+    @Mock private lateinit var mockSmallClockView: AnimatableClockView
+    @Mock private lateinit var mockLargeClockView: AnimatableClockView
     @Mock private lateinit var layoutInflater: LayoutInflater
     @Mock private lateinit var mockClockThumbnail: Drawable
     @Mock private lateinit var resources: Resources
@@ -47,14 +62,16 @@
 
     @Before
     fun setUp() {
-        whenever(layoutInflater.inflate(R.layout.clock_default_small, null))
-            .thenReturn(mockClockView)
-        whenever(layoutInflater.inflate(R.layout.clock_default_large, null))
-            .thenReturn(mockClockView)
+        whenever(layoutInflater.inflate(eq(R.layout.clock_default_small), any(), anyBoolean()))
+            .thenReturn(mockSmallClockView)
+        whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean()))
+            .thenReturn(mockLargeClockView)
         whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
             .thenReturn(mockClockThumbnail)
+        whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
+        whenever(mockLargeClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
 
-        provider = DefaultClockProvider(layoutInflater, resources)
+        provider = DefaultClockProvider(context, layoutInflater, resources)
     }
 
     @Test
@@ -71,7 +88,79 @@
         // Default clock provider must always provide the default clock
         val clock = provider.createClock(DEFAULT_CLOCK_ID)
         assertNotNull(clock)
-        assertEquals(clock.smallClock, mockClockView)
-        assertEquals(clock.largeClock, mockClockView)
+        assertEquals(clock.smallClock, mockSmallClockView)
+        assertEquals(clock.largeClock, mockLargeClockView)
+    }
+
+    @Test
+    fun defaultClock_initialize() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.initialize(resources, 0f, 0f)
+
+        verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+        verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+        verify(mockSmallClockView).onTimeZoneChanged(notNull())
+        verify(mockLargeClockView).onTimeZoneChanged(notNull())
+        verify(mockSmallClockView).refreshTime()
+        verify(mockLargeClockView).refreshTime()
+        verify(mockLargeClockView).setLayoutParams(any())
+    }
+
+    @Test
+    fun defaultClock_events_onTimeTick() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.events.onTimeTick()
+
+        verify(mockSmallClockView).refreshTime()
+        verify(mockLargeClockView).refreshTime()
+    }
+
+    @Test
+    fun defaultClock_events_onTimeFormatChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.events.onTimeFormatChanged(true)
+
+        verify(mockSmallClockView).refreshFormat(true)
+        verify(mockLargeClockView).refreshFormat(true)
+    }
+
+    @Test
+    fun defaultClock_events_onTimeZoneChanged() {
+        val timeZone = mock<TimeZone>()
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.events.onTimeZoneChanged(timeZone)
+
+        verify(mockSmallClockView).onTimeZoneChanged(timeZone)
+        verify(mockLargeClockView).onTimeZoneChanged(timeZone)
+    }
+
+    @Test
+    fun defaultClock_events_onFontSettingChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.events.onFontSettingChanged()
+
+        verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+        verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+        verify(mockLargeClockView).setLayoutParams(any())
+    }
+
+    @Test
+    fun defaultClock_events_onColorPaletteChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.events.onColorPaletteChanged(resources, true, true)
+
+        verify(mockSmallClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+        verify(mockLargeClockView, times(2)).setColors(eq(DOZE_COLOR), anyInt())
+    }
+
+    @Test
+    fun defaultClock_events_onLocaleChanged() {
+        val clock = provider.createClock(DEFAULT_CLOCK_ID)
+        clock.events.onLocaleChanged(Locale.getDefault())
+
+        verify(mockSmallClockView, times(2)).setLineSpacingScale(anyFloat())
+        verify(mockLargeClockView, times(2)).setLineSpacingScale(anyFloat())
+        verify(mockSmallClockView, times(2)).refreshFormat()
+        verify(mockLargeClockView, times(2)).refreshFormat()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 20bf50e..37c8f62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -333,10 +333,9 @@
     }
 
     @Test
-    public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() {
+    public void disable_isDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(true);
-        when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
@@ -345,10 +344,9 @@
     }
 
     @Test
-    public void disable_customClockButNotDozing_clockAndSystemInfoVisible() {
+    public void disable_NotDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
@@ -357,40 +355,6 @@
     }
 
     @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);