Migrate clocks to blueprint sections

Bug: 288242803

Flag: LEGACY MIGRATE_CLOCKS_TO_BLUEPRINT DEVELOPMENT

Test: atest ClockSectionTest, SmartspaceSectionTest,
KeyguardClockViewModelTest, keyguardClockRepositoryTest

Change-Id: I265e4494bc99cacbf0c4dfe5ee6aef695958f7e2
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index b076b2c..42ba643 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.DefaultClockFaceLayout
 import com.android.systemui.plugins.WeatherData
 import java.io.PrintWriter
 import java.util.Locale
@@ -114,6 +115,7 @@
         protected var targetRegion: Rect? = null
 
         override val config = ClockFaceConfig()
+        override val layout = DefaultClockFaceLayout(view)
 
         override var messageBuffer: MessageBuffer?
             get() = view.messageBuffer
@@ -184,6 +186,7 @@
         view: AnimatableClockView,
         seedColor: Int?,
     ) : DefaultClockFaceController(view, seedColor) {
+        override val layout = DefaultClockFaceLayout(view)
         override val config =
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index c428952..1beb55b 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -38,6 +38,7 @@
     // of the library which are used by the plugins but not by systemui itself.
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx-constraintlayout_constraintlayout",
         "PluginCoreLib",
         "SystemUIAnimationLib",
         "SystemUICommon",
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 485c27e..63ded2e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -17,6 +17,7 @@
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
 import com.android.internal.annotations.Keep
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.annotations.ProvidesInterface
@@ -85,6 +86,9 @@
     /** View that renders the clock face */
     val view: View
 
+    /** Layout specification for this clock */
+    val layout: ClockFaceLayout
+
     /** Determines the way the hosting app should behave when rendering this clock face */
     val config: ClockFaceConfig
 
@@ -98,6 +102,30 @@
     var messageBuffer: MessageBuffer?
 }
 
+/** Specifies layout information for the */
+interface ClockFaceLayout {
+    /** All clock views to add to the root constraint layout before applying constraints. */
+    val views: List<View>
+
+    /** Custom constraints to apply to Lockscreen ConstraintLayout. */
+    fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+}
+
+/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
+class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
+    // both small and large clock should have a container (RelativeLayout in
+    // SimpleClockFaceController)
+    override val views = listOf(view)
+    override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
+        if (views.size != 1) {
+            throw IllegalArgumentException(
+                "Should have only one container view when using DefaultClockFaceLayout"
+            )
+        }
+        return constraints
+    }
+}
+
 /** Events that should call when various rendering parameters change */
 interface ClockEvents {
     /** Call whenever timezone changes */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index f9f2c63..92f66902 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -269,6 +269,7 @@
         private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
             return new PluginManagerImpl.ClassLoaderFilter(
                     baseClassLoader,
+                    "androidx.constraintlayout.widget",
                     "com.android.systemui.common",
                     "com.android.systemui.log",
                     "com.android.systemui.plugin");
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index e47d36f..6b390b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -27,9 +27,9 @@
 import android.util.TypedValue
 import android.view.View
 import android.view.View.OnAttachStateChangeListener
+import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import android.view.ViewTreeObserver.OnGlobalLayoutListener
-import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -63,7 +63,6 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
 import java.util.Locale
 import java.util.TimeZone
@@ -150,25 +149,24 @@
                         var pastVisibility: Int? = null
                         override fun onViewAttachedToWindow(view: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                            if (view != null) {
-                                smallClockFrame = view.parent as FrameLayout
-                                smallClockFrame?.let {frame ->
-                                    pastVisibility = frame.visibility
-                                    onGlobalLayoutListener = OnGlobalLayoutListener {
-                                        val currentVisibility = frame.visibility
-                                        if (pastVisibility != currentVisibility) {
-                                            pastVisibility = currentVisibility
-                                            // when small clock  visible,
-                                            // recalculate bounds and sample
-                                            if (currentVisibility == View.VISIBLE) {
-                                                smallRegionSampler?.stopRegionSampler()
-                                                smallRegionSampler?.startRegionSampler()
-                                            }
+                            // Match the asing for view.parent's layout classes.
+                            smallClockFrame = view.parent as ViewGroup
+                            smallClockFrame?.let { frame ->
+                                pastVisibility = frame.visibility
+                                onGlobalLayoutListener = OnGlobalLayoutListener {
+                                    val currentVisibility = frame.visibility
+                                    if (pastVisibility != currentVisibility) {
+                                        pastVisibility = currentVisibility
+                                        // when small clock is visible,
+                                        // recalculate bounds and sample
+                                        if (currentVisibility == View.VISIBLE) {
+                                            smallRegionSampler?.stopRegionSampler()
+                                            smallRegionSampler?.startRegionSampler()
                                         }
                                     }
-                                    frame.viewTreeObserver
-                                            .addOnGlobalLayoutListener(onGlobalLayoutListener)
                                 }
+                                frame.viewTreeObserver
+                                        .addOnGlobalLayoutListener(onGlobalLayoutListener)
                             }
                         }
 
@@ -197,7 +195,7 @@
     var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
     @VisibleForTesting
     var largeClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
-    private var smallClockFrame: FrameLayout? = null
+    private var smallClockFrame: ViewGroup? = null
     private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
 
     private var isDozing = false
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 9bddcd7..39a59c4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -453,7 +453,7 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         // TODO: b/305022530
-        if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) {
+        if (mClock != null && mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) {
             mClock.getEvents().onColorPaletteChanged(mContext.getResources());
         }
 
@@ -461,7 +461,8 @@
             post(() -> updateClockTargetRegions());
         }
 
-        if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()) {
+        if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()
+                && mDisplayedClockSize != null) {
             mSmartspaceTop = mSmartspace.getTop();
             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 54f1457..f9eb686 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
@@ -128,6 +129,7 @@
     private boolean mIsActiveDreamLockscreenHosted = false;
     private final FeatureFlagsClassic mFeatureFlags;
     private KeyguardInteractor mKeyguardInteractor;
+    private KeyguardClockInteractor mKeyguardClockInteractor;
     private final DelayableExecutor mUiExecutor;
     private boolean mCanShowDoubleLineClock = true;
     private DisposableHandle mAodIconsBindHandle;
@@ -187,6 +189,7 @@
             DozeParameters dozeParameters,
             AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
             KeyguardInteractor keyguardInteractor,
+            KeyguardClockInteractor keyguardClockInteractor,
             FeatureFlagsClassic featureFlags) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
@@ -210,11 +213,14 @@
         mView.setLogBuffer(mLogBuffer);
         mFeatureFlags = featureFlags;
         mKeyguardInteractor = keyguardInteractor;
+        mKeyguardClockInteractor = keyguardClockInteractor;
 
         mClockChangedListener = new ClockRegistry.ClockChangeListener() {
             @Override
             public void onCurrentClockChanged() {
-                setClock(mClockRegistry.createCurrentClock());
+                if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+                    setClock(mClockRegistry.createCurrentClock());
+                }
             }
             @Override
             public void onAvailableClocksChanged() { }
@@ -344,9 +350,11 @@
                 addDateWeatherView();
             }
         }
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            setDateWeatherVisibility();
+            setWeatherVisibility();
+        }
 
