Merge "Priority ordering for keyguard ConstraintSet transitions" into main
diff --git a/packages/SystemUI/customization/src/com/android/systemui/util/ThreadAssert.kt b/packages/SystemUI/customization/src/com/android/systemui/util/ThreadAssert.kt
new file mode 100644
index 0000000..ccbf4ef
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/util/ThreadAssert.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.util
+
+/** Injectable helper providing thread assertions. */
+class ThreadAssert() {
+ fun isMainThread() = Assert.isMainThread()
+ fun isNotMainThread() = Assert.isNotMainThread()
+}
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2ab0813..71ae0d7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -228,6 +228,7 @@
<item type="id" name="ambient_indication_container" />
<item type="id" name="status_view_media_container" />
<item type="id" name="smart_space_barrier_bottom" />
+ <item type="id" name="small_clock_guideline_top" />
<item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
<!-- Privacy dialog -->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index abe49ee..86b99ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -100,6 +100,7 @@
private val keyguardClockViewModel: KeyguardClockViewModel,
private val lockscreenContentViewModel: LockscreenContentViewModel,
private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
+ private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -143,7 +144,7 @@
cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
keyguardRootView.addView(composeView)
} else {
- KeyguardBlueprintViewBinder.bind(
+ keyguardBlueprintViewBinder.bind(
keyguardRootView,
keyguardBlueprintViewModel,
keyguardClockViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 0b227fa..968c3e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -73,6 +73,7 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.ThreadAssert;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
@@ -223,6 +224,13 @@
return new KeyguardQuickAffordancesMetricsLoggerImpl();
}
+ /** */
+ @Provides
+ @SysUISingleton
+ static ThreadAssert providesThreadAssert() {
+ return new ThreadAssert();
+ }
+
/** Binds {@link KeyguardUpdateMonitor} as a {@link CoreStartable}. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 9381830..0659c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -17,14 +17,16 @@
package com.android.systemui.keyguard.data.repository
+import android.os.Handler
import android.util.Log
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
+import com.android.systemui.util.ThreadAssert
import java.io.PrintWriter
import java.util.TreeMap
import javax.inject.Inject
@@ -49,16 +51,17 @@
constructor(
configurationRepository: ConfigurationRepository,
blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
+ @Main val handler: Handler,
+ val assert: ThreadAssert,
) {
// This is TreeMap so that we can order the blueprints and assign numerical values to the
// blueprints in the adb tool.
private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> =
TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
- val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
- val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
- MutableSharedFlow(extraBufferCapacity = 1)
+ val refreshTransition = MutableSharedFlow<Config>(extraBufferCapacity = 1)
val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
+ private var targetTransitionConfig: Config? = null
/**
* Emits the blueprint value to the collectors.
@@ -105,14 +108,32 @@
blueprint?.let { this.blueprint.value = it }
}
- /** Re-emits the last emitted blueprint value if possible. */
- fun refreshBlueprint() {
- refreshBlueprintWithTransition(DefaultTransition)
- }
+ /**
+ * Re-emits the last emitted blueprint value if possible. This is delayed until next frame to
+ * dedupe requests and determine the correct transition to execute.
+ */
+ fun refreshBlueprint(config: Config = Config.DEFAULT) {
+ fun scheduleCallback() {
+ // We use a handler here instead of a CoroutineDipsatcher because the one provided by
+ // @Main CoroutineDispatcher is currently Dispatchers.Main.immediate, which doesn't
+ // delay the callback, and instead runs it imemdiately.
+ handler.post {
+ assert.isMainThread()
+ targetTransitionConfig?.let {
+ val success = refreshTransition.tryEmit(it)
+ if (!success) {
+ Log.e(TAG, "refreshBlueprint: Failed to emit blueprint refresh: $it")
+ }
+ }
+ targetTransitionConfig = null
+ }
+ }
- fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
- refreshBluePrint.tryEmit(Unit)
- refreshBlueprintTransition.tryEmit(type)
+ assert.isMainThread()
+ if ((targetTransitionConfig?.type?.priority ?: Int.MIN_VALUE) < config.type.priority) {
+ if (targetTransitionConfig == null) scheduleCallback()
+ targetTransitionConfig = config
+ }
}
/** Prints all available blueprints to the PrintWriter. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 566e006..56d64a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -24,13 +24,12 @@
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType.DefaultTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -44,20 +43,14 @@
private val splitShadeStateController: SplitShadeStateController,
) {
- /**
- * The current blueprint for the lockscreen.
- *
- * This flow can also emit the same blueprint value if refreshBlueprint is emitted.
- */
+ /** The current blueprint for the lockscreen. */
val blueprint: Flow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint
- val blueprintWithTransition =
- combine(
- keyguardBlueprintRepository.refreshBluePrint,
- keyguardBlueprintRepository.refreshBlueprintTransition
- ) { _, source ->
- source
- }
+ /**
+ * Triggered when the blueprint isn't changed, but the ConstraintSet should be rebuilt and
+ * optionally a transition should be fired to move to the rebuilt ConstraintSet.
+ */
+ val refreshTransition = keyguardBlueprintRepository.refreshTransition
init {
applicationScope.launch {
@@ -105,14 +98,11 @@
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
- /** Re-emits the blueprint value to the collectors. */
- fun refreshBlueprint() {
- keyguardBlueprintRepository.refreshBlueprint()
- }
+ /** Emits a value to refresh the blueprint with the appropriate transition. */
+ fun refreshBlueprint(type: Type = Type.NoTransition) = refreshBlueprint(Config(type))
- fun refreshBlueprintWithTransition(type: IntraBlueprintTransitionType = DefaultTransition) {
- keyguardBlueprintRepository.refreshBlueprintWithTransition(type)
- }
+ /** Emits a value to refresh the blueprint with the appropriate transition. */
+ fun refreshBlueprint(config: Config) = keyguardBlueprintRepository.refreshBlueprint(config)
fun getCurrentBlueprint(): KeyguardBlueprint {
return keyguardBlueprintRepository.blueprint.value
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 404046b..6e70368 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -17,7 +17,9 @@
package com.android.systemui.keyguard.ui.binder
+import android.os.Handler
import android.os.Trace
+import android.transition.Transition
import android.transition.TransitionManager
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
@@ -25,98 +27,168 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import javax.inject.Inject
+import kotlin.math.max
import kotlinx.coroutines.launch
-class KeyguardBlueprintViewBinder {
- companion object {
- private const val TAG = "KeyguardBlueprintViewBinder"
+private const val TAG = "KeyguardBlueprintViewBinder"
+private const val DEBUG = true
- fun bind(
- constraintLayout: ConstraintLayout,
- viewModel: KeyguardBlueprintViewModel,
- clockViewModel: KeyguardClockViewModel
- ) {
- constraintLayout.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- viewModel.blueprint.collect { blueprint ->
- val prevBluePrint = viewModel.currentBluePrint
- Trace.beginSection("KeyguardBlueprint#applyBlueprint")
- Log.d(TAG, "applying blueprint: $blueprint")
- TransitionManager.endTransitions(constraintLayout)
+@SysUISingleton
+class KeyguardBlueprintViewBinder
+@Inject
+constructor(
+ @Main private val handler: Handler,
+) {
+ private var runningPriority = -1
+ private val runningTransitions = mutableSetOf<Transition>()
+ private val isTransitionRunning: Boolean
+ get() = runningTransitions.size > 0
+ private val transitionListener =
+ object : Transition.TransitionListener {
+ override fun onTransitionCancel(transition: Transition) {
+ if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}")
+ runningTransitions.remove(transition)
+ }
- val cs =
- ConstraintSet().apply {
- clone(constraintLayout)
- val emptyLayout = ConstraintSet.Layout()
- knownIds.forEach {
- getConstraint(it).layout.copyFrom(emptyLayout)
- }
- blueprint.applyConstraints(this)
- }
+ override fun onTransitionEnd(transition: Transition) {
+ if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
+ runningTransitions.remove(transition)
+ }
- // Apply transition.
+ override fun onTransitionPause(transition: Transition) {
+ if (DEBUG) Log.i(TAG, "onTransitionPause: ${transition::class.simpleName}")
+ runningTransitions.remove(transition)
+ }
+
+ override fun onTransitionResume(transition: Transition) {
+ if (DEBUG) Log.i(TAG, "onTransitionResume: ${transition::class.simpleName}")
+ runningTransitions.add(transition)
+ }
+
+ override fun onTransitionStart(transition: Transition) {
+ if (DEBUG) Log.i(TAG, "onTransitionStart: ${transition::class.simpleName}")
+ runningTransitions.add(transition)
+ }
+ }
+
+ fun bind(
+ constraintLayout: ConstraintLayout,
+ viewModel: KeyguardBlueprintViewModel,
+ clockViewModel: KeyguardClockViewModel,
+ ) {
+ constraintLayout.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.blueprint.collect { blueprint ->
+ Trace.beginSection("KeyguardBlueprintViewBinder#applyBlueprint")
+ val prevBluePrint = viewModel.currentBluePrint
+
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ val emptyLayout = ConstraintSet.Layout()
+ knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
+ blueprint.applyConstraints(this)
+ }
+
+ var transition =
if (
!keyguardBottomAreaRefactor() &&
prevBluePrint != null &&
prevBluePrint != blueprint
) {
- TransitionManager.beginDelayedTransition(
- constraintLayout,
- BaseBlueprintTransition(clockViewModel)
- .addTransition(
- IntraBlueprintTransition(
- IntraBlueprintTransitionType.NoTransition,
- clockViewModel
- )
- )
- )
- } else {
- TransitionManager.beginDelayedTransition(
- constraintLayout,
- IntraBlueprintTransition(
- IntraBlueprintTransitionType.NoTransition,
- clockViewModel
+ BaseBlueprintTransition(clockViewModel)
+ .addTransition(
+ IntraBlueprintTransition(Config.DEFAULT, clockViewModel)
)
- )
+ } else {
+ IntraBlueprintTransition(Config.DEFAULT, clockViewModel)
}
- // Add and remove views of sections that are not contained by the
- // other.
+ runTransition(constraintLayout, transition, Config.DEFAULT) {
+ // Add and remove views of sections that are not contained by the other.
blueprint.replaceViews(prevBluePrint, constraintLayout)
cs.applyTo(constraintLayout)
-
- viewModel.currentBluePrint = blueprint
- Trace.endSection()
}
+
+ viewModel.currentBluePrint = blueprint
+ Trace.endSection()
}
+ }
- launch {
- viewModel.blueprintWithTransition.collect { source ->
- TransitionManager.endTransitions(constraintLayout)
+ launch {
+ viewModel.refreshTransition.collect { transition ->
+ Trace.beginSection("KeyguardBlueprintViewBinder#refreshTransition")
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ viewModel.currentBluePrint?.applyConstraints(this)
+ }
- val cs =
- ConstraintSet().apply {
- clone(constraintLayout)
- viewModel.currentBluePrint?.applyConstraints(this)
- }
-
- TransitionManager.beginDelayedTransition(
- constraintLayout,
- IntraBlueprintTransition(source, clockViewModel)
- )
+ runTransition(
+ constraintLayout,
+ IntraBlueprintTransition(transition, clockViewModel),
+ transition,
+ ) {
cs.applyTo(constraintLayout)
- Trace.endSection()
}
+ Trace.endSection()
}
}
}
}
}
+
+ private fun runTransition(
+ constraintLayout: ConstraintLayout,
+ transition: Transition,
+ config: Config,
+ apply: () -> Unit,
+ ) {
+ val currentPriority = if (isTransitionRunning) runningPriority else -1
+ if (config.checkPriority && config.type.priority < currentPriority) {
+ if (DEBUG) {
+ Log.w(
+ TAG,
+ "runTransition: skipping ${transition::class.simpleName}: " +
+ "currentPriority=$currentPriority; config=$config"
+ )
+ }
+ apply()
+ return
+ }
+
+ if (DEBUG) {
+ Log.i(
+ TAG,
+ "runTransition: running ${transition::class.simpleName}: " +
+ "currentPriority=$currentPriority; config=$config"
+ )
+ }
+
+ // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to
+ // the running set until the copy is started by the handler.
+ runningTransitions.add(transition)
+ transition.addListener(transitionListener)
+ runningPriority = max(currentPriority, config.type.priority)
+
+ handler.post {
+ if (config.terminatePrevious) {
+ TransitionManager.endTransitions(constraintLayout)
+ }
+
+ TransitionManager.beginDelayedTransition(constraintLayout, transition)
+ runningTransitions.remove(transition)
+ apply()
+ }
+ }
}
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
index 62a6e8b..01596ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -30,7 +30,7 @@
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -39,6 +39,8 @@
import kotlinx.coroutines.launch
object KeyguardClockViewBinder {
+ private val TAG = KeyguardClockViewBinder::class.simpleName!!
+
@JvmStatic
fun bind(
clockSection: ClockSection,
@@ -68,9 +70,7 @@
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
- blueprintInteractor.refreshBlueprintWithTransition(
- IntraBlueprintTransitionType.ClockSize
- )
+ blueprintInteractor.refreshBlueprint(Type.ClockSize)
}
}
launch {
@@ -83,13 +83,9 @@
it.largeClock.config.hasCustomPositionUpdatedAnimation &&
it.config.id == DEFAULT_CLOCK_ID
) {
- blueprintInteractor.refreshBlueprintWithTransition(
- IntraBlueprintTransitionType.DefaultClockStepping
- )
+ blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
} else {
- blueprintInteractor.refreshBlueprintWithTransition(
- IntraBlueprintTransitionType.DefaultTransition
- )
+ blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
}
}
}
@@ -102,9 +98,7 @@
if (
viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
) {
- blueprintInteractor.refreshBlueprintWithTransition(
- IntraBlueprintTransitionType.DefaultTransition
- )
+ blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
}
}
}
@@ -112,6 +106,7 @@
}
}
}
+
@VisibleForTesting
fun updateBurnInLayer(
keyguardRootView: ConstraintLayout,
@@ -171,6 +166,7 @@
}
}
}
+
fun applyConstraints(
clockSection: ClockSection,
rootView: ConstraintLayout,
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
index 08a2b9c..b77f0c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -23,6 +23,8 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -49,7 +51,13 @@
clockViewModel,
smartspaceViewModel
)
- blueprintInteractor.refreshBlueprintWithTransition()
+ blueprintInteractor.refreshBlueprint(
+ Config(
+ Type.SmartspaceVisibility,
+ checkPriority = false,
+ terminatePrevious = false,
+ )
+ )
}
}
@@ -57,7 +65,13 @@
if (!migrateClocksToBlueprint()) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
- blueprintInteractor.refreshBlueprintWithTransition()
+ blueprintInteractor.refreshBlueprint(
+ Config(
+ Type.SmartspaceVisibility,
+ checkPriority = false,
+ terminatePrevious = false,
+ )
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
index 524aa1a..a7075d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -21,25 +21,42 @@
import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-enum class IntraBlueprintTransitionType {
- ClockSize,
- ClockCenter,
- DefaultClockStepping,
- DefaultTransition,
- AodNotifIconsTransition,
- // When transition between blueprint, we don't need any duration or interpolator but we need
- // all elements go to correct state
- NoTransition,
-}
-
class IntraBlueprintTransition(
- type: IntraBlueprintTransitionType,
- clockViewModel: KeyguardClockViewModel
+ config: IntraBlueprintTransition.Config,
+ clockViewModel: KeyguardClockViewModel,
) : TransitionSet() {
+
+ enum class Type(
+ val priority: Int,
+ ) {
+ ClockSize(100),
+ ClockCenter(99),
+ DefaultClockStepping(98),
+ AodNotifIconsTransition(97),
+ SmartspaceVisibility(2),
+ DefaultTransition(1),
+ // When transition between blueprint, we don't need any duration or interpolator but we need
+ // all elements go to correct state
+ NoTransition(0),
+ }
+
+ data class Config(
+ val type: Type,
+ val checkPriority: Boolean = true,
+ val terminatePrevious: Boolean = true,
+ ) {
+ companion object {
+ val DEFAULT = Config(Type.NoTransition)
+ }
+ }
+
init {
ordering = ORDERING_TOGETHER
- if (type == IntraBlueprintTransitionType.DefaultClockStepping)
- addTransition(clockViewModel.clock?.let { DefaultClockSteppingTransition(it) })
- addTransition(ClockSizeTransition(type, clockViewModel))
+ when (config.type) {
+ Type.NoTransition -> {}
+ Type.DefaultClockStepping ->
+ addTransition(clockViewModel.clock?.let { DefaultClockSteppingTransition(it) })
+ else -> addTransition(ClockSizeTransition(config, clockViewModel))
+ }
}
}
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
index 631b342..54a7ca4 100644
--- 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
@@ -24,7 +24,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.INVISIBLE
+import androidx.constraintlayout.widget.ConstraintSet.GONE
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
@@ -52,11 +52,6 @@
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) }
-
open class ClockSection
@Inject
constructor(
@@ -105,7 +100,7 @@
// Add constraint between elements in clock and clock container
return constraintSet.apply {
setVisibility(getTargetClockFace(clock).views, VISIBLE)
- setVisibility(getNonTargetClockFace(clock).views, INVISIBLE)
+ setVisibility(getNonTargetClockFace(clock).views, GONE)
if (!keyguardClockViewModel.useLargeClock) {
connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
}
@@ -150,6 +145,7 @@
}
}
}
+
open fun applyDefaultConstraints(constraints: ConstraintSet) {
val guideline =
if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
@@ -168,8 +164,8 @@
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
- constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+ constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
@@ -190,11 +186,10 @@
context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
Utils.getStatusBarHeaderHeightKeyguard(context)
}
- if (keyguardClockViewModel.useLargeClock) {
- smallClockTopMargin -=
- context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
- }
- connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+
+ create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
+ setGuidelineBegin(R.id.small_clock_guideline_top, smallClockTopMargin)
+ connect(R.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM)
}
constrainWeatherClockDateIconsBarrier(constraints)
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
index 2f99719..8255bcc 100644
--- 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
@@ -53,14 +53,14 @@
private var dateView: View? = null
private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
+ private var pastVisibility: Int = -1
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
- return
- }
+ if (!migrateClocksToBlueprint()) return
smartspaceView = smartspaceController.buildAndConnectView(constraintLayout)
weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
dateView = smartspaceController.buildAndConnectDateView(constraintLayout)
+ pastVisibility = smartspaceView?.visibility ?: View.GONE
if (keyguardSmartspaceViewModel.isSmartspaceEnabled) {
constraintLayout.addView(smartspaceView)
if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
@@ -69,26 +69,20 @@
}
}
keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
- smartspaceVisibilityListener =
- object : OnGlobalLayoutListener {
- var pastVisibility = GONE
- override fun onGlobalLayout() {
- smartspaceView?.let {
- val newVisibility = it.visibility
- if (pastVisibility != newVisibility) {
- keyguardSmartspaceInteractor.setBcSmartspaceVisibility(newVisibility)
- pastVisibility = newVisibility
- }
- }
+ smartspaceVisibilityListener = OnGlobalLayoutListener {
+ smartspaceView?.let {
+ val newVisibility = it.visibility
+ if (pastVisibility != newVisibility) {
+ keyguardSmartspaceInteractor.setBcSmartspaceVisibility(newVisibility)
+ pastVisibility = newVisibility
}
}
+ }
smartspaceView?.viewTreeObserver?.addOnGlobalLayoutListener(smartspaceVisibilityListener)
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
- return
- }
+ if (!migrateClocksToBlueprint()) return
KeyguardSmartspaceViewBinder.bind(
constraintLayout,
keyguardClockViewModel,
@@ -98,9 +92,7 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!migrateClocksToBlueprint()) {
- return
- }
+ if (!migrateClocksToBlueprint()) return
val horizontalPaddingStart =
context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
@@ -196,9 +188,7 @@
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!migrateClocksToBlueprint()) {
- return
- }
+ if (!migrateClocksToBlueprint()) return
listOf(smartspaceView, dateView, weatherView).forEach {
it?.let {
if (it.parent == constraintLayout) {
@@ -211,6 +201,9 @@
}
private fun updateVisibility(constraintSet: ConstraintSet) {
+ // This may update the visibility of the smartspace views
+ smartspaceController.requestSmartspaceUpdate()
+
constraintSet.apply {
setVisibility(
sharedR.id.weather_smartspace_view,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 99565b1..64cbb32 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -17,175 +17,308 @@
package com.android.systemui.keyguard.ui.view.layout.sections.transitions
import android.animation.Animator
-import android.animation.ObjectAnimator
-import android.transition.ChangeBounds
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.transition.Transition
import android.transition.TransitionSet
import android.transition.TransitionValues
-import android.transition.Visibility
+import android.util.Log
import android.view.View
import android.view.ViewGroup
+import android.view.ViewTreeObserver.OnPreDrawListener
import com.android.app.animation.Interpolators
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
+import kotlin.math.abs
-const val CLOCK_OUT_MILLIS = 133L
-const val CLOCK_IN_MILLIS = 167L
-val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
-const val CLOCK_IN_START_DELAY_MILLIS = 133L
-val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
+internal fun View.setRect(rect: Rect) =
+ this.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom)
class ClockSizeTransition(
- val type: IntraBlueprintTransitionType,
- clockViewModel: KeyguardClockViewModel
+ config: IntraBlueprintTransition.Config,
+ clockViewModel: KeyguardClockViewModel,
) : TransitionSet() {
init {
ordering = ORDERING_TOGETHER
- addTransition(ClockOutTransition(clockViewModel, type))
- addTransition(ClockInTransition(clockViewModel, type))
- addTransition(SmartspaceChangeBounds(clockViewModel, type))
- addTransition(ClockInChangeBounds(clockViewModel, type))
- addTransition(ClockOutChangeBounds(clockViewModel, type))
+ if (config.type != Type.SmartspaceVisibility) {
+ addTransition(ClockFaceOutTransition(config, clockViewModel))
+ addTransition(ClockFaceInTransition(config, clockViewModel))
+ }
+ addTransition(SmartspaceMoveTransition(config, clockViewModel))
}
- class ClockInTransition(viewModel: KeyguardClockViewModel, type: IntraBlueprintTransitionType) :
- Visibility() {
- init {
- mode = MODE_IN
- if (type != IntraBlueprintTransitionType.NoTransition) {
- duration = CLOCK_IN_MILLIS
- startDelay = CLOCK_IN_START_DELAY_MILLIS
- interpolator = Interpolators.LINEAR_OUT_SLOW_IN
- } else {
- duration = 0
- startDelay = 0
- }
+ open class VisibilityBoundsTransition() : Transition() {
+ var captureSmartspace: Boolean = false
- addTarget(sharedR.id.bc_smartspace_view)
- addTarget(sharedR.id.date_smartspace_view)
- addTarget(sharedR.id.weather_smartspace_view)
- if (viewModel.useLargeClock) {
- viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
- } else {
- addTarget(R.id.lockscreen_clock_view)
- }
+ override fun captureEndValues(transition: TransitionValues) = captureValues(transition)
+ override fun captureStartValues(transition: TransitionValues) = captureValues(transition)
+ override fun getTransitionProperties(): Array<String> = TRANSITION_PROPERTIES
+ open fun mutateBounds(
+ view: View,
+ fromVis: Int,
+ toVis: Int,
+ fromBounds: Rect,
+ toBounds: Rect,
+ fromSSBounds: Rect?,
+ toSSBounds: Rect?
+ ) {}
+
+ private fun captureValues(transition: TransitionValues) {
+ val view = transition.view
+ transition.values[PROP_VISIBILITY] = view.visibility
+ transition.values[PROP_ALPHA] = view.alpha
+ transition.values[PROP_BOUNDS] = Rect(view.left, view.top, view.right, view.bottom)
+
+ if (!captureSmartspace) return
+ val ss = (view.parent as View).findViewById<View>(sharedR.id.bc_smartspace_view)
+ if (ss == null) return
+ transition.values[SMARTSPACE_BOUNDS] = Rect(ss.left, ss.top, ss.right, ss.bottom)
}
- override fun onAppear(
- sceneRoot: ViewGroup?,
- view: View,
+ override fun createAnimator(
+ sceenRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
- ): Animator {
- return ObjectAnimator.ofFloat(view, "alpha", 1f).also {
- it.duration = duration
- it.startDelay = startDelay
- it.interpolator = interpolator
- it.addUpdateListener { view.alpha = it.animatedValue as Float }
- it.start()
- }
- }
- }
+ ): Animator? {
+ if (startValues == null || endValues == null) return null
- class ClockOutTransition(
- viewModel: KeyguardClockViewModel,
- type: IntraBlueprintTransitionType
- ) : Visibility() {
- init {
- mode = MODE_OUT
- if (type != IntraBlueprintTransitionType.NoTransition) {
- duration = CLOCK_OUT_MILLIS
- interpolator = CLOCK_OUT_INTERPOLATOR
- } else {
- duration = 0
+ val fromView = startValues.view
+ var fromVis = startValues.values[PROP_VISIBILITY] as Int
+ var fromIsVis = fromVis == View.VISIBLE
+ var fromAlpha = startValues.values[PROP_ALPHA] as Float
+ val fromBounds = startValues.values[PROP_BOUNDS] as Rect
+ val fromSSBounds =
+ if (captureSmartspace) startValues.values[SMARTSPACE_BOUNDS] as Rect else null
+
+ val toView = endValues.view
+ val toVis = endValues.values[PROP_VISIBILITY] as Int
+ val toBounds = endValues.values[PROP_BOUNDS] as Rect
+ val toSSBounds =
+ if (captureSmartspace) endValues.values[SMARTSPACE_BOUNDS] as Rect else null
+ val toIsVis = toVis == View.VISIBLE
+ val toAlpha = if (toIsVis) 1f else 0f
+
+ // Align starting visibility and alpha
+ if (!fromIsVis) fromAlpha = 0f
+ else if (fromAlpha <= 0f) {
+ fromIsVis = false
+ fromVis = View.INVISIBLE
}
- addTarget(sharedR.id.bc_smartspace_view)
- addTarget(sharedR.id.date_smartspace_view)
- addTarget(sharedR.id.weather_smartspace_view)
- if (viewModel.useLargeClock) {
- addTarget(R.id.lockscreen_clock_view)
- } else {
- viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
- }
- }
-
- override fun onDisappear(
- sceneRoot: ViewGroup?,
- view: View,
- startValues: TransitionValues?,
- endValues: TransitionValues?
- ): Animator {
- return ObjectAnimator.ofFloat(view, "alpha", 0f).also {
- it.duration = duration
- it.interpolator = interpolator
- it.addUpdateListener { view.alpha = it.animatedValue as Float }
- it.start()
- }
- }
- }
-
- class ClockInChangeBounds(
- viewModel: KeyguardClockViewModel,
- type: IntraBlueprintTransitionType
- ) : ChangeBounds() {
- init {
- if (type != IntraBlueprintTransitionType.NoTransition) {
- duration = CLOCK_IN_MILLIS
- startDelay = CLOCK_IN_START_DELAY_MILLIS
- interpolator = CLOCK_IN_INTERPOLATOR
- } else {
- duration = 0
- startDelay = 0
+ mutateBounds(toView, fromVis, toVis, fromBounds, toBounds, fromSSBounds, toSSBounds)
+ if (fromIsVis == toIsVis && fromBounds.equals(toBounds)) {
+ if (DEBUG) {
+ Log.w(
+ TAG,
+ "Skipping no-op transition: $toView; " +
+ "vis: $fromVis -> $toVis; " +
+ "alpha: $fromAlpha -> $toAlpha; " +
+ "bounds: $fromBounds -> $toBounds; "
+ )
+ }
+ return null
}
- if (viewModel.useLargeClock) {
- viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
- } else {
- addTarget(R.id.lockscreen_clock_view)
- }
- }
- }
+ val sendToBack = fromIsVis && !toIsVis
+ fun lerp(start: Int, end: Int, fract: Float): Int =
+ (start * (1f - fract) + end * fract).toInt()
+ fun computeBounds(fract: Float): Rect =
+ Rect(
+ lerp(fromBounds.left, toBounds.left, fract),
+ lerp(fromBounds.top, toBounds.top, fract),
+ lerp(fromBounds.right, toBounds.right, fract),
+ lerp(fromBounds.bottom, toBounds.bottom, fract)
+ )
- class ClockOutChangeBounds(
- viewModel: KeyguardClockViewModel,
- type: IntraBlueprintTransitionType
- ) : ChangeBounds() {
- init {
- if (type != IntraBlueprintTransitionType.NoTransition) {
- duration = CLOCK_OUT_MILLIS
- interpolator = CLOCK_OUT_INTERPOLATOR
- } else {
- duration = 0
+ fun assignAnimValues(src: String, alpha: Float, fract: Float, vis: Int? = null) {
+ val bounds = computeBounds(fract)
+ if (DEBUG) Log.i(TAG, "$src: $toView; alpha=$alpha; vis=$vis; bounds=$bounds;")
+ toView.setVisibility(vis ?: View.VISIBLE)
+ toView.setAlpha(alpha)
+ toView.setRect(bounds)
}
- if (viewModel.useLargeClock) {
- addTarget(R.id.lockscreen_clock_view)
- } else {
- viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
- }
- }
- }
- class SmartspaceChangeBounds(
- viewModel: KeyguardClockViewModel,
- val type: IntraBlueprintTransitionType = IntraBlueprintTransitionType.DefaultTransition
- ) : ChangeBounds() {
- init {
- if (type != IntraBlueprintTransitionType.NoTransition) {
- duration =
- if (viewModel.useLargeClock) {
- STATUS_AREA_MOVE_UP_MILLIS
- } else {
- STATUS_AREA_MOVE_DOWN_MILLIS
+ if (DEBUG) {
+ Log.i(
+ TAG,
+ "transitioning: $toView; " +
+ "vis: $fromVis -> $toVis; " +
+ "alpha: $fromAlpha -> $toAlpha; " +
+ "bounds: $fromBounds -> $toBounds; "
+ )
+ }
+
+ return ValueAnimator.ofFloat(fromAlpha, toAlpha).also { anim ->
+ // We enforce the animation parameters on the target view every frame using a
+ // predraw listener. This is suboptimal but prevents issues with layout passes
+ // overwriting the animation for individual frames.
+ val predrawCallback = OnPreDrawListener {
+ assignAnimValues("predraw", anim.animatedValue as Float, anim.animatedFraction)
+ return@OnPreDrawListener true
+ }
+
+ anim.duration = duration
+ anim.startDelay = startDelay
+ anim.interpolator = interpolator
+ anim.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(anim: Animator) {
+ assignAnimValues("start", fromAlpha, 0f)
+ }
+
+ override fun onAnimationEnd(anim: Animator) {
+ assignAnimValues("end", toAlpha, 1f, toVis)
+ if (sendToBack) toView.translationZ = 0f
+ toView.viewTreeObserver.removeOnPreDrawListener(predrawCallback)
+ }
}
- interpolator = Interpolators.EMPHASIZED
- } else {
- duration = 0
+ )
+ toView.viewTreeObserver.addOnPreDrawListener(predrawCallback)
}
+ }
+
+ companion object {
+ private const val PROP_VISIBILITY = "ClockSizeTransition:Visibility"
+ private const val PROP_ALPHA = "ClockSizeTransition:Alpha"
+ private const val PROP_BOUNDS = "ClockSizeTransition:Bounds"
+ private const val SMARTSPACE_BOUNDS = "ClockSizeTransition:SSBounds"
+ private val TRANSITION_PROPERTIES =
+ arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
+
+ private val DEBUG = true
+ private val TAG = ClockFaceInTransition::class.simpleName!!
+ }
+ }
+
+ class ClockFaceInTransition(
+ config: IntraBlueprintTransition.Config,
+ val viewModel: KeyguardClockViewModel,
+ ) : VisibilityBoundsTransition() {
+ init {
+ duration = CLOCK_IN_MILLIS
+ startDelay = CLOCK_IN_START_DELAY_MILLIS
+ interpolator = CLOCK_IN_INTERPOLATOR
+ captureSmartspace = !viewModel.useLargeClock
+
+ if (viewModel.useLargeClock) {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ } else {
+ addTarget(R.id.lockscreen_clock_view)
+ }
+ }
+
+ override fun mutateBounds(
+ view: View,
+ fromVis: Int,
+ toVis: Int,
+ fromBounds: Rect,
+ toBounds: Rect,
+ fromSSBounds: Rect?,
+ toSSBounds: Rect?
+ ) {
+ fromBounds.left = toBounds.left
+ fromBounds.right = toBounds.right
+ if (viewModel.useLargeClock) {
+ // Large clock shouldn't move
+ fromBounds.top = toBounds.top
+ fromBounds.bottom = toBounds.bottom
+ } else if (toSSBounds != null && fromSSBounds != null) {
+ // Instead of moving the small clock the full distance, we compute the distance
+ // smartspace will move. We then scale this to match the duration of this animation
+ // so that the small clock moves at the same speed as smartspace.
+ val ssTranslation =
+ abs((toSSBounds.top - fromSSBounds.top) * SMALL_CLOCK_IN_MOVE_SCALE).toInt()
+ fromBounds.top = toBounds.top - ssTranslation
+ fromBounds.bottom = toBounds.bottom - ssTranslation
+ } else {
+ Log.e(TAG, "mutateBounds: smallClock received no smartspace bounds")
+ }
+ }
+
+ companion object {
+ const val CLOCK_IN_MILLIS = 167L
+ const val CLOCK_IN_START_DELAY_MILLIS = 133L
+ val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN
+ const val SMALL_CLOCK_IN_MOVE_SCALE =
+ CLOCK_IN_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_DOWN_MILLIS.toFloat()
+ private val TAG = ClockFaceInTransition::class.simpleName!!
+ }
+ }
+
+ class ClockFaceOutTransition(
+ config: IntraBlueprintTransition.Config,
+ val viewModel: KeyguardClockViewModel,
+ ) : VisibilityBoundsTransition() {
+ init {
+ duration = CLOCK_OUT_MILLIS
+ interpolator = CLOCK_OUT_INTERPOLATOR
+ captureSmartspace = viewModel.useLargeClock
+
+ if (viewModel.useLargeClock) {
+ addTarget(R.id.lockscreen_clock_view)
+ } else {
+ viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } }
+ }
+ }
+
+ override fun mutateBounds(
+ view: View,
+ fromVis: Int,
+ toVis: Int,
+ fromBounds: Rect,
+ toBounds: Rect,
+ fromSSBounds: Rect?,
+ toSSBounds: Rect?
+ ) {
+ toBounds.left = fromBounds.left
+ toBounds.right = fromBounds.right
+ if (!viewModel.useLargeClock) {
+ // Large clock shouldn't move
+ toBounds.top = fromBounds.top
+ toBounds.bottom = fromBounds.bottom
+ } else if (toSSBounds != null && fromSSBounds != null) {
+ // Instead of moving the small clock the full distance, we compute the distance
+ // smartspace will move. We then scale this to match the duration of this animation
+ // so that the small clock moves at the same speed as smartspace.
+ val ssTranslation =
+ abs((toSSBounds.top - fromSSBounds.top) * SMALL_CLOCK_OUT_MOVE_SCALE).toInt()
+ toBounds.top = fromBounds.top - ssTranslation
+ toBounds.bottom = fromBounds.bottom - ssTranslation
+ } else {
+ Log.w(TAG, "mutateBounds: smallClock received no smartspace bounds")
+ }
+ }
+
+ companion object {
+ const val CLOCK_OUT_MILLIS = 133L
+ val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR
+ const val SMALL_CLOCK_OUT_MOVE_SCALE =
+ CLOCK_OUT_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_UP_MILLIS.toFloat()
+ private val TAG = ClockFaceOutTransition::class.simpleName!!
+ }
+ }
+
+ // TODO: Might need a mechanism to update this one while in-progress
+ class SmartspaceMoveTransition(
+ val config: IntraBlueprintTransition.Config,
+ viewModel: KeyguardClockViewModel,
+ ) : VisibilityBoundsTransition() {
+ init {
+ duration =
+ if (viewModel.useLargeClock) STATUS_AREA_MOVE_UP_MILLIS
+ else STATUS_AREA_MOVE_DOWN_MILLIS
+ interpolator = Interpolators.EMPHASIZED
addTarget(sharedR.id.date_smartspace_view)
addTarget(sharedR.id.weather_smartspace_view)
addTarget(sharedR.id.bc_smartspace_view)
+
+ // Notifications normally and media on split shade needs to be moved
+ addTarget(R.id.aod_notification_icon_container)
+ addTarget(R.id.status_view_media_container)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
index c35dad7..60ab40c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
@@ -24,12 +24,15 @@
import com.android.app.animation.Interpolators
import com.android.systemui.plugins.clocks.ClockController
-class DefaultClockSteppingTransition(private val clock: ClockController) : Transition() {
+class DefaultClockSteppingTransition(
+ private val clock: ClockController,
+) : Transition() {
init {
interpolator = Interpolators.LINEAR
duration = KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS
addTarget(clock.largeClock.view)
}
+
private fun captureValues(transitionValues: TransitionValues) {
transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
val locationInWindowTmp = IntArray(2)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index d22856b..edd3318 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -23,8 +23,10 @@
class KeyguardBlueprintViewModel
@Inject
-constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
+constructor(
+ keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+) {
var currentBluePrint: KeyguardBlueprint? = null
val blueprint = keyguardBlueprintInteractor.blueprint
- val blueprintWithTransition = keyguardBlueprintInteractor.blueprintWithTransition
+ val refreshTransition = keyguardBlueprintInteractor.refreshTransition
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index f2bd817..37836a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -19,12 +19,18 @@
package com.android.systemui.keyguard.data.repository
+import android.os.fakeExecutorHandler
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.ThreadAssert
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,17 +51,23 @@
private lateinit var underTest: KeyguardBlueprintRepository
@Mock lateinit var configurationRepository: ConfigurationRepository
@Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint
+ @Mock lateinit var threadAssert: ThreadAssert
private val testScope = TestScope(StandardTestDispatcher())
+ private val kosmos: Kosmos = testKosmos()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
- underTest =
- KeyguardBlueprintRepository(
- configurationRepository,
- setOf(defaultLockscreenBlueprint),
- )
+ with(kosmos) {
+ whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
+ underTest =
+ KeyguardBlueprintRepository(
+ configurationRepository,
+ setOf(defaultLockscreenBlueprint),
+ fakeExecutorHandler,
+ threadAssert,
+ )
+ }
}
@Test
@@ -88,13 +100,17 @@
@Test
fun testTransitionToSameBlueprint_refreshesBlueprint() =
- testScope.runTest {
- val refreshBlueprint by collectLastValue(underTest.refreshBluePrint)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ val transition by collectLastValue(underTest.refreshTransition)
+ fakeExecutor.runAllReady()
+ runCurrent()
- underTest.applyBlueprint(defaultLockscreenBlueprint)
- runCurrent()
+ underTest.applyBlueprint(defaultLockscreenBlueprint)
+ fakeExecutor.runAllReady()
+ runCurrent()
- assertThat(refreshBlueprint).isNotNull()
+ assertThat(transition).isNotNull()
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index 8b16da2..9df00d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -23,7 +23,9 @@
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransitionType
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -47,8 +49,7 @@
private lateinit var underTest: KeyguardBlueprintInteractor
private lateinit var testScope: TestScope
- val refreshBluePrint: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1)
- val refreshBlueprintTransition: MutableSharedFlow<IntraBlueprintTransitionType> =
+ val refreshTransition: MutableSharedFlow<IntraBlueprintTransition.Config> =
MutableSharedFlow(extraBufferCapacity = 1)
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
@@ -59,9 +60,7 @@
MockitoAnnotations.initMocks(this)
testScope = TestScope(StandardTestDispatcher())
whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
- whenever(keyguardBlueprintRepository.refreshBluePrint).thenReturn(refreshBluePrint)
- whenever(keyguardBlueprintRepository.refreshBlueprintTransition)
- .thenReturn(refreshBlueprintTransition)
+ whenever(keyguardBlueprintRepository.refreshTransition).thenReturn(refreshTransition)
underTest =
KeyguardBlueprintInteractor(
@@ -116,8 +115,8 @@
@Test
fun testRefreshBlueprintWithTransition() {
- underTest.refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ underTest.refreshBlueprint(Type.DefaultTransition)
verify(keyguardBlueprintRepository)
- .refreshBlueprintWithTransition(IntraBlueprintTransitionType.DefaultTransition)
+ .refreshBlueprint(Config(Type.DefaultTransition, true, true))
}
}
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
index 2da74b0..08d44c1 100644
--- 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
@@ -138,10 +138,10 @@
underTest.applyDefaultConstraints(cs)
val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
- assetLargeClockTop(cs, expectedLargeClockTopMargin)
+ assertLargeClockTop(cs, expectedLargeClockTopMargin)
- val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE - CLOCK_FADE_TRANSLATION_Y
- assetSmallClockTop(cs, expectedSmallClockTopMargin)
+ val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
+ assertSmallClockTop(cs, expectedSmallClockTopMargin)
}
@Test
@@ -152,10 +152,10 @@
underTest.applyDefaultConstraints(cs)
val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
- assetLargeClockTop(cs, expectedLargeClockTopMargin)
+ assertLargeClockTop(cs, expectedLargeClockTopMargin)
- val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE - CLOCK_FADE_TRANSLATION_Y
- assetSmallClockTop(cs, expectedSmallClockTopMargin)
+ val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
+ assertSmallClockTop(cs, expectedSmallClockTopMargin)
}
@Test
@@ -166,10 +166,10 @@
underTest.applyDefaultConstraints(cs)
val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
- assetLargeClockTop(cs, expectedLargeClockTopMargin)
+ assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
- assetSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs, expectedSmallClockTopMargin)
}
@Test
@@ -179,10 +179,10 @@
val cs = ConstraintSet()
underTest.applyDefaultConstraints(cs)
val expectedLargeClockTopMargin = LARGE_CLOCK_TOP
- assetLargeClockTop(cs, expectedLargeClockTopMargin)
+ assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
- assetSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs, expectedSmallClockTopMargin)
}
@Test
@@ -228,16 +228,22 @@
.thenReturn(isInSplitShade)
}
- private fun assetLargeClockTop(cs: ConstraintSet, expectedLargeClockTopMargin: Int) {
+ private fun assertLargeClockTop(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) {
+ private fun assertSmallClockTop(cs: ConstraintSet, expectedSmallClockTopMargin: Int) {
+ val smallClockGuidelineConstraint = cs.getConstraint(R.id.small_clock_guideline_top)
+ assertThat(smallClockGuidelineConstraint.layout.topToTop).isEqualTo(-1)
+ assertThat(smallClockGuidelineConstraint.layout.guideBegin)
+ .isEqualTo(expectedSmallClockTopMargin)
+
val smallClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view)
- assertThat(smallClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
- assertThat(smallClockConstraint.layout.topMargin).isEqualTo(expectedSmallClockTopMargin)
+ assertThat(smallClockConstraint.layout.topToBottom)
+ .isEqualTo(R.id.small_clock_guideline_top)
+ assertThat(smallClockConstraint.layout.topMargin).isEqualTo(0)
}
companion object {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 19cd950..8452963 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -16,17 +16,22 @@
package com.android.systemui.keyguard.data.repository
+import android.os.fakeExecutorHandler
import com.android.systemui.common.ui.data.repository.configurationRepository
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.ThreadAssert
+import com.android.systemui.util.mockito.mock
val Kosmos.keyguardBlueprintRepository by
Kosmos.Fixture {
KeyguardBlueprintRepository(
configurationRepository = configurationRepository,
blueprints = setOf(defaultBlueprint),
+ handler = fakeExecutorHandler,
+ assert = mock<ThreadAssert>(),
)
}