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

Commit 1f16a7b9 authored by Matt Pietal's avatar Matt Pietal
Browse files

[DO NOT MERGE] Coordinated DREAMING->LOCKSCREEN animation

Part 1: Update dream side. Shift to utilize
KeyguardTransitionRepository as the source for all motion between
states. DreamingToLockscreenTransitionViewModel contains all logic
that subdivides the total animation time into flowables that emit
events during only their proper subsection. This means one animator
now runs the view animations across all spaces.

As part of this, add DreamCallbackController as a data source for
KeyguardRepository. The dream activity will signal the transition to
begin via onWakeUp(), which will start the view motions, then run the
unocclude transition, and then finally the lockscreen view motions.

Part 2 coming next... lockscreen tweaks

Test: atest KeyguardRepositoryImplTest
DreamOverlayContainerViewControllerTest
DreamOverlayAnimationsControllerTest
DreamingToLockscreenTransitionViewModelTest DreamOverlayServiceTest
Bug: 260631205

Change-Id: I9bcf877927d1e92e8c751d0b83d2dd70f15912f1
parent ded54efd
Loading
Loading
Loading
Loading
+0 −21
Original line number Diff line number Diff line
@@ -746,27 +746,6 @@
    <!-- How long in milliseconds before full burn-in protection is achieved. -->
    <integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>

    <!-- The duration in milliseconds of the y-translation animation when waking up from
         the dream -->
    <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
    <!-- The delay in milliseconds of the y-translation animation when waking up from
         the dream for the complications at the bottom of the screen -->
    <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
    <!-- The delay in milliseconds of the y-translation animation when waking up from
         the dream for the complications at the top of the screen -->
    <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
    <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
    <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
         complications at the top of the screen -->
    <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
         complications at the bottom of the screen -->
    <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
    <!-- The duration in milliseconds of the blur animation when waking up from
         the dream -->
    <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>

    <integer name="complicationFadeOutMs">500</integer>

    <integer name="complicationFadeInMs">500</integer>
+45 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.dreams

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.CallbackController
import javax.inject.Inject

/** Dream-related callback information */
@SysUISingleton
class DreamCallbackController @Inject constructor() :
    CallbackController<DreamCallbackController.DreamCallback> {

    private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()

    override fun addCallback(callback: DreamCallbackController.DreamCallback) {
        callbacks.add(callback)
    }

    override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
        callbacks.remove(callback)
    }

    fun onWakeUp() {
        callbacks.forEach { it.onWakeUp() }
    }

    interface DreamCallback {
        fun onWakeUp()
    }
}
+84 −80
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import android.animation.ValueAnimator
import android.view.View
import android.view.animation.Interpolator
import androidx.core.animation.doOnEnd
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
@@ -29,10 +32,20 @@ import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITIO
import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_ANIMATION_DURATION
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch

/** Controller for dream overlay animations. */
class DreamOverlayAnimationsController
@@ -43,6 +56,8 @@ constructor(
    private val mStatusBarViewController: DreamOverlayStatusBarViewController,
    private val mOverlayStateController: DreamOverlayStateController,
    @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
    private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
    private val configController: ConfigurationController,
    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
    private val mDreamInBlurAnimDurationMs: Long,
    @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -51,22 +66,10 @@ constructor(
    private val mDreamInTranslationYDistance: Int,
    @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
    private val mDreamInTranslationYDurationMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
    private val mDreamOutTranslationYDistance: Int,
    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
    private val mDreamOutTranslationYDurationMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
    private val mDreamOutTranslationYDelayBottomMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
    private val mDreamOutTranslationYDelayTopMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
    private val mDreamOutAlphaDelayBottomMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
    @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {

    private var mAnimator: Animator? = null
    private lateinit var view: View

    /**
     * Store the current alphas at the various positions. This is so that we may resume an animation
@@ -76,9 +79,63 @@ constructor(

    private var mCurrentBlurRadius: Float = 0f

    fun init(view: View) {
        this.view = view

        view.repeatWhenAttached {
            val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
            val configCallback =
                object : ConfigurationListener {
                    override fun onDensityOrFontScaleChanged() {
                        configurationBasedDimensions.value = loadFromResources(view)
                    }
                }

            configController.addCallback(configCallback)

            repeatOnLifecycle(Lifecycle.State.CREATED) {
                /* Translation animations, when moving from DREAMING->LOCKSCREEN state */
                launch {
                    configurationBasedDimensions
                        .flatMapLatest {
                            transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
                        }
                        .collect { px ->
                            setElementsTranslationYAtPosition(
                                px,
                                ComplicationLayoutParams.POSITION_TOP
                            )
                            setElementsTranslationYAtPosition(
                                px,
                                ComplicationLayoutParams.POSITION_BOTTOM
                            )
                        }
                }

                /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
                launch {
                    transitionViewModel.dreamOverlayAlpha.collect { alpha ->
                        setElementsAlphaAtPosition(
                            alpha = alpha,
                            position = ComplicationLayoutParams.POSITION_TOP,
                            fadingOut = true,
                        )
                        setElementsAlphaAtPosition(
                            alpha = alpha,
                            position = ComplicationLayoutParams.POSITION_BOTTOM,
                            fadingOut = true,
                        )
                    }
                }
            }

            configController.removeCallback(configCallback)
        }
    }

    /** Starts the dream content and dream overlay entry animations. */
    @JvmOverloads
    fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
    fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
        cancelAnimations()

        mAnimator =
