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

Commit 0e98504c authored by Luca Zuccarini's avatar Luca Zuccarini
Browse files

3.7 Update Keyguard to use the modern Animation Library APIs.

For simplicity and scope, this CL only changes the affected
animation, while the rest that don't go through the Animation
Library remain tied to the old RemoteAnimationRunner APIs.

Trying to make each of the CLs as small as possible to keep them
digestible and low risk. For the refactor plan see
go/animlib-shell-refactor-plan.

Bug: 397180418
Flag: com.android.systemui.animation_library_shell_migration
Test: atest KeyguardViewMediatorTest KeyguardViewMediatorTestKt + manual
Change-Id: Ic4ce3b4229eeb9f3e5717e5baebc8a4e4aea4381
parent ce778872
Loading
Loading
Loading
Loading
+108 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.keyguard

import android.app.WindowConfiguration
import android.os.IBinder
import android.service.dreams.Flags
import android.util.Log
import android.util.RotationUtils
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.IRemoteTransitionFinishedCallback
import android.window.TransitionInfo
import com.android.systemui.animation.RemoteTransitionHelper
import com.android.wm.shell.shared.CounterRotator

/**
 * RemoteTransitionHelper that initializes changes with particular attention to Keyguard
 * requirements.
 */
class KeyguardTransitionHelper : RemoteTransitionHelper {
    companion object {
        private const val TAG = "DefaultTransitionHelper"
    }

    private val wallpaperRotators = mutableMapOf<IBinder, CounterRotator>()

    override fun setUpAnimation(
        token: IBinder,
        info: TransitionInfo,
        transaction: SurfaceControl.Transaction,
        finishCallback: IRemoteTransitionFinishedCallback?,
    ) {
        info.changes.forEach { change ->
            // Make sure the wallpaper is rotated correctly.
            if ((change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                val rotationDelta =
                    RotationUtils.deltaRotation(change.startRotation, change.endRotation)
                val wallpaperParent = change?.parent
                if (
                    wallpaperParent != null &&
                        rotationDelta != 0 &&
                        change.mode == WindowManager.TRANSIT_TO_BACK
                ) {
                    val parent = info.getChange(wallpaperParent)
                    if (parent != null) {
                        val rotator = CounterRotator()
                        rotator.setup(
                            transaction,
                            parent.leash,
                            rotationDelta,
                            parent.endAbsBounds.width().toFloat(),
                            parent.endAbsBounds.height().toFloat(),
                        )
                        transaction.setLayer(rotator.surface, -1)
                        rotator.addChild(transaction, change.leash)
                        wallpaperRotators[token] = rotator
                    } else {
                        Log.e(
                            TAG,
                            "Malformed: $change has parent=$wallpaperParent, which is not part " +
                                "of the transition info=$info.",
                        )
                    }
                }
            }

            if (RemoteTransitionHelper.OPENING_MODES.contains(change.mode)) {
                transaction.setAlpha(change.leash, 0f)
            } else if (TransitionInfo.isIndependent(change, info)) {
                // Set alpha back to 1 for the independent changes because we will be animating
                // children instead.
                transaction.setAlpha(change.leash, 1f)
            }

            // If the keyguard is going away, hide the dream if one exists.
            if (
                Flags.dismissDreamOnKeyguardDismiss() &&
                    (change.flags and WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0 &&
                    change.taskInfo?.activityType == WindowConfiguration.ACTIVITY_TYPE_DREAM &&
                    RemoteTransitionHelper.CLOSING_MODES.contains(change.mode)
            ) {
                transaction.hide(change.leash)
            }
        }

        transaction.apply()
    }

    override fun cleanUpAnimation(token: IBinder, transaction: SurfaceControl.Transaction) {
        wallpaperRotators.remove(token)?.cleanUp(transaction)
        transaction.apply()
    }
}
+111 −8
Original line number Diff line number Diff line
@@ -101,6 +101,10 @@ import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.RemoteTransitionStub;
import android.window.TransitionInfo;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -138,7 +142,7 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
@@ -183,7 +187,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitions;

import dagger.Lazy;

import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.CoroutineScope;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -1147,8 +1151,12 @@ public class KeyguardViewMediator implements CoreStartable,
                                (int) (fullWidth - initialWidth) /* left */,
                                fullWidth /* right */,
                                mWindowCornerRadius, mWindowCornerRadius);
                    } else if (mOccludingRemoteAnimationTarget != null
                            && mOccludingRemoteAnimationTarget.isTranslucent) {
                    } else if ((ActivityTransitionAnimator.Companion.shellMigrationEnabled()
                            && mIsOccludingWithTranslucentTask)
                            || (!ActivityTransitionAnimator.Companion.shellMigrationEnabled()
                            && mOccludingRemoteAnimationTarget != null
                            && mOccludingRemoteAnimationTarget.isTranslucent)) {
                        mIsOccludingWithTranslucentTask = false;
                        // Animating in a transparent window looks really weird. Just let it be
                        // fullscreen and the app can do an internal animation if it wants to.
                        return new TransitionAnimator.State(
@@ -1226,6 +1234,9 @@ public class KeyguardViewMediator implements CoreStartable,
        }
    };

    private final IRemoteTransition mOccludeTransition =
            new OccludeActivityLaunchRemoteTransition(mOccludeAnimationController);

    private final IRemoteAnimationRunner mOccludeAnimationRunner =
            new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController);