-        setDateWeatherVisibility();
-        setWeatherVisibility();
     }
 
     int getNotificationIconAreaHeight() {
@@ -386,6 +394,9 @@
     }
 
     private void addDateWeatherView() {
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return;
+        }
         mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
@@ -395,11 +406,13 @@
         int endPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_end);
         mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
-
         addWeatherView();
     }
 
     private void addWeatherView() {
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return;
+        }
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 WRAP_CONTENT, WRAP_CONTENT);
         mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
@@ -410,6 +423,10 @@
     }
 
     private void addSmartspaceView() {
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return;
+        }
+
         mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
@@ -607,6 +624,9 @@
     }
 
     private void setClock(ClockController clock) {
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return;
+        }
         if (clock != null && mLogBuffer != null) {
             mLogBuffer.log(TAG, LogLevel.INFO, "New Clock");
         }
@@ -618,7 +638,11 @@
 
     @Nullable
     public ClockController getClock() {
-        return mClockEventController.getClock();
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return mKeyguardClockInteractor.getClock();
+        } else {
+            return mClockEventController.getClock();
+        }
     }
 
     private int getCurrentLayoutDirection() {
@@ -626,13 +650,17 @@
     }
 
     private void updateDoubleLineClock() {
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return;
+        }
         mCanShowDoubleLineClock = mSecureSettings.getIntForUser(
             Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, mView.getResources()
-                .getInteger(com.android.internal.R.integer.config_doublelineClockDefault),
-                UserHandle.USER_CURRENT) != 0;
+                    .getInteger(com.android.internal.R.integer.config_doublelineClockDefault),
+            UserHandle.USER_CURRENT) != 0;
 
         if (!mCanShowDoubleLineClock) {
-            mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
+            mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL,
+                    /* animate */ true));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 16ad29a..d1c6218 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -18,6 +18,8 @@
 
 import android.os.UserHandle
 import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.keyguard.KeyguardClockSwitch.SMALL
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
@@ -71,7 +73,10 @@
             }
             .mapNotNull { it }
 
