Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 170d7d8f authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Glitchy notifs on clock size change

Notifications weren't animating Y position changes as expected. In order
to synchronize the animation, we tether the intrablueprint transition
state into KeyguardRootViewBinder which listens for placeholder position
changes and forwards them to the notification code. This function now
skips certain layouts during transitions to prevent later layout changes
from conflicting with the running animation. These layouts are erronous
to the transition and suppressed by the transition directly for other
relevant lockscreen elements.

To facilitate two binders relying on the same transition state, I've
moved the transition management logic from the binder to the model, but
otherwise not modified it much. This makes the state available to more
components, allows the binder to be stateless, and lets us convert it
to being a top-level kotlin object like other binders.

I've also corrected an issue where the notif shelf would animate in when
the clock size changed. AodNotificationIconsSection.applyConstraints was
not setting the visibility, so it'd default to VISIBLE for a moment when
it was applied. The transition would pick up this incorrect transient
visibility value and animate the element in.

Bug: 341932557
Test: Manual & Presubmits
Flag: com.android.systemui.migrate_clocks_to_blueprint
Change-Id: Ib447caeb142f55a04f77f34cf8bd7fe6e7e83896
parent 5014643c
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -103,7 +103,6 @@ constructor(
    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
    private val lockscreenContentViewModel: LockscreenContentViewModel,
    private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
    private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
    private val clockInteractor: KeyguardClockInteractor,
    private val keyguardViewMediator: KeyguardViewMediator,
) : CoreStartable {
@@ -150,7 +149,7 @@ constructor(
                cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
                keyguardRootView.addView(composeView)
            } else {
                keyguardBlueprintViewBinder.bind(
                KeyguardBlueprintViewBinder.bind(
                    keyguardRootView,
                    keyguardBlueprintViewModel,
                    keyguardClockViewModel,
@@ -197,12 +196,14 @@ constructor(
            KeyguardRootViewBinder.bind(
                keyguardRootView,
                keyguardRootViewModel,
                keyguardBlueprintViewModel,
                configuration,
                occludingAppDeviceEntryMessageViewModel,
                chipbarCoordinator,
                screenOffAnimationController,
                shadeInteractor,
                clockInteractor,
                keyguardClockViewModel,
                interactionJankMonitor,
                deviceEntryHapticsInteractor,
                vibratorHelper,
+6 −95
Original line number Diff line number Diff line
@@ -17,17 +17,12 @@

package com.android.systemui.keyguard.ui.binder

import android.os.Handler
import android.transition.Transition
import android.transition.TransitionManager
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
@@ -40,47 +35,9 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlin.math.max

@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)
            }

            override fun onTransitionEnd(transition: Transition) {
                if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
                runningTransitions.remove(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)
            }
        }

object KeyguardBlueprintViewBinder {
    @JvmStatic
    fun bind(
        constraintLayout: ConstraintLayout,
        viewModel: KeyguardBlueprintViewModel,
@@ -118,7 +75,7 @@ constructor(
                                    )
                                }

                            runTransition(constraintLayout, transition, config) {
                            viewModel.runTransition(constraintLayout, transition, config) {
                                // Replace sections from the previous blueprint with the new ones
                                blueprint.replaceViews(
                                    constraintLayout,
@@ -146,7 +103,7 @@ constructor(
                    viewModel.refreshTransition.collect { config ->
                        val blueprint = viewModel.blueprint.value

                        runTransition(
                        viewModel.runTransition(
                            constraintLayout,
                            IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel),
                            config,
@@ -167,50 +124,6 @@ constructor(
        }
    }

    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()
        }
    }

    private fun logAlphaVisibilityOfAppliedConstraintSet(
        cs: ConstraintSet,
        viewModel: KeyguardClockViewModel
@@ -237,8 +150,6 @@ constructor(
        )
    }

    companion object {
    private const val TAG = "KeyguardBlueprintViewBinder"
    private const val DEBUG = false
}
}
+33 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.DrawableRes
import android.annotation.SuppressLint
import android.graphics.Point
import android.graphics.Rect
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
@@ -56,8 +57,11 @@ import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.keyguard.ui.viewmodel.TransitionData
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
@@ -93,12 +97,14 @@ object KeyguardRootViewBinder {
    fun bind(
        view: ViewGroup,
        viewModel: KeyguardRootViewModel,
        blueprintViewModel: KeyguardBlueprintViewModel,
        configuration: ConfigurationState,
        occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel?,
        chipbarCoordinator: ChipbarCoordinator?,
        screenOffAnimationController: ScreenOffAnimationController,
        shadeInteractor: ShadeInteractor,
        clockInteractor: KeyguardClockInteractor,
        clockViewModel: KeyguardClockViewModel,
        interactionJankMonitor: InteractionJankMonitor?,
        deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
        vibratorHelper: VibratorHelper?,
@@ -348,7 +354,16 @@ object KeyguardRootViewBinder {
            }
        }

        disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams))
        disposables +=
            view.onLayoutChanged(
                OnLayoutChange(
                    viewModel,
                    blueprintViewModel,
                    clockViewModel,
                    childViews,
                    burnInParams
                )
            )

        // Views will be added or removed after the call to bind(). This is needed to avoid many
        // calls to findViewById
@@ -404,9 +419,13 @@ object KeyguardRootViewBinder {

    private class OnLayoutChange(
        private val viewModel: KeyguardRootViewModel,
        private val blueprintViewModel: KeyguardBlueprintViewModel,
        private val clockViewModel: KeyguardClockViewModel,
        private val childViews: Map<Int, View>,
        private val burnInParams: MutableStateFlow<BurnInParameters>,
    ) : OnLayoutChangeListener {
        var prevTransition: TransitionData? = null

        override fun onLayoutChange(
            view: View,
            left: Int,
@@ -418,11 +437,21 @@ object KeyguardRootViewBinder {
            oldRight: Int,
            oldBottom: Int
        ) {
            childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
            // After layout, ensure the notifications are positioned correctly
            childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
                // Do not update a second time while a blueprint transition is running
                val transition = blueprintViewModel.currentTransition.value
                val shouldAnimate = transition != null && transition.config.type.animateNotifChanges
                if (prevTransition == transition && shouldAnimate) {
                    if (DEBUG) Log.w(TAG, "Skipping; layout during transition")
                    return
                }

                prevTransition = transition
                viewModel.onNotificationContainerBoundsChanged(
                    notificationListPlaceholder.top.toFloat(),
                    notificationListPlaceholder.bottom.toFloat(),
                    animate = shouldAnimate
                )
            }

@@ -585,4 +614,6 @@ object KeyguardRootViewBinder {

    private const val ID = "occluding_app_device_entry_unlock_msg"
    private const val AOD_ICONS_APPEAR_DURATION: Long = 200
    private const val TAG = "KeyguardRootViewBinder"
    private const val DEBUG = false
}
+6 −0
Original line number Diff line number Diff line
@@ -68,7 +68,9 @@ import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
@@ -134,6 +136,7 @@ constructor(
    private val vibratorHelper: VibratorHelper,
    private val indicationController: KeyguardIndicationController,
    private val keyguardRootViewModel: KeyguardRootViewModel,
    private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
    @Assisted bundle: Bundle,
    private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
    private val chipbarCoordinator: ChipbarCoordinator,
@@ -143,6 +146,7 @@ constructor(
    private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
    private val defaultShortcutsSection: DefaultShortcutsSection,
    private val keyguardClockInteractor: KeyguardClockInteractor,
    private val keyguardClockViewModel: KeyguardClockViewModel,
) {
    val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
    private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -379,12 +383,14 @@ constructor(
                KeyguardRootViewBinder.bind(
                    keyguardRootView,
                    keyguardRootViewModel,
                    keyguardBlueprintViewModel,
                    configuration,
                    occludingAppDeviceEntryMessageViewModel,
                    chipbarCoordinator,
                    screenOffAnimationController,
                    shadeInteractor,
                    keyguardClockInteractor,
                    keyguardClockViewModel,
                    null, // jank monitor not required for preview mode
                    null, // device entry haptics not required preview mode
                    null, // device entry haptics not required for preview mode
+7 −7
Original line number Diff line number Diff line
@@ -31,16 +31,16 @@ class IntraBlueprintTransition(

    enum class Type(
        val priority: Int,
        val animateNotifChanges: Boolean,
    ) {
        ClockSize(100),
        ClockCenter(99),
        DefaultClockStepping(98),
        AodNotifIconsTransition(97),
        SmartspaceVisibility(2),
        DefaultTransition(1),
        ClockSize(100, true),
        ClockCenter(99, false),
        DefaultClockStepping(98, false),
        SmartspaceVisibility(2, true),
        DefaultTransition(1, false),
        // When transition between blueprint, we don't need any duration or interpolator but we need
        // all elements go to correct state
        NoTransition(0),
        NoTransition(0, false),
    }

    data class Config(
Loading