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
}