-    private suspend fun getClockSize(): SettingsClockSize {
+    val currentClock = currentClockId.map { clockRegistry.createCurrentClock() }
+
+    @VisibleForTesting
+    suspend fun getClockSize(): SettingsClockSize {
         return withContext(backgroundDispatcher) {
             if (
                 secureSettings.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 4d26466..6ff446e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -18,6 +18,8 @@
 
 import android.graphics.Point
 import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardClockSwitch.ClockSize
+import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.biometrics.AuthController
@@ -190,6 +192,12 @@
     /** Observable updated when keyguardDone should be called either now or soon. */
     val keyguardDone: Flow<KeyguardDone>
 
+    /** Receive SMALL or LARGE clock should be displayed on keyguard. */
+    val clockSize: Flow<Int>
+
+    /** Receive whether clock should be centered on lockscreen. */
+    val clockShouldBeCentered: Flow<Boolean>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -238,6 +246,10 @@
     fun setDismissAction(dismissAction: DismissAction)
 
     suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
+
+    fun setClockSize(@ClockSize size: Int)
+
+    fun setClockShouldBeCentered(shouldBeCentered: Boolean)
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -281,6 +293,12 @@
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition = _clockPosition.asStateFlow()
 
+    private val _clockSize = MutableStateFlow(LARGE)
+    override val clockSize: Flow<Int> = _clockSize.asStateFlow()
+
+    private val _clockShouldBeCentered = MutableStateFlow(true)
+    override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
+
     override val isKeyguardShowing: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
@@ -663,6 +681,14 @@
         _isActiveDreamLockscreenHosted.value = isLockscreenHosted
     }
 
+    override fun setClockSize(@ClockSize size: Int) {
+        _clockSize.value = size
+    }
+
+    override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
+        _clockShouldBeCentered.value = shouldBeCentered
+    }
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index dad5831..2f103f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -17,21 +17,35 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.keyguard.ClockEventController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
+private val TAG = KeyguardClockInteractor::class.simpleName
+/** Manages keyguard clock for the lockscreen root view. */
 /** Encapsulates business-logic related to the keyguard clock. */
 @SysUISingleton
 class KeyguardClockInteractor
 @Inject
 constructor(
-    repository: KeyguardClockRepository,
+    val eventController: ClockEventController,
+    private val keyguardClockRepository: KeyguardClockRepository,
 ) {
-    val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize
 
-    val currentClockId: Flow<ClockId> = repository.currentClockId
+    val selectedClockSize: Flow<SettingsClockSize> = keyguardClockRepository.selectedClockSize
+
+    val currentClockId: Flow<ClockId> = keyguardClockRepository.currentClockId
+
+    val currentClock: Flow<ClockController> = keyguardClockRepository.currentClock
+
+    var clock: ClockController?
+        get() = eventController.clock
+        set(value) {
+            eventController.clock = value
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index eaec0d4..e256b49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -23,6 +23,7 @@
 import android.graphics.Point
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch.ClockSize
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -241,6 +242,10 @@
             }
         }
 
+    val clockSize: Flow<Int> = repository.clockSize.distinctUntilChanged()
+
+    val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
+
     /** Whether to animate the next doze mode transition. */
     val animateDozingTransitions: Flow<Boolean> by lazy {
         if (sceneContainerFlags.isEnabled()) {
@@ -315,6 +320,14 @@
         repository.setAnimateDozingTransitions(animate)
     }
 
+    fun setClockSize(@ClockSize size: Int) {
+        repository.setClockSize(size)
+    }
+
+    fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
+        repository.setClockShouldBeCentered(shouldBeCentered)
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
new file mode 100644
index 0000000..c688cfff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.transition.TransitionManager
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
+import kotlinx.coroutines.launch
+
+private val TAG = KeyguardClockViewBinder::class.simpleName
+
+object KeyguardClockViewBinder {
+    @JvmStatic
+    fun bind(
+        clockSection: ClockSection,
+        keyguardRootView: ConstraintLayout,
+        viewModel: KeyguardClockViewModel,
+        keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+        keyguardClockInteractor: KeyguardClockInteractor,
+        featureFlags: FeatureFlagsClassic,
+    ) {
+        keyguardRootView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                keyguardClockInteractor.eventController.registerListeners(keyguardRootView)
+            }
+        }
+        keyguardRootView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) return@launch
+                    viewModel.currentClock.collect { currentClock ->
+                        viewModel.clock?.let { clock -> cleanupClockViews(clock, keyguardRootView) }
+                        viewModel.clock = currentClock
+                        addClockViews(currentClock, keyguardRootView)
+                        keyguardBlueprintInteractor.refreshBlueprint()
+                    }
+                }
+                // TODO: Weather clock dozing animation
+                // will trigger both shouldBeCentered and clockSize change
+                // we should avoid this
+                launch {
+                    if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) return@launch
+                    viewModel.clockSize.collect {
+                        applyConstraints(clockSection, keyguardRootView, true)
+                    }
+                }
+                launch {
+                    if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) return@launch
+                    viewModel.clockShouldBeCentered.collect { shouldBeCentered ->
+                        clockSection.setClockShouldBeCentered(
+                            viewModel.useLargeClock && shouldBeCentered
+                        )
+                        applyConstraints(clockSection, keyguardRootView, true)
+                    }
+                }
+            }
+        }
+    }
+
+    fun applyConstraints(
+        clockSection: ClockSection,
+        rootView: ConstraintLayout,
+        animated: Boolean
+    ) {
+        val constraintSet = ConstraintSet().apply { clone(rootView) }
+        clockSection.applyConstraints(constraintSet)
+        if (animated) {
+            TransitionManager.beginDelayedTransition(rootView)
+        }
+
+        constraintSet.applyTo(rootView)
+    }
+
+    private fun cleanupClockViews(clock: ClockController, rootView: ConstraintLayout) {
+        clock.smallClock.layout.views.forEach { rootView.removeView(it) }
+        clock.largeClock.layout.views.forEach { rootView.removeView(it) }
+    }
+
+    private fun addClockViews(clock: ClockController, rootView: ConstraintLayout) {
+        clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
+        if (clock.largeClock.layout.views.size == 1) {
+            clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large
+        }
+        // small clock should either be a single view or container with id `lockscreen_clock_view`
+        clock.smallClock.layout.views.forEach { rootView.addView(it) }
+        clock.largeClock.layout.views.forEach { rootView.addView(it) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
new file mode 100644
index 0000000..41a2e50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -0,0 +1,28 @@
+package com.android.systemui.keyguard.ui.binder
+
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+
+object KeyguardSmartspaceViewBinder {
+    @JvmStatic
+    fun bind(
+        smartspaceSection: SmartspaceSection,
+        keyguardRootView: ConstraintLayout,
+        clockViewModel: KeyguardClockViewModel,
+    ) {
+        keyguardRootView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                clockViewModel.hasCustomWeatherDataDisplay.collect {
+                    val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
+                    smartspaceSection.applyConstraints(constraintSet)
+                    constraintSet.applyTo(keyguardRootView)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 21eba56..f1ea446 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
@@ -30,6 +31,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import javax.inject.Inject
 
@@ -55,6 +57,8 @@
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+    clockSection: ClockSection,
+    smartspaceSection: SmartspaceSection
 ) : KeyguardBlueprint {
     override val id: String = DEFAULT
 
@@ -72,6 +76,8 @@
             aodNotificationIconsSection,
             aodBurnInSection,
             communalTutorialIndicatorSection,
+            clockSection,
+            smartspaceSection
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 0390077..06bb0a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@@ -53,6 +54,7 @@
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationIconAreaController: NotificationIconAreaController,
+    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
 ) : KeyguardSection() {
 
     private var nicBindingDisposable: DisposableHandle? = null
@@ -115,9 +117,19 @@
             } else {
                 BOTTOM
             }
-
         constraintSet.apply {
-            connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
+            if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+                connect(
+                    nicId,
+                    TOP,
+                    smartspaceViewModel.smartspaceViewId,
+                    topAlignment,
+                    bottomMargin
+                )
+                setGoneMargin(nicId, topAlignment, bottomMargin)
+            } else {
+                connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
+            }
             connect(nicId, START, PARENT_ID, START)
             connect(nicId, END, PARENT_ID, END)
             constrainHeight(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
new file mode 100644
index 0000000..941c295
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.items
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockFaceLayout
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.util.Utils
+import dagger.Lazy
+import javax.inject.Inject
+
+internal fun ConstraintSet.setVisibility(
+    views: Iterable<View>,
+    visibility: Int,
+) = views.forEach { view -> this.setVisibility(view.id, visibility) }
+
+internal fun ConstraintSet.setAlpha(
+    views: Iterable<View>,
+    alpha: Float,
+) = views.forEach { view -> this.setAlpha(view.id, alpha) }
+
+class ClockSection
+@Inject
+constructor(
+    private val clockInteractor: KeyguardClockInteractor,
+    private val keyguardClockViewModel: KeyguardClockViewModel,
+    val smartspaceViewModel: KeyguardSmartspaceViewModel,
+    private val context: Context,
+    private val splitShadeStateController: SplitShadeStateController,
+    private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+    private val featureFlags: FeatureFlagsClassic,
+) : KeyguardSection() {
+    override fun addViews(constraintLayout: ConstraintLayout) {}
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        KeyguardClockViewBinder.bind(
+            this,
+            constraintLayout,
+            keyguardClockViewModel,
+            keyguardBlueprintInteractor.get(),
+            clockInteractor,
+            featureFlags
+        )
+    }
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        clockInteractor.clock?.let { clock ->
+            constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {}
+
+    private fun buildConstraints(
+        clock: ClockController,
+        constraintSet: ConstraintSet
+    ): ConstraintSet {
+        // Add constraint between rootView and clockContainer
+        applyDefaultConstraints(constraintSet)
+        getTargetClockFace(clock).applyConstraints(constraintSet)
+
+        // Add constraint between elements in clock and clock container
+        return constraintSet.apply {
+            setAlpha(getTargetClockFace(clock).views, 1F)
+            setAlpha(getNonTargetClockFace(clock).views, 0F)
+        }
+    }
+
+    var largeClockEndGuideline = PARENT_ID
+
+    // Return if largeClockEndGuideline changes,
+    // and use it to decide whether to refresh blueprint
+    fun setClockShouldBeCentered(shouldBeCentered: Boolean): Boolean {
+        val previousValue = largeClockEndGuideline
+        largeClockEndGuideline = if (shouldBeCentered) PARENT_ID else R.id.split_shade_guideline
+        return previousValue != largeClockEndGuideline
+    }
+
+    fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
+        if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock)
+        else getSmallClockFace(clock)
+    fun getNonTargetClockFace(clock: ClockController): ClockFaceLayout =
+        if (keyguardClockViewModel.useLargeClock) getSmallClockFace(clock)
+        else getLargeClockFace(clock)
+
+    fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
+    fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
+    fun applyDefaultConstraints(constraints: ConstraintSet) {
+        constraints.apply {
+            connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+            connect(R.id.lockscreen_clock_view_large, END, largeClockEndGuideline, END)
+            connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+            var largeClockTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+                    context.resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_padding_top
+                    ) +
+                    context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+            largeClockTopMargin += smartspaceViewModel.getDimen(DATE_WEATHER_VIEW_HEIGHT)
+            largeClockTopMargin += smartspaceViewModel.getDimen(ENHANCED_SMARTSPACE_HEIGHT)
+            if (!keyguardClockViewModel.useLargeClock) {
+                largeClockTopMargin -=
+                    context.resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_height
+                    )
+            }
+            connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+            constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+            constrainHeight(
+                R.id.lockscreen_clock_view,
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.small_clock_height
+                )
+            )
+            connect(
+                R.id.lockscreen_clock_view,
+                START,
+                PARENT_ID,
+                START,
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.clock_padding_start
+                )
+            )
+            var smallClockTopMargin =
+                if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
+                    context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+                } else {
+                    context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+                        Utils.getStatusBarHeaderHeightKeyguard(context)
+                }
+            if (keyguardClockViewModel.useLargeClock) {
+                smallClockTopMargin -=
+                    context.resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_height
+                    )
+            }
+            connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+        }
+    }
+
+    companion object {
+        private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
+        private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index afa49bd..1995661 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -27,10 +27,11 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.res.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -47,6 +48,7 @@
     private val sharedNotificationContainer: SharedNotificationContainer,
     private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
     private val controller: NotificationStackScrollLayoutController,
+    private val smartspaceViewModel: KeyguardSmartspaceViewModel
 ) : KeyguardSection() {
     private val placeHolderId = R.id.nssl_placeholder
 
@@ -92,13 +94,25 @@
                 } else {
                     BOTTOM
                 }
