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

Commit 5c85e5b6 authored by Arthur Hung's avatar Arthur Hung
Browse files

Migrate back animation to shell transition

When back animation finished, it will invoke the real back callback and
cause a new transition started.

In this CL, we introduce the back transition handler to consume the
incoming transition request and takeover the whole transition if the
transition info contians same departing window token.

This also seperated the behaviors of enabled/disabled shell
transition.

Bug: 238475694
Test: Enabled shell transition, atest BackNavigationControllerTests
      BackAnimationControllerTest
Change-Id: I57e7c89ce6cb7a99ab3af403704b9dd948f26151
parent 197140ab
Loading
Loading
Loading
Loading
+34 −2
Original line number Diff line number Diff line
@@ -89,6 +89,8 @@ public final class BackNavigationInfo implements Parcelable {
    @Nullable
    private final IOnBackInvokedCallback mOnBackInvokedCallback;
    private final boolean mPrepareRemoteAnimation;
    @Nullable
    private WindowContainerToken mDepartingWindowContainerToken;

    /**
     * Create a new {@link BackNavigationInfo} instance.
@@ -97,6 +99,7 @@ public final class BackNavigationInfo implements Parcelable {
     * @param onBackNavigationDone    The callback to be called once the client is done with the
     *                                back preview.
     * @param onBackInvokedCallback   The back callback registered by the current top level window.
     * @param departingWindowContainerToken The {@link WindowContainerToken} of departing window.
     * @param isPrepareRemoteAnimation  Return whether the core is preparing a back gesture
     *                                  animation, if true, the caller of startBackNavigation should
     *                                  be expected to receive an animation start callback.
@@ -104,11 +107,13 @@ public final class BackNavigationInfo implements Parcelable {
    private BackNavigationInfo(@BackTargetType int type,
            @Nullable RemoteCallback onBackNavigationDone,
            @Nullable IOnBackInvokedCallback onBackInvokedCallback,
            boolean isPrepareRemoteAnimation) {
            boolean isPrepareRemoteAnimation,
            @Nullable WindowContainerToken departingWindowContainerToken) {
        mType = type;
        mOnBackNavigationDone = onBackNavigationDone;
        mOnBackInvokedCallback = onBackInvokedCallback;
        mPrepareRemoteAnimation = isPrepareRemoteAnimation;
        mDepartingWindowContainerToken = departingWindowContainerToken;
    }

    private BackNavigationInfo(@NonNull Parcel in) {
@@ -116,6 +121,7 @@ public final class BackNavigationInfo implements Parcelable {
        mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
        mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
        mPrepareRemoteAnimation = in.readBoolean();
        mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR);
    }

    @Override
@@ -124,6 +130,7 @@ public final class BackNavigationInfo implements Parcelable {
        dest.writeTypedObject(mOnBackNavigationDone, flags);
        dest.writeStrongInterface(mOnBackInvokedCallback);
        dest.writeBoolean(mPrepareRemoteAnimation);
        dest.writeTypedObject(mDepartingWindowContainerToken, flags);
    }

    /**
@@ -156,6 +163,18 @@ public final class BackNavigationInfo implements Parcelable {
        return mPrepareRemoteAnimation;
    }

    /**
     * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being
     * removed.
     * <p>
     * For example, if an Activity is the last one of its Task, the Task's token will be given.
     * Otherwise, it will be the Activity's token.
     */
    @Nullable
    public WindowContainerToken getDepartingWindowContainerToken() {
        return mDepartingWindowContainerToken;
    }

    /**
     * Callback to be called when the back preview is finished in order to notify the server that
     * it can clean up the resources created for the animation.
@@ -193,6 +212,7 @@ public final class BackNavigationInfo implements Parcelable {
                + "mType=" + typeToString(mType) + " (" + mType + ")"
                + ", mOnBackNavigationDone=" + mOnBackNavigationDone
                + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
                + ", mWindowContainerToken=" + mDepartingWindowContainerToken
                + '}';
    }

@@ -228,6 +248,9 @@ public final class BackNavigationInfo implements Parcelable {
        @Nullable
        private IOnBackInvokedCallback mOnBackInvokedCallback = null;
        private boolean mPrepareRemoteAnimation;
        @Nullable
        private WindowContainerToken mDepartingWindowContainerToken = null;

        /**
         * @see BackNavigationInfo#getType()
         */
@@ -261,12 +284,21 @@ public final class BackNavigationInfo implements Parcelable {
            return this;
        }

        /**
         * @see BackNavigationInfo#getDepartingWindowContainerToken()
         */
        public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) {
            mDepartingWindowContainerToken = windowContainerToken;
        }

        /**
         * Builds and returns an instance of {@link BackNavigationInfo}
         */
        public BackNavigationInfo build() {
            return new BackNavigationInfo(mType, mOnBackNavigationDone,
                    mOnBackInvokedCallback, mPrepareRemoteAnimation);
                    mOnBackInvokedCallback,
                    mPrepareRemoteAnimation,
                    mDepartingWindowContainerToken);
        }
    }
}
+55 −15
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;