@@ -113,73 +170,9 @@ constructor(
    }

    /** Starts the dream content and dream overlay exit animations. */
    @JvmOverloads
    fun startExitAnimations(
        view: View,
        doneCallback: () -> Unit,
        animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
    ) {
    fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
        cancelAnimations()

        mAnimator =
            animatorBuilder().apply {
                playTogether(
                    blurAnimator(
                        view = view,
                        // Start the blurring wherever the entry animation ended, in
                        // case it was cancelled early.
                        fromBlurRadius = mCurrentBlurRadius,
                        toBlurRadius = mDreamBlurRadius.toFloat(),
                        durationMs = mDreamOutBlurDurationMs,
                        interpolator = Interpolators.EMPHASIZED_ACCELERATE
                    ),
                    translationYAnimator(
                        from = 0f,
                        to = mDreamOutTranslationYDistance.toFloat(),
                        durationMs = mDreamOutTranslationYDurationMs,
                        delayMs = mDreamOutTranslationYDelayBottomMs,
                        positions = POSITION_BOTTOM,
                        interpolator = Interpolators.EMPHASIZED_ACCELERATE
                    ),
                    translationYAnimator(
                        from = 0f,
                        to = mDreamOutTranslationYDistance.toFloat(),
                        durationMs = mDreamOutTranslationYDurationMs,
                        delayMs = mDreamOutTranslationYDelayTopMs,
                        positions = POSITION_TOP,
                        interpolator = Interpolators.EMPHASIZED_ACCELERATE
                    ),
                    alphaAnimator(
                        from =
                            mCurrentAlphaAtPosition.getOrDefault(
                                key = POSITION_BOTTOM,
                                defaultValue = 1f
                            ),
                        to = 0f,
                        durationMs = mDreamOutAlphaDurationMs,
                        delayMs = mDreamOutAlphaDelayBottomMs,
                        positions = POSITION_BOTTOM
                    ),
                    alphaAnimator(
                        from =
                            mCurrentAlphaAtPosition.getOrDefault(
                                key = POSITION_TOP,
                                defaultValue = 1f
                            ),
                        to = 0f,
                        durationMs = mDreamOutAlphaDurationMs,
                        delayMs = mDreamOutAlphaDelayTopMs,
                        positions = POSITION_TOP
                    )
                )
                doOnEnd {
                    mAnimator = null
                    mOverlayStateController.setExitAnimationsRunning(false)
                    doneCallback()
                }
                start()
            }
        mOverlayStateController.setExitAnimationsRunning(true)
        executor.executeDelayed(doneCallback, DREAM_ANIMATION_DURATION.inWholeMilliseconds)
    }

    /** Cancels the dream content and dream overlay animations, if they're currently running. */
@@ -288,4 +281,15 @@ constructor(
            mStatusBarViewController.setTranslationY(translationY)
        }
    }

    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
        return ConfigurationBasedDimensions(
            translationYPx =
                view.resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset),
        )
    }

    private data class ConfigurationBasedDimensions(
        val translationYPx: Int,
    )
}
+6 −7
Original line number Diff line number Diff line
@@ -42,9 +42,9 @@ import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;

import java.util.Arrays;
import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Named;
@@ -170,6 +170,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
    protected void onInit() {
        mStatusBarViewController.init();
        mComplicationHostViewController.init();
        mDreamOverlayAnimationsController.init(mView);
    }

    @Override
@@ -184,7 +185,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve

        // Start dream entry animations. Skip animations for low light clock.
        if (!mStateController.isLowLightActive()) {
            mDreamOverlayAnimationsController.startEntryAnimations(mView);
            mDreamOverlayAnimationsController.startEntryAnimations();
        }
    }

@@ -261,10 +262,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
     * @param onAnimationEnd Callback to trigger once animations are finished.
     * @param callbackExecutor Executor to execute the callback on.
     */
    public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
        mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
            callbackExecutor.execute(onAnimationEnd);
            return null;
        });
    public void wakeUp(@NonNull Runnable onAnimationEnd,
            @NonNull DelayableExecutor callbackExecutor) {
        mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
    }
}
+8 −4
Original line number Diff line number Diff line
@@ -46,10 +46,10 @@ import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;

import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Named;
@@ -66,10 +66,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
    // The Context is used to construct the hosting constraint layout and child overlay views.
    private final Context mContext;
    // The Executor ensures actions and ui updates happen on the same thread.
    private final Executor mExecutor;
    private final DelayableExecutor mExecutor;
    // A controller for the dream overlay container view (which contains both the status bar and the
    // content area).
    private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
    private final DreamCallbackController mDreamCallbackController;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    @Nullable
    private final ComponentName mLowLightDreamComponent;
@@ -137,8 +138,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
    @Inject
    public DreamOverlayService(
            Context context,
            @Main Executor executor,
            DreamOverlayLifecycleOwner lifecycleOwner,
            @Main DelayableExecutor executor,
            WindowManager windowManager,
            ComplicationComponent.Factory complicationComponentFactory,
            com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
@@ -149,7 +150,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
            UiEventLogger uiEventLogger,
            TouchInsetManager touchInsetManager,
            @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
                    ComponentName lowLightDreamComponent) {
                    ComponentName lowLightDreamComponent,
            DreamCallbackController dreamCallbackController) {
        mContext = context;
        mExecutor = executor;
        mWindowManager = windowManager;
@@ -158,6 +160,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
        mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
        mStateController = stateController;
        mUiEventLogger = uiEventLogger;
        mDreamCallbackController = dreamCallbackController;

        final ViewModelStore viewModelStore = new ViewModelStore();
        final Complication.Host host =
@@ -242,6 +245,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
    public void onWakeUp(@NonNull Runnable onCompletedCallback) {
        mExecutor.execute(() -> {
            if (mDreamOverlayContainerViewController != null) {
                mDreamCallbackController.onWakeUp();
                mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
            }
        });
Loading