-            connect(
-                R.id.nssl_placeholder,
-                TOP,
-                R.id.keyguard_status_view,
-                topAlignment,
-                bottomMargin
-            )
+            if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+                connect(
+                    R.id.nssl_placeholder,
+                    TOP,
+                    smartspaceViewModel.smartspaceViewId,
+                    topAlignment,
+                    bottomMargin
+                )
+                setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
+            } else {
+                connect(
+                    R.id.nssl_placeholder,
+                    TOP,
+                    R.id.keyguard_status_view,
+                    topAlignment,
+                    bottomMargin
+                )
+            }
+
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
             connect(R.id.nssl_placeholder, BOTTOM, R.id.lock_icon_view, TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
new file mode 100644
index 0000000..25931a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import javax.inject.Inject
+
+class SmartspaceSection
+@Inject
+constructor(
+    val keyguardClockViewModel: KeyguardClockViewModel,
+    val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+    private val context: Context,
+    val smartspaceController: LockscreenSmartspaceController,
+    val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+    val featureFlags: FeatureFlagsClassic,
+) : KeyguardSection() {
+    var smartspaceView: View? = null
+    var weatherView: View? = null
+    var dateView: View? = null
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            return
+        }
+        smartspaceView = smartspaceController.buildAndConnectView(constraintLayout)
+        weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
+        dateView = smartspaceController.buildAndConnectDateView(constraintLayout)
+        if (keyguardSmartspaceViewModel.isSmartspaceEnabled) {
+            constraintLayout.addView(smartspaceView)
+            if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
+                constraintLayout.addView(weatherView)
+                constraintLayout.addView(dateView)
+            }
+        }
+
+        keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        KeyguardSmartspaceViewBinder.bind(
+            this,
+            constraintLayout,
+            keyguardClockViewModel,
+        )
+    }
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        // Generally, weather should be next to dateView
+        // smartspace should be below date & weather views
+        constraintSet.apply {
+            // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
+            dateView?.let { dateView ->
+                constrainHeight(dateView.id, WRAP_CONTENT)
+                constrainWidth(dateView.id, WRAP_CONTENT)
+                connect(
+                    dateView.id,
+                    START,
+                    PARENT_ID,
+                    START,
+                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+                )
+            }
+            weatherView?.let {
+                constrainWidth(it.id, WRAP_CONTENT)
+                dateView?.let { dateView ->
+                    connect(it.id, TOP, dateView.id, TOP)
+                    connect(it.id, BOTTOM, dateView.id, BOTTOM)
+                    connect(it.id, START, dateView.id, END, 4)
+                }
+            }
+            // migrate addSmartspaceView from KeyguardClockSwitchController
+            smartspaceView?.let {
+                constrainHeight(it.id, WRAP_CONTENT)
+                connect(
+                    it.id,
+                    START,
+                    PARENT_ID,
+                    START,
+                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+                )
+                connect(
+                    it.id,
+                    END,
+                    PARENT_ID,
+                    END,
+                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
+                )
+            }
+
+            if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+                dateView?.let { dateView ->
+                    smartspaceView?.let { smartspaceView ->
+                        connect(dateView.id, BOTTOM, smartspaceView.id, TOP)
+                    }
+                }
+            } else {
+                dateView?.let { dateView ->
+                    clear(dateView.id, BOTTOM)
+                    connect(dateView.id, TOP, R.id.lockscreen_clock_view, BOTTOM)
+                    constrainHeight(dateView.id, WRAP_CONTENT)
+                    smartspaceView?.let { smartspaceView ->
+                        clear(smartspaceView.id, TOP)
+                        connect(smartspaceView.id, TOP, dateView.id, BOTTOM)
+                    }
+                }
+            }
+            updateVisibility(constraintSet)
+        }
+    }
+
+    private fun updateVisibility(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            weatherView?.let {
+                setVisibility(
+                    it.id,
+                    when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+                        true -> ConstraintSet.GONE
+                        false ->
+                            when (keyguardSmartspaceViewModel.isWeatherEnabled) {
+                                true -> ConstraintSet.VISIBLE
+                                false -> ConstraintSet.GONE
+                            }
+                    }
+                )
+            }
+            dateView?.let {
+                setVisibility(
+                    it.id,
+                    if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
+                    else ConstraintSet.VISIBLE
+                )
+            }
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        listOf(smartspaceView, dateView, weatherView).forEach {
+            it?.let {
+                if (it.parent == constraintLayout) {
+                    constraintLayout.removeView(it)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
new file mode 100644
index 0000000..c54f47b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.ClockController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class KeyguardClockViewModel
+@Inject
+constructor(
+    val keyguardInteractor: KeyguardInteractor,
+    val keyguardClockInteractor: KeyguardClockInteractor,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    val useLargeClock: Boolean
+        get() = clockSize.value == LARGE
+
+    var clock: ClockController?
+        set(value) {
+            keyguardClockInteractor.clock = value
+        }
+        get() {
+            return keyguardClockInteractor.clock
+        }
+
+    val clockSize =
+        combine(keyguardClockInteractor.selectedClockSize, keyguardInteractor.clockSize) {
+                selectedSize,
+                clockSize ->
+                if (selectedSize == SettingsClockSize.SMALL) {
+                    SMALL
+                } else {
+                    clockSize
+                }
+            }
+            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = LARGE
+            )
+
+    val currentClock = keyguardClockInteractor.currentClock
+
+    val hasCustomWeatherDataDisplay =
+        combine(clockSize, currentClock) { size, clock ->
+                (if (size == LARGE) clock.largeClock.config.hasCustomWeatherDataDisplay
+                else clock.smallClock.config.hasCustomWeatherDataDisplay)
+            }
+            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false
+            )
+
+    val clockShouldBeCentered: Flow<Boolean> =
+        keyguardInteractor.clockShouldBeCentered.distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
new file mode 100644
index 0000000..8e33651
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSmartspaceViewModel
+@Inject
+constructor(val context: Context, smartspaceController: LockscreenSmartspaceController) {
+    val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
+    val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
+    val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
+    val smartspaceViewId: Int
+        get() {
+            return context.resources
+                .getIdentifier("bc_smartspace_view", "id", context.packageName)
+                .also {
+                    if (it == 0) {
+                        Log.d(TAG, "Cannot resolve id bc_smartspace_view")
+                    }
+                }
+        }
+
+    fun getDimen(name: String): Int {
+        val res = context.packageManager.getResourcesForApplication(context.packageName)
+        val id = res.getIdentifier(name, "dimen", context.packageName)
+        return res.getDimensionPixelSize(id)
+    }
+
+    companion object {
+        private val TAG = KeyguardSmartspaceViewModel::class.java.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index dfe6adc..a21268a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1600,8 +1600,12 @@
         int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
         boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
-        mKeyguardStatusViewController.displayClock(computeDesiredClockSize(),
-                shouldAnimateClockChange);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            mKeyguardInteractor.setClockSize(computeDesiredClockSize());
+        } else {
+            mKeyguardStatusViewController.displayClock(computeDesiredClockSize(),
+                    shouldAnimateClockChange);
+        }
         updateKeyguardStatusViewAlignment(/* animate= */shouldAnimateKeyguardStatusViewAlignment());
         int userSwitcherHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
@@ -1729,9 +1733,13 @@
         } else {
             layout = mNotificationContainerParent;
         }
-        mKeyguardStatusViewController.updateAlignment(
-                layout, mSplitShadeEnabled, shouldBeCentered, animate);
-        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) {
+            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+        } else {
+            mKeyguardStatusViewController.updateAlignment(
+                    layout, mSplitShadeEnabled, shouldBeCentered, animate);
+            mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
+        }
     }
 
     private boolean shouldKeyguardStatusViewBeCentered() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 4a799d8..aabe633 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT;
 import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -40,6 +41,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
 import com.android.systemui.log.LogBuffer;