import java.util.concurrent.atomic.AtomicBoolean;

@@ -75,6 +76,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
                    SETTING_VALUE_ON) != SETTING_VALUE_OFF;
    private static final int PROGRESS_THRESHOLD = SystemProperties
            .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);

    // TODO (b/241808055) Find a appropriate time to remove during refactor
    private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
    /**
     * Max duration to wait for a transition to finish before accepting another gesture start
     * request.
@@ -113,6 +117,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    private final TouchTracker mTouchTracker = new TouchTracker();

    private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
    private final Transitions mTransitions;
    private BackTransitionHandler mBackTransitionHandler;

    @VisibleForTesting
    final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@@ -184,9 +190,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
            @NonNull ShellInit shellInit,
            @NonNull @ShellMainThread ShellExecutor shellExecutor,
            @NonNull @ShellBackgroundThread Handler backgroundHandler,
            Context context) {
            Context context,
            Transitions transitions) {
        this(shellInit, shellExecutor, backgroundHandler,
                ActivityTaskManager.getService(), context, context.getContentResolver());
                ActivityTaskManager.getService(), context, context.getContentResolver(),
                transitions);
    }

    @VisibleForTesting
@@ -195,18 +203,24 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
            @NonNull @ShellMainThread ShellExecutor shellExecutor,
            @NonNull @ShellBackgroundThread Handler bgHandler,
            @NonNull IActivityTaskManager activityTaskManager,
            Context context, ContentResolver contentResolver) {
            Context context, ContentResolver contentResolver,
            Transitions transitions) {
        mShellExecutor = shellExecutor;
        mActivityTaskManager = activityTaskManager;
        mContext = context;
        mContentResolver = contentResolver;
        mBgHandler = bgHandler;
        shellInit.addInitCallback(this::onInit, this);
        mTransitions = transitions;
    }

    private void onInit() {
        setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
        createAdapter();
        if (ENABLE_SHELL_TRANSITIONS) {
            mBackTransitionHandler = new BackTransitionHandler(this);
            mTransitions.addHandler(mBackTransitionHandler);
        }
    }

    private void setupAnimationDeveloperSettingsObserver(
@@ -333,7 +347,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
                dispatchOnBackCancelled(callback);
            }
        }
        finishAnimation();

        // In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the
        // following transition when back callback is invoked.
        // If the back callback is not invoked, we should reset the token and finish the whole back
        // navigation without waiting the transition.
        if (!ENABLE_SHELL_TRANSITIONS) {
            finishBackNavigation();
        } else if (!mTriggerBack) {
            // reset the token to prevent it consume next transition.
            mBackTransitionHandler.setDepartingWindowContainerToken(null);
            finishBackNavigation();
        }
    }

    /**
@@ -373,7 +398,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
        if (mBackGestureStarted || mBackNavigationInfo != null) {
            Log.e(TAG, "Animation is being initialized but is already started.");
            finishAnimation();
            finishBackNavigation();
        }

        mTouchTracker.setGestureStartLocation(touchX, touchY);
@@ -385,7 +410,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
            onBackNavigationInfoReceived(mBackNavigationInfo);
        } catch (RemoteException remoteException) {
            Log.e(TAG, "Failed to initAnimation", remoteException);
            finishAnimation();
            finishBackNavigation();
        }
    }

@@ -448,7 +473,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    private void onGestureFinished(boolean fromTouch) {
        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
        if (!mBackGestureStarted) {
            finishAnimation();
            finishBackNavigation();
            return;
        }

@@ -468,7 +493,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
            if (mTriggerBack) {
                injectBackKey();
            }
            finishAnimation();
            finishBackNavigation();
            return;
        }

@@ -492,7 +517,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        }
        if (!shouldDispatchToAnimator) {
            // Animation callback missing. Simply finish animation.
            finishAnimation();
            finishBackNavigation();
        }
    }

@@ -562,8 +587,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
        mTouchTracker.setProgressThreshold(progressThreshold);
    }

    private void finishAnimation() {
        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
    @VisibleForTesting
    void finishBackNavigation() {
        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
        BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
        boolean triggerBack = mTriggerBack;
        mBackNavigationInfo = null;
@@ -590,17 +616,31 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
            return;
        }
        mTransitionInProgress = true;
        if (ENABLE_SHELL_TRANSITIONS) {
            mBackTransitionHandler.setDepartingWindowContainerToken(
                    mBackNavigationInfo.getDepartingWindowContainerToken());
        }
        mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
    }

    private void stopTransition() {
        if (!mTransitionInProgress) {
            return;
        }
    void stopTransition() {
        mShellExecutor.removeCallbacks(mResetTransitionRunnable);
        mTransitionInProgress = false;
    }

    /**
     * This should be called from {@link BackTransitionHandler#startAnimation} when the following
     * transition is triggered by the real back callback in {@link #onBackAnimationFinished}.
     * Will consume the default transition and finish current back navigation.
     */
    void finishTransition(Transitions.TransitionFinishCallback finishCallback) {
        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()");
        mShellExecutor.execute(() -> {
            finishBackNavigation();
            finishCallback.onTransitionFinished(null, null);
        });
    }

    private void createAdapter() {
        IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
            @Override
+78 −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.wm.shell.back;

import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.wm.shell.transition.Transitions;

class BackTransitionHandler implements Transitions.TransitionHandler {
    private BackAnimationController mBackAnimationController;
    private WindowContainerToken mDepartingWindowContainerToken;

    BackTransitionHandler(@NonNull BackAnimationController backAnimationController) {
        mBackAnimationController = backAnimationController;
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        if (mDepartingWindowContainerToken != null) {
            final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken);
            if (change == null) {
                return false;
            }

            startTransaction.hide(change.getLeash());
            startTransaction.apply();
            mDepartingWindowContainerToken = null;
            mBackAnimationController.finishTransition(finishCallback);
            return true;
        }

        return false;
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
            @NonNull TransitionRequestInfo request) {
        return null;
    }

    @Override
    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
    }

    void setDepartingWindowContainerToken(
            @Nullable WindowContainerToken departingWindowContainerToken) {
        mDepartingWindowContainerToken = departingWindowContainerToken;
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -273,12 +273,13 @@ public abstract class WMShellBaseModule {
            Context context,
            ShellInit shellInit,
            @ShellMainThread ShellExecutor shellExecutor,
            @ShellBackgroundThread Handler backgroundHandler
            @ShellBackgroundThread Handler backgroundHandler,
            Transitions transitions
    ) {
        if (BackAnimationController.IS_ENABLED) {
            return Optional.of(
                    new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
                            context));
                            context, transitions));
        }
        return Optional.empty();
    }