@@ -1532,7 +1543,7 @@ public class KeyguardViewMediator implements CoreStartable,

    private final UiEventLogger mUiEventLogger;
    private final SessionTracker mSessionTracker;
    private final CoroutineDispatcher mMainDispatcher;
    private final CoroutineScope mApplicationScope;
    private final Lazy<DreamViewModel> mDreamViewModel;
    private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
    private RemoteAnimationTarget mRemoteAnimationTarget;
@@ -1541,6 +1552,8 @@ public class KeyguardViewMediator implements CoreStartable,
     * The most recent RemoteAnimationTarget provided for an occluding activity animation.
     */
    private RemoteAnimationTarget mOccludingRemoteAnimationTarget;
    /** Whether we're currently animating an occlusion with a translucent task. */
    private boolean mIsOccludingWithTranslucentTask = false;
    private boolean mShowCommunalWhenUnoccluding = false;
    /**
     * Either transitioning to dreaming, from dreaming, or currently in the dreaming state. If the
@@ -1594,7 +1607,7 @@ public class KeyguardViewMediator implements CoreStartable,
            SystemSettings systemSettings,
            SystemClock systemClock,
            ProcessWrapper processWrapper,
            @Main CoroutineDispatcher mainDispatcher,
            @Application CoroutineScope applicationScope,
            Lazy<DreamViewModel> dreamViewModel,
            Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
            SystemPropertiesHelper systemPropertiesHelper,
@@ -1676,7 +1689,7 @@ public class KeyguardViewMediator implements CoreStartable,
        mDreamViewModel = dreamViewModel;
        mCommunalTransitionViewModel = communalTransitionViewModel;
        mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
        mMainDispatcher = mainDispatcher;
        mApplicationScope = applicationScope;

        mOrderUnlockAndWake = context.getResources().getBoolean(
                com.android.internal.R.bool.config_orderUnlockAndWake);
@@ -1722,7 +1735,9 @@ public class KeyguardViewMediator implements CoreStartable,
        mKeyguardTransitions.register(
                KeyguardService.wrap(this, getExitAnimationRunner()),
                KeyguardService.wrap(this, getAppearAnimationRunner()),
                KeyguardService.wrap(this, getOccludeAnimationRunner()),
                ActivityTransitionAnimator.Companion.shellMigrationEnabled()
                        ? getOccludeTransition()
                        : KeyguardService.wrap(this, getOccludeAnimationRunner()),
                KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()),
                KeyguardService.wrap(this, getUnoccludeAnimationRunner()));

@@ -2322,6 +2337,14 @@ public class KeyguardViewMediator implements CoreStartable,
        return validatingRemoteAnimationRunner(mAppearAnimationRunner);
    }

    public IRemoteTransition getOccludeTransition() {
        if (KeyguardWmStateRefactor.isEnabled()) {
            return validatingRemoteTransition(mWmOcclusionManager.getOccludeTransition());
        } else {
            return validatingRemoteTransition(mOccludeTransition);
        }
    }

    public IRemoteAnimationRunner getOccludeAnimationRunner() {
        if (KeyguardWmStateRefactor.isEnabled()) {
            return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
@@ -4312,6 +4335,86 @@ public class KeyguardViewMediator implements CoreStartable,
        mKeyguardTransitions.setLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen);
    }

    /**
     * Implementation of {@link IRemoteTransition} that wraps the default Animation Library launch
     * transition and calls {@link #setOccluded} when startAnimation is called.
     */
    private class OccludeActivityLaunchRemoteTransition extends RemoteTransitionStub {
        private final ActivityTransitionAnimator.Controller mController;
        private IRemoteTransition mDelegate;

        OccludeActivityLaunchRemoteTransition(ActivityTransitionAnimator.Controller controller) {
            mController = controller;
        }

        @Override
        public void startAnimation(IBinder token, TransitionInfo info, Transaction t,
                IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
            for (TransitionInfo.Change change : info.getChanges()) {
                // This flag needs to be set before mDelegate.startAnimation(),  since that's the
                // call that eventually asks the animation controller to configure the animation
                // state.
                mIsOccludingWithTranslucentTask =
                        (change.getMode() == WindowManager.TRANSIT_OPEN
                                || change.getMode() == WindowManager.TRANSIT_TO_FRONT)
                                && (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0;
                if (mIsOccludingWithTranslucentTask) {
                    break;
                }
            }

            mDelegate = mActivityTransitionAnimator.get().createOriginTransition(
                    mController, mApplicationScope, mController.isDialogLaunch(),
                    new KeyguardTransitionHelper());
            mDelegate.startAnimation(token, info, t, finishCallback);

            mInteractionJankMonitor.begin(
                    createInteractionJankMonitorConf(CUJ_LOCKSCREEN_OCCLUSION)
                            .setTag("OCCLUDE"));

            // This is the first signal we have from WM that we're going to be occluded. Set our
            // internal state to reflect that immediately, vs. waiting for the launch animator to
            // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
            // be occluded and might re-show the keyguard.
            Log.d(TAG, "OccludeAnimator#onAnimationStart. Set occluded = true.");
            setOccluded(true /* isOccluded */, false /* animate */);
        }

        @Override
        public void mergeAnimation(IBinder transition, TransitionInfo info, Transaction t,
                IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback)
                throws RemoteException {
            Log.d(TAG, "Occlude animation cancelled by WM (mergeAnimation).");
            mDelegate.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
            mIsOccludingWithTranslucentTask = false;
            mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
        }

        @Override
        public void onTransitionConsumed(IBinder transition, boolean aborted)
                throws RemoteException {
            Log.d(TAG, "Occlude animation cancelled by WM (onTransitionConsumed).");
            mDelegate.onTransitionConsumed(transition, aborted);
            mIsOccludingWithTranslucentTask = false;
            mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
        }
    }

    private IRemoteTransition validatingRemoteTransition(IRemoteTransition delegate) {
        return new RemoteTransitionStub() {
            @Override
            public void startAnimation(IBinder token, TransitionInfo info, Transaction t,
                    IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
                if (!isViewRootReady()) {
                    Log.w(TAG, "Skipping remote transition - view root not ready");
                    return;
                }

                delegate.startAnimation(token, info, t, finishCallback);
            }
        };
    }

    /**
     * Implementation of RemoteAnimationRunner that creates a new
     * {@link ActivityTransitionAnimator.Runner} whenever onAnimationStart is called, delegating the
+71 −0
Original line number Diff line number Diff line
@@ -21,16 +21,24 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Matrix
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
import android.view.WindowManager.TRANSIT_OPEN
import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransitionStub
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
@@ -39,6 +47,7 @@ import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.TransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -49,6 +58,7 @@ import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope

private val UNOCCLUDE_ANIMATION_DURATION = 250
private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f
@@ -87,6 +97,7 @@ constructor(
    @ShadeDisplayAware val context: Context,
    val interactionJankMonitor: InteractionJankMonitor,
    @Main executor: Executor,
    @Application val applicationScope: CoroutineScope,
    val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
    val occlusionInteractor: KeyguardOcclusionInteractor,
) {
@@ -96,8 +107,68 @@ constructor(
        )
    val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)

    var occludeTransitionFinishedCallback: IRemoteTransitionFinishedCallback? = null

    var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null

    /**
     * Remote transition provided to Shell, which will be used if an occluding activity is launched
     * and Shell wants us to animate it in. This is used as a signal that we are now occluded, and
     * should update our state accordingly.
     */
    val occludeTransition: IRemoteTransition =
        object : RemoteTransitionStub() {
            var delegate: IRemoteTransition? = null

            override fun startAnimation(
                token: IBinder?,
                info: TransitionInfo?,
                t: SurfaceControl.Transaction?,
                finishCallback: IRemoteTransitionFinishedCallback?,
            ) {
                Log.d(TAG, "occludeTransition#startAnimation")
                // Wrap the callback so that it's guaranteed to be nulled out once called.
                occludeTransitionFinishedCallback =
                    object : IRemoteTransitionFinishedCallback.Stub() {
                        override fun onTransitionFinished(
                            wct: WindowContainerTransaction?,
                            sct: SurfaceControl.Transaction?,
                        ) {
                            finishCallback?.onTransitionFinished(wct, sct)
                            occludeTransitionFinishedCallback = null
                        }
                    }
                keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
                    showWhenLockedActivityOnTop = true,
                    taskInfo = info?.changes?.firstOrNull { it.mode == TRANSIT_OPEN }?.taskInfo,
                )
                delegate =
                    activityTransitionAnimator.createOriginTransition(
                        occludeAnimationController,
                        applicationScope,
                        isDialogLaunch = false,
                        transitionHelper = KeyguardTransitionHelper(),
                    )
                delegate?.startAnimation(token, info, t, finishCallback)
            }

            override fun mergeAnimation(
                transition: IBinder?,
                info: TransitionInfo?,
                t: SurfaceControl.Transaction?,
                mergeTarget: IBinder?,
                finishCallback: IRemoteTransitionFinishedCallback?,
            ) {
                Log.d(TAG, "occludeTransition#mergeAnimation")
                delegate?.mergeAnimation(transition, info, t, mergeTarget, finishCallback)
            }

            override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
                Log.d(TAG, "occludeTransition#onTransitionConsumed")
                delegate?.onTransitionConsumed(transition, aborted)
            }
        }

    /**
     * Animation runner provided to WindowManager, which will be used if an occluding activity is
     * launched and Window Manager wants us to animate it in. This is used as a signal that we are
+4 −3
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
@@ -101,7 +102,7 @@ import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;

import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.CoroutineScope;

import java.util.concurrent.Executor;

@@ -177,7 +178,7 @@ public interface KeyguardModule {
            SystemSettings systemSettings,
            SystemClock systemClock,
            ProcessWrapper processWrapper,
            @Main CoroutineDispatcher mainDispatcher,
            @Application CoroutineScope applicationScope,
            Lazy<DreamViewModel> dreamViewModel,
            Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
            SystemPropertiesHelper systemPropertiesHelper,
@@ -230,7 +231,7 @@ public interface KeyguardModule {
                systemSettings,
                systemClock,
                processWrapper,
                mainDispatcher,
                applicationScope,
                dreamViewModel,
                communalTransitionViewModel,
                systemPropertiesHelper,
+3 −3
Original line number Diff line number Diff line
@@ -136,7 +136,7 @@ import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
import com.android.window.flags.Flags;
import com.android.wm.shell.keyguard.KeyguardTransitions;

import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.test.TestScope;

@@ -227,7 +227,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    /** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */
    private boolean mKeyguardGoingAway = false;

    private @Mock CoroutineDispatcher mDispatcher;
    private @Mock CoroutineScope mApplicationScope;
    private @Mock DreamViewModel mDreamViewModel;
    private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel;
    private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
@@ -1550,7 +1550,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
                mSystemSettings,
                mSystemClock,
                mProcessWrapper,
                mDispatcher,
                mApplicationScope,
                () -> mDreamViewModel,
                () -> mCommunalTransitionViewModel,
                mSystemPropertiesHelper,
Loading