@@ -124,6 +126,9 @@
     @Mock
     protected LogBuffer mLogBuffer;
 
+    @Mock
+    protected KeyguardClockInteractor mKeyguardClockInteractor;
+
     protected final View mFakeDateView = (View) (new ViewGroup(mContext) {
         @Override
         protected void onLayout(boolean changed, int l, int t, int r, int b) {}
@@ -176,6 +181,7 @@
         mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
         mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
         mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false);
+        mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false);
         mController = new KeyguardClockSwitchController(
                 mView,
                 mStatusBarStateController,
@@ -197,6 +203,7 @@
                 mock(DozeParameters.class),
                 mock(AlwaysOnDisplayNotificationIconViewStore.class),
                 KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
+                mKeyguardClockInteractor,
                 mFakeFeatureFlags
         );
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
new file mode 100644
index 0000000..bc40c2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import kotlin.test.Test
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardClockRepositoryTest : SysuiTestCase() {
+
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var scope: TestScope
+
+    private lateinit var underTest: KeyguardClockRepository
+    private lateinit var fakeSettings: FakeSettings
+    @Mock private lateinit var clockRegistry: ClockRegistry
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeSettings = FakeSettings()
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
+        scope = TestScope(dispatcher)
+        underTest = KeyguardClockRepository(fakeSettings, clockRegistry, dispatcher)
+    }
+
+    @Test
+    fun testSelectedClockSize_small() =
+        scope.runTest {
+            fakeSettings.putInt(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 0)
+            val value = collectLastValue(underTest.selectedClockSize)
+            Truth.assertThat(value()).isEqualTo(SettingsClockSize.SMALL)
+        }
+
+    @Test
+    fun testSelectedClockSize_dynamic() =
+        scope.runTest {
+            fakeSettings.putInt(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+            val value = collectLastValue(underTest.selectedClockSize)
+            Truth.assertThat(value()).isEqualTo(SettingsClockSize.DYNAMIC)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 8cfa87d..2831053 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
@@ -37,6 +38,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.util.mockito.whenever
 import org.junit.Before
@@ -67,6 +69,8 @@
     @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
     @Mock private lateinit var aodBurnInSection: AodBurnInSection
     @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection
+    @Mock private lateinit var clockSection: ClockSection
+    @Mock private lateinit var smartspaceSection: SmartspaceSection
 
     @Before
     fun setup() {
@@ -86,6 +90,8 @@
                 aodNotificationIconsSection,
                 aodBurnInSection,
                 communalTutorialIndicatorSection,
+                clockSection,
+                smartspaceSection,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
new file mode 100644
index 0000000..6b85cf7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.util.Utils
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class ClockSectionTest : SysuiTestCase() {
+    @Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
+    @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
+    @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
+    @Mock private lateinit var splitShadeStateController: SplitShadeStateController
+    @Mock private lateinit var keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+    private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
+
+    private lateinit var underTest: ClockSection
+
+    // smartspaceViewModel.getDimen("date_weather_view_height")
+    private val SMART_SPACE_DATE_WEATHER_HEIGHT = 10
+
+    // smartspaceViewModel.getDimen("enhanced_smartspace_height")
+    private val ENHANCED_SMART_SPACE_HEIGHT = 11
+
+    private val SMALL_CLOCK_TOP_SPLIT_SHADE =
+        context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+
+    private val SMALL_CLOCK_TOP_NON_SPLIT_SHADE =
+        context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+            Utils.getStatusBarHeaderHeightKeyguard(context)
+
+    private val LARGE_CLOCK_TOP =
+        context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+            context.resources.getDimensionPixelSize(
+                com.android.systemui.customization.R.dimen.small_clock_padding_top
+            ) +
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+            SMART_SPACE_DATE_WEATHER_HEIGHT +
+            ENHANCED_SMART_SPACE_HEIGHT
+
+    private val CLOCK_FADE_TRANSLATION_Y =
+        context.resources.getDimensionPixelSize(
+            com.android.systemui.customization.R.dimen.small_clock_height
+        )
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(smartspaceViewModel.getDimen("date_weather_view_height"))
+            .thenReturn(SMART_SPACE_DATE_WEATHER_HEIGHT)
+        whenever(smartspaceViewModel.getDimen("enhanced_smartspace_height"))
+            .thenReturn(ENHANCED_SMART_SPACE_HEIGHT)
+        featureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, true)
+        underTest =
+            ClockSection(
+                keyguardClockInteractor,
+                keyguardClockViewModel,
+                smartspaceViewModel,
+                mContext,
+                splitShadeStateController,
+                keyguardBlueprintInteractor,
+                featureFlags
+            )
+    }
+
+    @Test
+    fun testApplyDefaultConstraints_LargeClock_SplitShade() {
+        setLargeClock(true)
+        setSplitShade(true)
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+
+        val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
+        assetLargeClockTop(cs, expectedLargeClockTopMargin)
+
+        val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE - CLOCK_FADE_TRANSLATION_Y
+        assetSmallClockTop(cs, expectedSmallClockTopMargin)
+    }
+
+    @Test
+    fun testApplyDefaultConstraints_LargeClock_NonSplitShade() {
+        setLargeClock(true)
+        setSplitShade(false)
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+
+        val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
+        assetLargeClockTop(cs, expectedLargeClockTopMargin)
+
+        val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE - CLOCK_FADE_TRANSLATION_Y
+        assetSmallClockTop(cs, expectedSmallClockTopMargin)
+    }
+
+    @Test
+    fun testApplyDefaultConstraints_SmallClock_SplitShade() {
+        setLargeClock(false)
+        setSplitShade(true)
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+
+        val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+        assetLargeClockTop(cs, expectedLargeClockTopMargin)
+
+        val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
+        assetSmallClockTop(cs, expectedSmallClockTopMargin)
+    }
+
+    @Test
+    fun testApplyDefaultConstraints_SmallClock_NonSplitShade() {
+        setLargeClock(false)
+        setSplitShade(false)
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+        val expectedLargeClockTopMargin = LARGE_CLOCK_TOP - CLOCK_FADE_TRANSLATION_Y
+        assetLargeClockTop(cs, expectedLargeClockTopMargin)
+
+        val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
+        assetSmallClockTop(cs, expectedSmallClockTopMargin)
+    }
+
+    @Test
+    fun testLargeClockShouldBeCentered() {
+        underTest.setClockShouldBeCentered(true)
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+        val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
+        assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testLargeClockShouldNotBeCentered() {
+        underTest.setClockShouldBeCentered(false)
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+        val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
+        assertThat(constraint.layout.endToEnd).isEqualTo(R.id.split_shade_guideline)
+    }
+
+    private fun setLargeClock(useLargeClock: Boolean) {
+        whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
+    }
+
+    private fun setSplitShade(isInSplitShade: Boolean) {
+        whenever(splitShadeStateController.shouldUseSplitNotificationShade(context.resources))
+            .thenReturn(isInSplitShade)
+    }
+
+    private fun assetLargeClockTop(cs: ConstraintSet, expectedLargeClockTopMargin: Int) {
+        val largeClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
+        assertThat(largeClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(largeClockConstraint.layout.topMargin).isEqualTo(expectedLargeClockTopMargin)
+    }
+
+    private fun assetSmallClockTop(cs: ConstraintSet, expectedSmallClockTopMargin: Int) {
+        val smallClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view)
+        assertThat(smallClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(smallClockConstraint.layout.topMargin).isEqualTo(expectedSmallClockTopMargin)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
new file mode 100644
index 0000000..02bafd0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.GONE
+import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.StateFlow
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class SmartspaceSectionTest : SysuiTestCase() {
+
+    private lateinit var underTest: SmartspaceSection
+    @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
+    @Mock private lateinit var keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel
+    @Mock private lateinit var lockscreenSmartspaceController: LockscreenSmartspaceController
+    @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+    @Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean>
+    private lateinit var mFakeFeatureFlags: FakeFeatureFlagsClassic
+
+    private val smartspaceView = View(mContext).also { it.id = View.generateViewId() }
+    private val weatherView = View(mContext).also { it.id = View.generateViewId() }
+    private val dateView = View(mContext).also { it.id = View.generateViewId() }
+    private lateinit var constraintLayout: ConstraintLayout
+    private lateinit var constraintSet: ConstraintSet
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFakeFeatureFlags = FakeFeatureFlagsClassic()
+        mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, true)
+        underTest =
+            SmartspaceSection(
+                keyguardClockViewModel,
+                keyguardSmartspaceViewModel,
+                mContext,
+                lockscreenSmartspaceController,
+                keyguardUnlockAnimationController,
+                mFakeFeatureFlags
+            )
+        constraintLayout = ConstraintLayout(mContext)
+        whenever(lockscreenSmartspaceController.buildAndConnectView(constraintLayout))
+            .thenReturn(smartspaceView)
+        whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(constraintLayout))
+            .thenReturn(weatherView)
+        whenever(lockscreenSmartspaceController.buildAndConnectDateView(constraintLayout))
+            .thenReturn(dateView)
+        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
+            .thenReturn(hasCustomWeatherDataDisplay)
+        constraintSet = ConstraintSet()
+    }
+
+    @Test
+    fun testAddViews_notSmartspaceEnabled() {
+        whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(false)
+        val constraintLayout = ConstraintLayout(mContext)
+        underTest.addViews(constraintLayout)
+        assertThat(smartspaceView.parent).isNull()
+        assertThat(weatherView.parent).isNull()
+        assertThat(dateView.parent).isNull()
+    }
+
+    @Test
+    fun testAddViews_smartspaceEnabled_dateWeatherDecoupled() {
+        whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
+        whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
+        underTest.addViews(constraintLayout)
+        assert(smartspaceView.parent == constraintLayout)
+        assert(weatherView.parent == constraintLayout)
+        assert(dateView.parent == constraintLayout)
+    }
+
+    @Test
+    fun testAddViews_smartspaceEnabled_notDateWeatherDecoupled() {
+        whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
+        whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(false)
+        underTest.addViews(constraintLayout)
+        assert(smartspaceView.parent == constraintLayout)
+        assert(weatherView.parent == null)
+        assert(dateView.parent == null)
+    }
+
+    @Test
+    fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
+        whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
+        whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
+        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+        underTest.addViews(constraintLayout)
+        underTest.applyConstraints(constraintSet)
+        assertWeatherSmartspaceConstrains(constraintSet)
+
+        val smartspaceConstraints = constraintSet.getConstraint(smartspaceView.id)
+        assertThat(smartspaceConstraints.layout.topToBottom).isEqualTo(dateView.id)
+
+        val dateConstraints = constraintSet.getConstraint(dateView.id)
+        assertThat(dateConstraints.layout.topToBottom).isEqualTo(R.id.lockscreen_clock_view)
+    }
+
+    @Test
+    fun testConstraintsWhenHasCustomWeatherDataDisplay() {
+        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+        underTest.addViews(constraintLayout)
+        underTest.applyConstraints(constraintSet)
+        assertWeatherSmartspaceConstrains(constraintSet)
+
+        val dateConstraints = constraintSet.getConstraint(dateView.id)
+        assertThat(dateConstraints.layout.bottomToTop).isEqualTo(smartspaceView.id)
+    }
+
+    @Test
+    fun testNormalDateWeatherVisibility() {
+        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+        whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
+        underTest.addViews(constraintLayout)
+        underTest.applyConstraints(constraintSet)
+        assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(VISIBLE)
+
+        whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(false)
+        underTest.applyConstraints(constraintSet)
+        assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(GONE)
+        assertThat(constraintSet.getVisibility(dateView.id)).isEqualTo(VISIBLE)
+    }
+    @Test
+    fun testCustomDateWeatherVisibility() {
+        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+        underTest.addViews(constraintLayout)
+        underTest.applyConstraints(constraintSet)
+
+        assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(GONE)
+        assertThat(constraintSet.getVisibility(dateView.id)).isEqualTo(GONE)
+    }
+
+    private fun assertWeatherSmartspaceConstrains(cs: ConstraintSet) {
+        val weatherConstraints = cs.getConstraint(weatherView.id)
+        assertThat(weatherConstraints.layout.topToTop).isEqualTo(dateView.id)
+        assertThat(weatherConstraints.layout.bottomToBottom).isEqualTo(dateView.id)
+        assertThat(weatherConstraints.layout.startToEnd).isEqualTo(dateView.id)
+        assertThat(weatherConstraints.layout.startMargin).isEqualTo(4)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
new file mode 100644
index 0000000..46a7735
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.provider.Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK
+import androidx.test.filters.SmallTest
+import com.android.keyguard.ClockEventController
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardClockViewModelTest : SysuiTestCase() {
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: CoroutineDispatcher
+    private lateinit var scope: TestScope
+
+    private lateinit var underTest: KeyguardClockViewModel
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var keyguardRepository: KeyguardRepository
+    private lateinit var keyguardClockInteractor: KeyguardClockInteractor
+    private lateinit var keyguardClockRepository: KeyguardClockRepository
+    private lateinit var fakeSettings: FakeSettings
+    @Mock private lateinit var clockRegistry: ClockRegistry
+    @Mock private lateinit var eventController: ClockEventController
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        KeyguardInteractorFactory.create().let {
+            keyguardInteractor = it.keyguardInteractor
+            keyguardRepository = it.repository
+        }
+        fakeSettings = FakeSettings()
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
+        scope = TestScope(dispatcher)
+        keyguardClockRepository = KeyguardClockRepository(fakeSettings, clockRegistry, dispatcher)
+        keyguardClockInteractor = KeyguardClockInteractor(eventController, keyguardClockRepository)
+        underTest =
+            KeyguardClockViewModel(
+                keyguardInteractor,
+                keyguardClockInteractor,
+                scope.backgroundScope
+            )
+    }
+
+    @Test
+    fun testClockSize_alwaysSmallClock() =
+        scope.runTest {
+            // When use double line clock is disabled,
+            // should always return small
+            fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 0)
+            keyguardRepository.setClockSize(LARGE)
+            val value = collectLastValue(underTest.clockSize)
+            assertThat(value()).isEqualTo(SMALL)
+        }
+
+    @Test
+    fun testClockSize_dynamicClockSize() =
+        scope.runTest {
+            fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+            keyguardRepository.setClockSize(SMALL)
+            var value = collectLastValue(underTest.clockSize)
+            assertThat(value()).isEqualTo(SMALL)
+
+            keyguardRepository.setClockSize(LARGE)
+            value = collectLastValue(underTest.clockSize)
+            assertThat(value()).isEqualTo(LARGE)
+        }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 88a88c7..d3744d55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
+import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -41,6 +42,11 @@
 class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
     private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
     override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone
+    private val _clockSize = MutableStateFlow<Int>(LARGE)
+    override val clockSize: Flow<Int> = _clockSize
+
+    private val _clockShouldBeCentered = MutableStateFlow<Boolean>(true)
+    override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered
 
     private val _dismissAction = MutableStateFlow<DismissAction>(DismissAction.None)
     override val dismissAction: StateFlow<DismissAction> = _dismissAction
@@ -177,6 +183,14 @@
         _deferKeyguardDone.emit(timing)
     }
 
+    override fun setClockSize(size: Int) {
+        _clockSize.value = size
+    }
+
+    override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
+        _clockShouldBeCentered.value = shouldBeCentered
+    }
+
     fun dozeTimeTick(millis: Long) {
         _dozeTimeTick.value = millis
     }