+10 −2
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;

import org.junit.Before;
import org.junit.Rule;
@@ -98,6 +99,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
    @Mock
    private IRemoteAnimationRunner mBackAnimationRunner;

    @Mock
    private Transitions mTransitions;

    private BackAnimationController mController;

    private int mEventTime = 0;
@@ -117,7 +121,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
        mController = new BackAnimationController(mShellInit,
                mShellExecutor, new Handler(mTestableLooper.getLooper()),
                mActivityTaskManager, mContext,
                mContentResolver);
                mContentResolver, mTransitions);
        mShellInit.init();
        mEventTime = 0;
        mShellExecutor.flushAll();
@@ -209,7 +213,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
        mController = new BackAnimationController(shellInit,
                mShellExecutor, new Handler(mTestableLooper.getLooper()),
                mActivityTaskManager, mContext,
                mContentResolver);
                mContentResolver, mTransitions);
        shellInit.init();
        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);

@@ -250,6 +254,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
        verifyNoMoreInteractions(mIOnBackInvokedCallback);
        mController.onBackAnimationFinished();
        // Pretend the transition handler called finishAnimation.
        mController.finishBackNavigation();

        // Verify that more events from a rejected swipe cannot start animation.
        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
@@ -278,6 +284,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
        // Simulate transition timeout.
        mShellExecutor.flushAll();
        mController.onBackAnimationFinished();
        // Pretend the transition handler called finishAnimation.
        mController.finishBackNavigation();

        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
Loading