1882 lines
80 KiB
Java
1882 lines
80 KiB
Java
/*
|
|
* Copyright (C) 2021 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 static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
|
import static android.view.Display.INVALID_DISPLAY;
|
|
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
|
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
|
import static android.view.WindowManager.TRANSIT_CHANGE;
|
|
import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
|
|
import static android.window.BackEvent.EDGE_NONE;
|
|
import static android.window.DesktopExperienceFlags.ENABLE_INDEPENDENT_BACK_IN_PROJECTED;
|
|
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
|
|
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
|
|
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
|
|
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
|
|
|
|
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
|
|
import static com.android.systemui.Flags.predictiveBackDelayWmTransition;
|
|
import static com.android.window.flags2.Flags.unifyBackNavigationTransition;
|
|
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.SuppressLint;
|
|
import android.app.ActivityManager;
|
|
import android.app.ActivityTaskManager;
|
|
import android.app.IActivityTaskManager;
|
|
import android.app.TaskInfo;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.hardware.input.InputManager;
|
|
import android.hardware.input.KeyGestureEvent;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteCallback;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
import android.view.IRemoteAnimationRunner;
|
|
import android.view.InputDevice;
|
|
import android.view.KeyCharacterMap;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.RemoteAnimationTarget;
|
|
import android.view.SurfaceControl;
|
|
import android.view.WindowManager;
|
|
import android.window.BackAnimationAdapter;
|
|
import android.window.BackEvent;
|
|
import android.window.BackMotionEvent;
|
|
import android.window.BackNavigationInfo;
|
|
import android.window.BackTouchTracker;
|
|
import android.window.IBackAnimationFinishedCallback;
|
|
import android.window.IBackAnimationHandoffHandler;
|
|
import android.window.IBackAnimationRunner;
|
|
import android.window.IOnBackInvokedCallback;
|
|
import android.window.TransitionInfo;
|
|
import android.window.TransitionRequestInfo;
|
|
import android.window.WindowAnimationState;
|
|
import android.window.WindowContainerToken;
|
|
import android.window.WindowContainerTransaction;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.internal.protolog.ProtoLog;
|
|
import com.android.internal.util.LatencyTracker;
|
|
import com.android.internal.view.AppearanceRegion;
|
|
import com.android.systemui.animation.TransitionAnimator;
|
|
import com.android.window.flags2.Flags;
|
|
import com.android.wm.shell.R;
|
|
import com.android.wm.shell.common.ExternalInterfaceBinder;
|
|
import com.android.wm.shell.common.RemoteCallable;
|
|
import com.android.wm.shell.common.ShellExecutor;
|
|
import com.android.wm.shell.shared.TransitionUtil;
|
|
import com.android.wm.shell.shared.annotations.ShellMainThread;
|
|
import com.android.wm.shell.sysui.ConfigurationChangeListener;
|
|
import com.android.wm.shell.sysui.ShellCommandHandler;
|
|
import com.android.wm.shell.sysui.ShellController;
|
|
import com.android.wm.shell.sysui.ShellInit;
|
|
import com.android.wm.shell.transition.Transitions;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.function.Predicate;
|
|
|
|
/**
|
|
* Controls the window animation run when a user initiates a back gesture.
|
|
*/
|
|
public class BackAnimationController implements RemoteCallable<BackAnimationController>,
|
|
ConfigurationChangeListener {
|
|
private static final String TAG = "ShellBackPreview";
|
|
|
|
/**
|
|
* Max duration to wait for an animation to finish before triggering the real back.
|
|
*/
|
|
private static final long MAX_ANIMATION_DURATION = 2000;
|
|
private final LatencyTracker mLatencyTracker;
|
|
@ShellMainThread private final Handler mHandler;
|
|
|
|
/** True when a back gesture is ongoing */
|
|
@VisibleForTesting public boolean mBackGestureStarted = false;
|
|
|
|
/** Tracks if an uninterruptible animation is in progress */
|
|
private boolean mPostCommitAnimationInProgress = false;
|
|
private boolean mRealCallbackInvoked = false;
|
|
|
|
/** Tracks if we should start the back gesture on the next motion move event */
|
|
private boolean mShouldStartOnNextMoveEvent = false;
|
|
private boolean mOnBackStartDispatched = false;
|
|
private boolean mThresholdCrossed = false;
|
|
private boolean mPointersPilfered = false;
|
|
private final boolean mRequirePointerPilfer;
|
|
|
|
/** Registry for the back animations */
|
|
private final ShellBackAnimationRegistry mShellBackAnimationRegistry;
|
|
|
|
@Nullable
|
|
private BackNavigationInfo mBackNavigationInfo;
|
|
private boolean mReceivedNullNavigationInfo = false;
|
|
private final IActivityTaskManager mActivityTaskManager;
|
|
private final Context mContext;
|
|
private final ShellController mShellController;
|
|
private final ShellCommandHandler mShellCommandHandler;
|
|
private final ShellExecutor mShellExecutor;
|
|
private final WindowManager mWindowManager;
|
|
private final Transitions mTransitions;
|
|
private final InputManager mInputManager;
|
|
@VisibleForTesting
|
|
final BackTransitionHandler mBackTransitionHandler;
|
|
@VisibleForTesting
|
|
final Rect mTouchableArea = new Rect();
|
|
|
|
/**
|
|
* Tracks the current user back gesture.
|
|
*/
|
|
private BackTouchTracker mCurrentTracker = new BackTouchTracker();
|
|
|
|
/**
|
|
* Tracks the next back gesture in case a new user gesture has started while the back animation
|
|
* (and navigation) associated with {@link #mCurrentTracker} have not yet finished.
|
|
*/
|
|
private BackTouchTracker mQueuedTracker = new BackTouchTracker();
|
|
|
|
private final BackTransitionObserver mBackTransitionObserver =
|
|
new BackTransitionObserver();
|
|
|
|
private final Runnable mAnimationTimeoutRunnable = () -> {
|
|
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
|
|
MAX_ANIMATION_DURATION);
|
|
finishBackAnimation();
|
|
};
|
|
|
|
private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
|
|
@VisibleForTesting
|
|
BackAnimationAdapter mBackAnimationAdapter;
|
|
|
|
@Nullable
|
|
private IOnBackInvokedCallback mActiveCallback;
|
|
@Nullable
|
|
@VisibleForTesting
|
|
RemoteAnimationTarget[] mApps;
|
|
|
|
@VisibleForTesting
|
|
final RemoteCallback mNavigationObserver = new RemoteCallback(
|
|
new RemoteCallback.OnResultListener() {
|
|
@Override
|
|
public void onResult(@Nullable Bundle result) {
|
|
mShellExecutor.execute(() -> {
|
|
if (mBackGestureStarted && result != null && result.getBoolean(
|
|
BackNavigationInfo.KEY_TOUCH_GESTURE_TRANSFERRED)) {
|
|
// Host app won't able to process motion event anymore, so pilfer
|
|
// pointers anyway.
|
|
if (mBackNavigationInfo != null) {
|
|
mBackNavigationInfo.disableAppProgressGenerationAllowed();
|
|
}
|
|
tryPilferPointers();
|
|
return;
|
|
}
|
|
if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
|
|
// If an uninterruptible animation is already in progress, we should
|
|
// ignore this due to it may cause focus lost. (alpha = 0)
|
|
return;
|
|
}
|
|
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
|
|
setTriggerBack(false);
|
|
// Trigger close transition if necessary.
|
|
mBackTransitionHandler.onAnimationFinished();
|
|
resetTouchTracker();
|
|
// Don't wait for animation start
|
|
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
|
|
});
|
|
}
|
|
});
|
|
|
|
private final BackAnimationBackground mAnimationBackground;
|
|
private StatusBarCustomizer mCustomizer;
|
|
private boolean mTrackingLatency;
|
|
|
|
// Keep previous navigation type before remove mBackNavigationInfo.
|
|
@BackNavigationInfo.BackTargetType
|
|
private int mPreviousNavigationType;
|
|
private Runnable mPilferPointerCallback;
|
|
private BackAnimation.TopUiRequest mRequestTopUiCallback;
|
|
|
|
private final IBackAnimationHandoffHandler mHandoffHandler =
|
|
new IBackAnimationHandoffHandler.Stub() {
|
|
@Override
|
|
public void handOffAnimation(
|
|
RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
|
|
mBackTransitionHandler.handOffAnimation(targets, states);
|
|
}
|
|
};
|
|
|
|
public BackAnimationController(
|
|
@NonNull ShellInit shellInit,
|
|
@NonNull ShellController shellController,
|
|
@NonNull @ShellMainThread ShellExecutor shellExecutor,
|
|
Context context,
|
|
@NonNull BackAnimationBackground backAnimationBackground,
|
|
ShellBackAnimationRegistry shellBackAnimationRegistry,
|
|
ShellCommandHandler shellCommandHandler,
|
|
Transitions transitions,
|
|
@ShellMainThread Handler handler) {
|
|
this(
|
|
shellInit,
|
|
shellController,
|
|
shellExecutor,
|
|
ActivityTaskManager.getService(),
|
|
context,
|
|
backAnimationBackground,
|
|
shellBackAnimationRegistry,
|
|
shellCommandHandler,
|
|
transitions,
|
|
handler);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
BackAnimationController(
|
|
@NonNull ShellInit shellInit,
|
|
@NonNull ShellController shellController,
|
|
@NonNull @ShellMainThread ShellExecutor shellExecutor,
|
|
@NonNull IActivityTaskManager activityTaskManager,
|
|
Context context,
|
|
@NonNull BackAnimationBackground backAnimationBackground,
|
|
ShellBackAnimationRegistry shellBackAnimationRegistry,
|
|
ShellCommandHandler shellCommandHandler,
|
|
Transitions transitions,
|
|
@NonNull @ShellMainThread Handler handler) {
|
|
mShellController = shellController;
|
|
mShellExecutor = shellExecutor;
|
|
mActivityTaskManager = activityTaskManager;
|
|
mContext = context;
|
|
mRequirePointerPilfer =
|
|
context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
|
|
shellInit.addInitCallback(this::onInit, this);
|
|
mAnimationBackground = backAnimationBackground;
|
|
mShellBackAnimationRegistry = shellBackAnimationRegistry;
|
|
mLatencyTracker = LatencyTracker.getInstance(mContext);
|
|
mShellCommandHandler = shellCommandHandler;
|
|
mWindowManager = context.getSystemService(WindowManager.class);
|
|
mInputManager = context.getSystemService(InputManager.class);
|
|
mTransitions = transitions;
|
|
mBackTransitionHandler = new BackTransitionHandler(mTransitions);
|
|
mTransitions.addHandler(mBackTransitionHandler);
|
|
mHandler = handler;
|
|
mTransitions.registerObserver(mBackTransitionObserver);
|
|
mBackTransitionObserver.setBackTransitionHandler(mBackTransitionHandler);
|
|
updateTouchableArea();
|
|
}
|
|
|
|
private void onInit() {
|
|
createAdapter();
|
|
mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR,
|
|
this::createExternalInterface, this);
|
|
mShellCommandHandler.addDumpCallback(this::dump, this);
|
|
mShellController.addConfigurationChangeListener(this);
|
|
registerBackGestureDelegate();
|
|
}
|
|
|
|
public BackAnimation getBackAnimationImpl() {
|
|
return mBackAnimation;
|
|
}
|
|
|
|
private ExternalInterfaceBinder createExternalInterface() {
|
|
return new IBackAnimationImpl(this);
|
|
}
|
|
|
|
private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
|
|
|
|
@Override
|
|
public void onConfigurationChanged(Configuration newConfig) {
|
|
mShellBackAnimationRegistry.onConfigurationChanged(newConfig);
|
|
updateTouchableArea();
|
|
}
|
|
|
|
private void updateTouchableArea() {
|
|
mTouchableArea.set(mWindowManager.getCurrentWindowMetrics().getBounds());
|
|
}
|
|
|
|
@Override
|
|
public Context getContext() {
|
|
return mContext;
|
|
}
|
|
|
|
@Override
|
|
public ShellExecutor getRemoteCallExecutor() {
|
|
return mShellExecutor;
|
|
}
|
|
|
|
private class BackAnimationImpl implements BackAnimation {
|
|
@Override
|
|
public void onBackMotion(
|
|
float touchX,
|
|
float touchY,
|
|
int keyAction,
|
|
@BackEvent.SwipeEdge int swipeEdge,
|
|
int displayId
|
|
) {
|
|
mShellExecutor.execute(
|
|
() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge, displayId));
|
|
}
|
|
|
|
@Override
|
|
public void onThresholdCrossed() {
|
|
if (predictiveBackDelayWmTransition()) {
|
|
mShellExecutor.execute(BackAnimationController.this::onThresholdCrossed);
|
|
} else {
|
|
BackAnimationController.this.onThresholdCrossed();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setTriggerBack(boolean triggerBack) {
|
|
mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack));
|
|
}
|
|
|
|
@Override
|
|
public void setSwipeThresholds(
|
|
float linearDistance,
|
|
float maxDistance,
|
|
float nonLinearFactor) {
|
|
mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds(
|
|
linearDistance, maxDistance, nonLinearFactor));
|
|
}
|
|
|
|
@Override
|
|
public void setStatusBarCustomizer(StatusBarCustomizer customizer) {
|
|
mCustomizer = customizer;
|
|
mAnimationBackground.setStatusBarCustomizer(customizer);
|
|
}
|
|
|
|
@Override
|
|
public void setPilferPointerCallback(Runnable callback) {
|
|
mShellExecutor.execute(() -> {
|
|
mPilferPointerCallback = callback;
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void setTopUiRequestCallback(TopUiRequest topUiRequest) {
|
|
mShellExecutor.execute(() -> mRequestTopUiCallback = topUiRequest);
|
|
}
|
|
}
|
|
|
|
private class IBackAnimationImpl extends IBackAnimation.Stub
|
|
implements ExternalInterfaceBinder {
|
|
private BackAnimationController mController;
|
|
|
|
IBackAnimationImpl(BackAnimationController controller) {
|
|
mController = controller;
|
|
}
|
|
|
|
@Override
|
|
public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
|
|
IRemoteAnimationRunner runner) {
|
|
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
|
|
(controller) -> controller.registerAnimation(
|
|
BackNavigationInfo.TYPE_RETURN_TO_HOME,
|
|
new BackAnimationRunner(
|
|
callback,
|
|
runner,
|
|
controller.mContext,
|
|
CUJ_PREDICTIVE_BACK_HOME,
|
|
mHandler)));
|
|
}
|
|
|
|
@Override
|
|
public void clearBackToLauncherCallback() {
|
|
executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
|
|
(controller) -> controller.unregisterAnimation(
|
|
BackNavigationInfo.TYPE_RETURN_TO_HOME));
|
|
}
|
|
|
|
public void customizeStatusBarAppearance(AppearanceRegion appearance) {
|
|
executeRemoteCallWithTaskPermission(mController, "useLauncherSysBarFlags",
|
|
(controller) -> controller.customizeStatusBarAppearance(appearance));
|
|
}
|
|
|
|
@Override
|
|
public void invalidate() {
|
|
mController = null;
|
|
}
|
|
}
|
|
|
|
private void customizeStatusBarAppearance(AppearanceRegion appearance) {
|
|
if (mCustomizer != null) {
|
|
mCustomizer.customizeStatusBarAppearance(appearance);
|
|
}
|
|
}
|
|
|
|
void registerAnimation(@BackNavigationInfo.BackTargetType int type,
|
|
@NonNull BackAnimationRunner runner) {
|
|
mShellBackAnimationRegistry.registerAnimation(type, runner);
|
|
}
|
|
|
|
void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
|
|
mShellBackAnimationRegistry.unregisterAnimation(type);
|
|
}
|
|
|
|
private BackTouchTracker getActiveTracker() {
|
|
if (mCurrentTracker.isActive()) return mCurrentTracker;
|
|
if (mQueuedTracker.isActive()) return mQueuedTracker;
|
|
return null;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public void onThresholdCrossed() {
|
|
mThresholdCrossed = true;
|
|
BackTouchTracker activeTracker = getActiveTracker();
|
|
if (predictiveBackDelayWmTransition() && activeTracker != null && mActiveCallback == null
|
|
&& mBackGestureStarted) {
|
|
startBackNavigation(activeTracker);
|
|
}
|
|
// There was no focus window when calling startBackNavigation, still pilfer pointers so
|
|
// the next focus window won't receive motion events.
|
|
if (mBackNavigationInfo == null && mReceivedNullNavigationInfo) {
|
|
tryPilferPointers();
|
|
return;
|
|
}
|
|
// Dispatch onBackStarted, only to app callbacks.
|
|
// System callbacks will receive onBackStarted when the remote animation starts.
|
|
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
|
|
if (!shouldDispatchToAnimator && mActiveCallback != null) {
|
|
mCurrentTracker.updateStartLocation();
|
|
tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
|
|
if (mBackNavigationInfo != null && !isAppProgressGenerationAllowed()) {
|
|
tryPilferPointers();
|
|
}
|
|
} else if (shouldDispatchToAnimator) {
|
|
tryPilferPointers();
|
|
}
|
|
}
|
|
|
|
private boolean isAppProgressGenerationAllowed() {
|
|
return mBackNavigationInfo.isAppProgressGenerationAllowed()
|
|
&& mBackNavigationInfo.getTouchableRegion().equals(mTouchableArea);
|
|
}
|
|
|
|
/**
|
|
* Called when a new motion event needs to be transferred to this
|
|
* {@link BackAnimationController}
|
|
*/
|
|
public void onMotionEvent(
|
|
float touchX,
|
|
float touchY,
|
|
int keyAction,
|
|
@BackEvent.SwipeEdge int swipeEdge,
|
|
int displayId) {
|
|
|
|
if (ENABLE_INDEPENDENT_BACK_IN_PROJECTED.isTrue()) {
|
|
mBackAnimationAdapter.mOriginDisplayId = displayId;
|
|
}
|
|
|
|
BackTouchTracker activeTouchTracker = getActiveTracker();
|
|
if (activeTouchTracker != null) {
|
|
activeTouchTracker.update(touchX, touchY);
|
|
}
|
|
|
|
// two gestures are waiting to be processed at the moment, skip any further user touches
|
|
if (mCurrentTracker.isFinished() && mQueuedTracker.isFinished()) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
|
"Ignoring MotionEvent because two gestures are already being queued.");
|
|
return;
|
|
} else if (mBackGestureStarted && mCurrentTracker.isInInitialState()
|
|
&& mQueuedTracker.isInInitialState()) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW,
|
|
"Both touch trackers in initial state and mBackGestureStarted=true");
|
|
mBackGestureStarted = false;
|
|
}
|
|
|
|
if (keyAction == MotionEvent.ACTION_DOWN) {
|
|
if (!mBackGestureStarted) {
|
|
if (swipeEdge == EDGE_NONE) {
|
|
// start animation immediately for non-gestural sources (without ACTION_MOVE
|
|
// events)
|
|
if (!predictiveBackDelayWmTransition()) {
|
|
mThresholdCrossed = true;
|
|
}
|
|
mPointersPilfered = true;
|
|
onGestureStarted(touchX, touchY, swipeEdge);
|
|
if (predictiveBackDelayWmTransition()) {
|
|
onThresholdCrossed();
|
|
}
|
|
mShouldStartOnNextMoveEvent = false;
|
|
} else {
|
|
if (predictiveBackDelayWmTransition()) {
|
|
onGestureStarted(touchX, touchY, swipeEdge);
|
|
} else {
|
|
mShouldStartOnNextMoveEvent = true;
|
|
}
|
|
}
|
|
}
|
|
} else if (keyAction == MotionEvent.ACTION_MOVE) {
|
|
if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {
|
|
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
|
|
// could be happened when ACTION_DOWN, it may change the current focus that we
|
|
// would access it when startBackNavigation.
|
|
onGestureStarted(touchX, touchY, swipeEdge);
|
|
mShouldStartOnNextMoveEvent = false;
|
|
}
|
|
onMove(swipeEdge);
|
|
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
|
"Finishing gesture with event action: %d", keyAction);
|
|
if (keyAction == MotionEvent.ACTION_CANCEL) {
|
|
setTriggerBack(false);
|
|
}
|
|
onGestureFinished();
|
|
}
|
|
}
|
|
|
|
private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
|
|
boolean interruptCancelPostCommitAnimation = mPostCommitAnimationInProgress
|
|
&& mCurrentTracker.isFinished() && !mCurrentTracker.getTriggerBack()
|
|
&& mQueuedTracker.isInInitialState();
|
|
if (interruptCancelPostCommitAnimation) {
|
|
// If a system animation is currently in the post-commit phase animating an
|
|
// onBackCancelled event, let's interrupt it and start animating a new back gesture
|
|
resetTouchTracker();
|
|
}
|
|
BackTouchTracker touchTracker;
|
|
if (mCurrentTracker.isInInitialState()) {
|
|
touchTracker = mCurrentTracker;
|
|
} else if (mQueuedTracker.isInInitialState()) {
|
|
touchTracker = mQueuedTracker;
|
|
} else {
|
|
ProtoLog.w(WM_SHELL_BACK_PREVIEW,
|
|
"Cannot start tracking new gesture with neither tracker in initial state.");
|
|
return;
|
|
}
|
|
touchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
|
|
touchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE);
|
|
mBackGestureStarted = true;
|
|
|
|
if (interruptCancelPostCommitAnimation) {
|
|
// post-commit cancel is currently running. let's interrupt it and dispatch a new
|
|
// onBackStarted event.
|
|
mPostCommitAnimationInProgress = false;
|
|
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
|
|
startSystemAnimation();
|
|
} else if (!predictiveBackDelayWmTransition()) {
|
|
startBackNavigation(touchTracker);
|
|
}
|
|
}
|
|
|
|
private void startBackNavigation(@NonNull BackTouchTracker touchTracker) {
|
|
if (touchTracker != mCurrentTracker) {
|
|
// Only start the back navigation if no other gesture is being processed. Otherwise,
|
|
// the back navigation will fall back to legacy back event injection.
|
|
return;
|
|
}
|
|
try {
|
|
startLatencyTracking();
|
|
if (mBackAnimationAdapter != null
|
|
&& mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) {
|
|
mBackAnimationAdapter.updateSupportedAnimators(
|
|
mShellBackAnimationRegistry.getSupportedAnimators());
|
|
}
|
|
mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
|
|
mNavigationObserver, mBackAnimationAdapter);
|
|
onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker);
|
|
} catch (RemoteException remoteException) {
|
|
Log.e(TAG, "Failed to initAnimation", remoteException);
|
|
finishBackNavigation(touchTracker.getTriggerBack());
|
|
}
|
|
}
|
|
|
|
private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo,
|
|
@NonNull BackTouchTracker touchTracker) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
|
|
if (backNavigationInfo == null) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
|
|
mReceivedNullNavigationInfo = true;
|
|
cancelLatencyTracking();
|
|
tryPilferPointers();
|
|
return;
|
|
}
|
|
final int backType = backNavigationInfo.getType();
|
|
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
|
|
if (shouldDispatchToAnimator) {
|
|
if (!mShellBackAnimationRegistry.startGesture(backType)) {
|
|
mActiveCallback = null;
|
|
}
|
|
requestTopUi(true, backType);
|
|
tryPilferPointers();
|
|
} else {
|
|
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
|
|
// App is handling back animation. Cancel system animation latency tracking.
|
|
cancelLatencyTracking();
|
|
tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
|
|
if (!isAppProgressGenerationAllowed()) {
|
|
tryPilferPointers();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void onMove(@BackEvent.SwipeEdge int swipeEdge) {
|
|
if (predictiveBackDelayWmTransition() && mCurrentTracker.isActive()) {
|
|
mCurrentTracker.updateSwipeEdge(swipeEdge);
|
|
}
|
|
if (!mBackGestureStarted
|
|
|| mBackNavigationInfo == null
|
|
|| mActiveCallback == null
|
|
|| !mOnBackStartDispatched) {
|
|
return;
|
|
}
|
|
// Skip dispatching if the move corresponds to the queued instead of the current gesture
|
|
if (mQueuedTracker.isActive()) return;
|
|
final BackMotionEvent backEvent = mCurrentTracker.createProgressEvent();
|
|
dispatchOnBackProgressed(mActiveCallback, backEvent);
|
|
}
|
|
|
|
private void injectBackKey(int displayId) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "injectBackKey");
|
|
sendBackEvent(KeyEvent.ACTION_DOWN, displayId);
|
|
sendBackEvent(KeyEvent.ACTION_UP, displayId);
|
|
}
|
|
|
|
@SuppressLint("MissingPermission")
|
|
private void sendBackEvent(int action, int displayId) {
|
|
final long when = SystemClock.uptimeMillis();
|
|
final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
|
|
0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
|
|
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
|
|
InputDevice.SOURCE_KEYBOARD);
|
|
if (ENABLE_INDEPENDENT_BACK_IN_PROJECTED.isTrue()) {
|
|
ev.setDisplayId(displayId);
|
|
}
|
|
|
|
if (!mContext.getSystemService(InputManager.class)
|
|
.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Inject input event fail");
|
|
}
|
|
}
|
|
|
|
private boolean shouldDispatchToAnimator() {
|
|
return mBackNavigationInfo != null && mBackNavigationInfo.isPrepareRemoteAnimation();
|
|
}
|
|
|
|
private void tryPilferPointers() {
|
|
if (mPointersPilfered || !mThresholdCrossed) {
|
|
return;
|
|
}
|
|
if (mPilferPointerCallback != null) {
|
|
mPilferPointerCallback.run();
|
|
}
|
|
mPointersPilfered = true;
|
|
}
|
|
|
|
private void tryDispatchOnBackStarted(
|
|
IOnBackInvokedCallback callback,
|
|
BackMotionEvent backEvent) {
|
|
if (mOnBackStartDispatched
|
|
|| callback == null
|
|
|| (!mThresholdCrossed && mRequirePointerPilfer)) {
|
|
return;
|
|
}
|
|
dispatchOnBackStarted(callback, backEvent);
|
|
}
|
|
|
|
private void dispatchOnBackStarted(
|
|
IOnBackInvokedCallback callback,
|
|
BackMotionEvent backEvent) {
|
|
if (callback == null) {
|
|
return;
|
|
}
|
|
try {
|
|
callback.onBackStarted(backEvent);
|
|
if (mBackTransitionHandler.canHandOffAnimation()) {
|
|
callback.setHandoffHandler(mHandoffHandler);
|
|
} else {
|
|
callback.setHandoffHandler(null);
|
|
}
|
|
mOnBackStartDispatched = true;
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "dispatchOnBackStarted error: ", e);
|
|
}
|
|
}
|
|
|
|
private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
|
|
if (callback == null) {
|
|
return;
|
|
}
|
|
try {
|
|
callback.onBackInvoked();
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "dispatchOnBackInvoked error: ", e);
|
|
}
|
|
}
|
|
|
|
private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) {
|
|
if (!mOnBackStartDispatched) {
|
|
Log.d(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
|
|
return;
|
|
}
|
|
if (callback == null) {
|
|
return;
|
|
}
|
|
try {
|
|
callback.onBackCancelled();
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
|
|
}
|
|
}
|
|
|
|
private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
|
|
BackMotionEvent backEvent) {
|
|
if (callback == null || (!shouldDispatchToAnimator() && mBackNavigationInfo != null
|
|
&& isAppProgressGenerationAllowed())) {
|
|
return;
|
|
}
|
|
try {
|
|
callback.onBackProgressed(backEvent);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Latest task id which back gesture has occurred on it.
|
|
*/
|
|
public int getLatestTriggerBackTask() {
|
|
return mBackTransitionObserver.mFocusedTaskId;
|
|
}
|
|
|
|
/**
|
|
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
|
|
*/
|
|
public void setTriggerBack(boolean triggerBack) {
|
|
if (mActiveCallback != null) {
|
|
try {
|
|
mActiveCallback.setTriggerBack(triggerBack);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "remote setTriggerBack error: ", e);
|
|
}
|
|
}
|
|
BackTouchTracker activeBackGestureInfo = getActiveTracker();
|
|
if (activeBackGestureInfo != null) {
|
|
activeBackGestureInfo.setTriggerBack(triggerBack);
|
|
}
|
|
}
|
|
|
|
private void setSwipeThresholds(
|
|
float linearDistance,
|
|
float maxDistance,
|
|
float nonLinearFactor) {
|
|
mCurrentTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
|
|
mQueuedTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor);
|
|
}
|
|
|
|
private void invokeOrCancelBack(@NonNull BackTouchTracker touchTracker) {
|
|
// Make a synchronized call to core before dispatch back event to client side.
|
|
// If the close transition happens before the core receives onAnimationFinished, there will
|
|
// play a second close animation for that transition.
|
|
if (mBackAnimationFinishedCallback != null) {
|
|
try {
|
|
mBackAnimationFinishedCallback.onAnimationFinished(touchTracker.getTriggerBack());
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
|
|
}
|
|
mBackAnimationFinishedCallback = null;
|
|
}
|
|
|
|
if (mBackNavigationInfo != null && !mRealCallbackInvoked) {
|
|
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
|
|
if (touchTracker.getTriggerBack()) {
|
|
dispatchOnBackInvoked(callback);
|
|
} else {
|
|
tryDispatchOnBackCancelled(callback);
|
|
}
|
|
}
|
|
mRealCallbackInvoked = false;
|
|
finishBackNavigation(touchTracker.getTriggerBack());
|
|
}
|
|
|
|
/**
|
|
* Called when the gesture is released, then it could start the post commit animation.
|
|
*/
|
|
private void onGestureFinished() {
|
|
BackTouchTracker activeTouchTracker = getActiveTracker();
|
|
if (!mBackGestureStarted || activeTouchTracker == null) {
|
|
// This can happen when an unfinished gesture has been reset in resetTouchTracker
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
|
"onGestureFinished called while no gesture is started");
|
|
return;
|
|
}
|
|
boolean triggerBack = activeTouchTracker.getTriggerBack();
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", triggerBack);
|
|
|
|
if (triggerBack) {
|
|
mBackTransitionObserver.update(mBackNavigationInfo != null
|
|
? mBackNavigationInfo.getFocusedTaskId()
|
|
: INVALID_TASK_ID);
|
|
}
|
|
// Reset gesture states.
|
|
mThresholdCrossed = false;
|
|
mPointersPilfered = false;
|
|
mBackGestureStarted = false;
|
|
activeTouchTracker.setState(BackTouchTracker.TouchTrackerState.FINISHED);
|
|
|
|
if (mPostCommitAnimationInProgress) {
|
|
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
|
|
return;
|
|
}
|
|
|
|
if (mBackNavigationInfo == null) {
|
|
// No focus window found or core are running recents animation, inject back key as
|
|
// legacy behavior, or new back gesture was started while previous has not finished yet
|
|
if (!mQueuedTracker.isInInitialState()) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "mBackNavigationInfo is null AND there is "
|
|
+ "another back animation in progress");
|
|
}
|
|
mCurrentTracker.reset();
|
|
if (triggerBack) {
|
|
injectBackKey(mBackAnimationAdapter.mOriginDisplayId);
|
|
}
|
|
finishBackNavigation(triggerBack);
|
|
return;
|
|
}
|
|
|
|
final int backType = mBackNavigationInfo.getType();
|
|
// Simply trigger and finish back navigation when no animator defined.
|
|
if (!shouldDispatchToAnimator()
|
|
|| mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Trigger back without dispatching to animator.");
|
|
invokeOrCancelBack(mCurrentTracker);
|
|
mCurrentTracker.reset();
|
|
return;
|
|
} else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) {
|
|
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
|
|
// Supposed it is in post commit animation state, and start the timeout to watch
|
|
// if the animation is ready.
|
|
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
|
|
return;
|
|
}
|
|
startPostCommitAnimation();
|
|
}
|
|
|
|
/**
|
|
* Start the phase 2 animation when gesture is released.
|
|
* Callback to {@link #onBackAnimationFinished} when it is finished or timeout.
|
|
*/
|
|
private void startPostCommitAnimation() {
|
|
if (mPostCommitAnimationInProgress) {
|
|
return;
|
|
}
|
|
|
|
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
|
|
mPostCommitAnimationInProgress = true;
|
|
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
|
|
|
|
// The next callback should be {@link #onBackAnimationFinished}.
|
|
if (mCurrentTracker.getTriggerBack()) {
|
|
// notify core gesture is commit
|
|
if (shouldTriggerCloseTransition()) {
|
|
mBackTransitionHandler.mCloseTransitionRequested = true;
|
|
final IOnBackInvokedCallback callback =
|
|
mBackNavigationInfo.getOnBackInvokedCallback();
|
|
// invoked client side onBackInvoked
|
|
dispatchOnBackInvoked(callback);
|
|
mRealCallbackInvoked = true;
|
|
}
|
|
// start post animation
|
|
dispatchOnBackInvoked(mActiveCallback);
|
|
} else {
|
|
tryDispatchOnBackCancelled(mActiveCallback);
|
|
}
|
|
}
|
|
|
|
// Close window won't create any transition
|
|
private boolean shouldTriggerCloseTransition() {
|
|
if (mBackNavigationInfo == null) {
|
|
return false;
|
|
}
|
|
int type = mBackNavigationInfo.getType();
|
|
return type == BackNavigationInfo.TYPE_RETURN_TO_HOME
|
|
|| type == BackNavigationInfo.TYPE_CROSS_TASK
|
|
|| type == BackNavigationInfo.TYPE_CROSS_ACTIVITY;
|
|
}
|
|
/**
|
|
* Called when the post commit animation is completed or timeout.
|
|
* This will trigger the real {@link IOnBackInvokedCallback} behavior.
|
|
*/
|
|
@VisibleForTesting
|
|
void onBackAnimationFinished() {
|
|
if (!mPostCommitAnimationInProgress) {
|
|
// This can happen when a post-commit cancel animation was interrupted by a new back
|
|
// gesture but the timing of interruption was bad such that the back-callback
|
|
// implementation finished in between the time of the new gesture having started and
|
|
// the time of the back-callback receiving the new onBackStarted event. Due to the
|
|
// asynchronous APIs this isn't an unlikely case. To handle this, let's return early.
|
|
// The back-callback implementation will call onBackAnimationFinished again when it is
|
|
// done with animating the second gesture.
|
|
return;
|
|
}
|
|
finishBackAnimation();
|
|
}
|
|
|
|
private void finishBackAnimation() {
|
|
// Stop timeout runner.
|
|
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
|
|
mPostCommitAnimationInProgress = false;
|
|
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
|
|
|
|
if (mCurrentTracker.isActive() || mCurrentTracker.isFinished()) {
|
|
// Trigger the real back.
|
|
invokeOrCancelBack(mCurrentTracker);
|
|
} else {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
|
"mCurrentBackGestureInfo was null when back animation finished");
|
|
}
|
|
resetTouchTracker();
|
|
mBackTransitionHandler.onAnimationFinished();
|
|
}
|
|
|
|
/**
|
|
* Resets the BackTouchTracker and potentially starts a new back navigation in case one
|
|
* is queued.
|
|
*/
|
|
private void resetTouchTracker() {
|
|
BackTouchTracker temp = mCurrentTracker;
|
|
mCurrentTracker = mQueuedTracker;
|
|
temp.reset();
|
|
mQueuedTracker = temp;
|
|
|
|
if (mCurrentTracker.isInInitialState()) {
|
|
if (mBackGestureStarted) {
|
|
mBackGestureStarted = false;
|
|
tryDispatchOnBackCancelled(mActiveCallback);
|
|
finishBackNavigation(false);
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
|
"resetTouchTracker -> reset an unfinished gesture");
|
|
} else {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> no queued gesture");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mCurrentTracker.isFinished() && mCurrentTracker.getTriggerBack()) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> start queued back navigation "
|
|
+ "AND post commit animation");
|
|
injectBackKey(mBackAnimationAdapter.mOriginDisplayId);
|
|
finishBackNavigation(true);
|
|
mCurrentTracker.reset();
|
|
} else if (!mCurrentTracker.isFinished()) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
|
|
"resetTouchTracker -> queued gesture not finished; do nothing");
|
|
} else {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> reset queued gesture");
|
|
mCurrentTracker.reset();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This should be called after the whole back navigation is completed.
|
|
*/
|
|
@VisibleForTesting
|
|
void finishBackNavigation(boolean triggerBack) {
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
|
|
mActiveCallback = null;
|
|
mApps = null;
|
|
mOnBackStartDispatched = false;
|
|
mThresholdCrossed = false;
|
|
mPointersPilfered = false;
|
|
mShellBackAnimationRegistry.resetDefaultCrossActivity();
|
|
cancelLatencyTracking();
|
|
mReceivedNullNavigationInfo = false;
|
|
if (mBackNavigationInfo != null) {
|
|
mPreviousNavigationType = mBackNavigationInfo.getType();
|
|
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
|
|
mBackNavigationInfo = null;
|
|
requestTopUi(false, mPreviousNavigationType);
|
|
}
|
|
}
|
|
|
|
private void startLatencyTracking() {
|
|
if (mTrackingLatency) {
|
|
cancelLatencyTracking();
|
|
}
|
|
mLatencyTracker.onActionStart(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
|
|
mTrackingLatency = true;
|
|
}
|
|
|
|
private void cancelLatencyTracking() {
|
|
if (!mTrackingLatency) {
|
|
return;
|
|
}
|
|
mLatencyTracker.onActionCancel(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
|
|
mTrackingLatency = false;
|
|
}
|
|
|
|
private void endLatencyTracking() {
|
|
if (!mTrackingLatency) {
|
|
return;
|
|
}
|
|
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION);
|
|
mTrackingLatency = false;
|
|
}
|
|
|
|
private void startSystemAnimation() {
|
|
if (mBackNavigationInfo == null) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Lack of navigation info to start animation.");
|
|
return;
|
|
}
|
|
if (!validateAnimationTargets(mApps)) {
|
|
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Not starting animation due to mApps being null.");
|
|
return;
|
|
}
|
|
|
|
final BackAnimationRunner runner =
|
|
mShellBackAnimationRegistry.getAnimationRunnerAndInit(mBackNavigationInfo);
|
|
if (runner == null) {
|
|
if (mBackAnimationFinishedCallback != null) {
|
|
try {
|
|
mBackAnimationFinishedCallback.onAnimationFinished(false);
|
|
} catch (RemoteException e) {
|
|
Log.w(TAG, "Failed call IBackNaviAnimationController", e);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
mActiveCallback = runner.getCallback();
|
|
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
|
|
|
|
runner.startAnimation(mApps, /*wallpapers*/ null, /*nonApps*/ null,
|
|
() -> mShellExecutor.execute(this::onBackAnimationFinished));
|
|
|
|
if (mApps.length >= 1) {
|
|
BackMotionEvent startEvent = mCurrentTracker.createStartEvent(
|
|
Flags.removeDepartTargetFromMotion() ? null : mApps[0]);
|
|
dispatchOnBackStarted(mActiveCallback, startEvent);
|
|
if (startEvent.getSwipeEdge() == EDGE_NONE) {
|
|
// TODO(b/373544911): onBackStarted is dispatched here so that
|
|
// WindowOnBackInvokedDispatcher knows about the back navigation and intercepts
|
|
// touch events while it's active. It would be cleaner and safer to disable
|
|
// multitouch altogether (same as in gesture-nav).
|
|
dispatchOnBackStarted(mBackNavigationInfo.getOnBackInvokedCallback(), startEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void requestTopUi(boolean hasTopUi, int backType) {
|
|
if (mRequestTopUiCallback != null && (backType == BackNavigationInfo.TYPE_CROSS_TASK
|
|
|| backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
|
|
mRequestTopUiCallback.requestTopUi(hasTopUi, TAG);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate animation targets.
|
|
*/
|
|
static boolean validateAnimationTargets(RemoteAnimationTarget[] apps) {
|
|
if (apps == null || apps.length == 0) {
|
|
return false;
|
|
}
|
|
for (int i = apps.length - 1; i >= 0; --i) {
|
|
if (!apps[i].leash.isValid()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void kickStartAnimation() {
|
|
startSystemAnimation();
|
|
|
|
// Dispatch the first progress after animation start for
|
|
// smoothing the initial animation, instead of waiting for next
|
|
// onMove.
|
|
final BackMotionEvent backFinish = mCurrentTracker
|
|
.createProgressEvent();
|
|
dispatchOnBackProgressed(mActiveCallback, backFinish);
|
|
if (mCurrentTracker.isFinished()) {
|
|
// if the down -> up gesture happened before animation
|
|
// start, we have to trigger the uninterruptible transition
|
|
// to finish the back animation.
|
|
startPostCommitAnimation();
|
|
}
|
|
}
|
|
|
|
private void createAdapter() {
|
|
IBackAnimationRunner runner =
|
|
new IBackAnimationRunner.Stub() {
|
|
@Override
|
|
public void onAnimationStart(
|
|
RemoteAnimationTarget[] apps,
|
|
IBinder token,
|
|
IBackAnimationFinishedCallback finishedCallback) {
|
|
mShellExecutor.execute(
|
|
() -> {
|
|
endLatencyTracking();
|
|
if (!validateAnimationTargets(apps)) {
|
|
Log.e(TAG, "Invalid animation targets!");
|
|
return;
|
|
}
|
|
mBackAnimationFinishedCallback = finishedCallback;
|
|
mApps = apps;
|
|
// app only visible after transition ready, break for now.
|
|
if (token != null) {
|
|
return;
|
|
}
|
|
kickStartAnimation();
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationCancelled() {
|
|
mShellExecutor.execute(
|
|
() -> {
|
|
if (!mShellBackAnimationRegistry.cancel(
|
|
mBackNavigationInfo != null
|
|
? mBackNavigationInfo.getType()
|
|
: mPreviousNavigationType)) {
|
|
return;
|
|
}
|
|
if (!mBackGestureStarted) {
|
|
invokeOrCancelBack(mCurrentTracker);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
mBackAnimationAdapter = new BackAnimationAdapter(runner);
|
|
}
|
|
|
|
private void registerBackGestureDelegate() {
|
|
mInputManager.registerKeyGestureEventHandler(List.of(KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
|
|
(event, focussedToken) -> {
|
|
if (event.getKeyGestureType() == KeyGestureEvent.KEY_GESTURE_TYPE_BACK) {
|
|
mShellExecutor.execute(() -> {
|
|
if (mBackGestureStarted) {
|
|
Log.w(TAG, "Back gesture is running, ignore request");
|
|
return;
|
|
}
|
|
onMotionEvent(0, 0, KeyEvent.ACTION_DOWN, EDGE_NONE, INVALID_DISPLAY);
|
|
setTriggerBack(true);
|
|
onMotionEvent(0, 0, KeyEvent.ACTION_UP, EDGE_NONE, INVALID_DISPLAY);
|
|
});
|
|
} else {
|
|
Log.w(TAG, "Unsupported gesture " + event + " received!");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Description of current BackAnimationController state.
|
|
*/
|
|
private void dump(PrintWriter pw, String prefix) {
|
|
pw.println(prefix + "BackAnimationController state:");
|
|
pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
|
|
pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
|
|
pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
|
|
pw.println(prefix + " mPointerPilfered=" + mThresholdCrossed);
|
|
pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer);
|
|
pw.println(prefix + " mCurrentTracker state:");
|
|
mCurrentTracker.dump(pw, prefix + " ");
|
|
pw.println(prefix + " mQueuedTracker state:");
|
|
mQueuedTracker.dump(pw, prefix + " ");
|
|
}
|
|
|
|
class BackTransitionHandler implements Transitions.TransitionHandler {
|
|
private final Transitions mTransitions;
|
|
|
|
Runnable mOnAnimationFinishCallback;
|
|
boolean mCloseTransitionRequested;
|
|
SurfaceControl.Transaction mFinishOpenTransaction;
|
|
Transitions.TransitionFinishCallback mFinishOpenTransitionCallback;
|
|
// The Transition to make behindActivity become visible
|
|
IBinder mPrepareOpenTransition;
|
|
// The Transition to make behindActivity become invisible, if prepare open exist and
|
|
// animation is canceled, start a close prepare transition to finish the whole transition.
|
|
IBinder mClosePrepareTransition;
|
|
TransitionInfo mOpenTransitionInfo;
|
|
Transitions.TransitionHandler mTakeoverHandler;
|
|
|
|
BackTransitionHandler(Transitions transitions) {
|
|
mTransitions = transitions;
|
|
}
|
|
|
|
void onAnimationFinished() {
|
|
if (!mCloseTransitionRequested && mPrepareOpenTransition != null) {
|
|
createClosePrepareTransition();
|
|
}
|
|
if (mOnAnimationFinishCallback != null) {
|
|
mOnAnimationFinishCallback.run();
|
|
mOnAnimationFinishCallback = null;
|
|
}
|
|
}
|
|
|
|
private void applyFinishOpenTransition() {
|
|
if (mFinishOpenTransaction != null) {
|
|
final SurfaceControl.Transaction t = mFinishOpenTransaction;
|
|
t.apply();
|
|
}
|
|
if (mFinishOpenTransitionCallback != null) {
|
|
final Transitions.TransitionFinishCallback callback = mFinishOpenTransitionCallback;
|
|
callback.onTransitionFinished(null);
|
|
}
|
|
cleanUpInternalState();
|
|
}
|
|
|
|
private void cleanUpInternalState() {
|
|
mOpenTransitionInfo = null;
|
|
mPrepareOpenTransition = null;
|
|
mFinishOpenTransaction = null;
|
|
mFinishOpenTransitionCallback = null;
|
|
mTakeoverHandler = null;
|
|
}
|
|
|
|
private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
|
|
@NonNull SurfaceControl.Transaction ft,
|
|
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
|
applyFinishOpenTransition();
|
|
st.apply();
|
|
ft.apply();
|
|
finishCallback.onTransitionFinished(null);
|
|
mCloseTransitionRequested = false;
|
|
}
|
|
|
|
@Override
|
|
public boolean startAnimation(@NonNull IBinder transition,
|
|
@NonNull TransitionInfo info,
|
|
@NonNull SurfaceControl.Transaction st,
|
|
@NonNull SurfaceControl.Transaction ft,
|
|
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
|
// Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
|
|
// need to post to ShellExecutor when called.
|
|
if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
|
|
// only consume it if this transition hasn't being processed.
|
|
if (mClosePrepareTransition != null) {
|
|
mClosePrepareTransition = null;
|
|
applyAndFinish(st, ft, finishCallback);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
|
|
&& isNotGestureBackTransition(info)) {
|
|
return false;
|
|
}
|
|
|
|
if (shouldCancelAnimation(info)) {
|
|
mPrepareOpenTransition = null;
|
|
return false;
|
|
}
|
|
|
|
if (mApps == null || mApps.length == 0) {
|
|
if (mCloseTransitionRequested) {
|
|
// animation never start, consume directly
|
|
applyAndFinish(st, ft, finishCallback);
|
|
return true;
|
|
} else if (mClosePrepareTransition == null
|
|
&& info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
|
|
// Gesture animation was cancelled before prepare transition ready, create
|
|
// the close prepare transition
|
|
createClosePrepareTransition();
|
|
}
|
|
}
|
|
|
|
if (handlePrepareTransition(transition, info, st, ft, finishCallback)) {
|
|
if (checkTakeoverFlags()) {
|
|
mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info);
|
|
}
|
|
kickStartAnimation();
|
|
return true;
|
|
}
|
|
return handleCloseTransition(info, st, ft, finishCallback);
|
|
}
|
|
|
|
private boolean canHandOffAnimation() {
|
|
if (!checkTakeoverFlags()) {
|
|
return false;
|
|
}
|
|
|
|
return mTakeoverHandler != null;
|
|
}
|
|
|
|
private void handOffAnimation(
|
|
RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
|
|
if (!checkTakeoverFlags()) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW,
|
|
"Trying to hand off the animation, but the required flags are disabled.");
|
|
return;
|
|
} else if (mTakeoverHandler == null) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW,
|
|
"Missing takeover handler when trying to hand off animation.");
|
|
return;
|
|
} else if (targets.length != states.length) {
|
|
ProtoLog.e(WM_SHELL_BACK_PREVIEW,
|
|
"Targets passed for takeover don't match the window states.");
|
|
return;
|
|
}
|
|
|
|
// The states passed to this method are paired with the targets, but they need to be
|
|
// paired with the changes inside the TransitionInfo. So for each change we find its
|
|
// matching target, and leave the state for any change missing a matching target blank.
|
|
WindowAnimationState[] updatedStates =
|
|
new WindowAnimationState[mOpenTransitionInfo.getChanges().size()];
|
|
for (int i = 0; i < mOpenTransitionInfo.getChanges().size(); i++) {
|
|
ActivityManager.RunningTaskInfo taskInfo =
|
|
mOpenTransitionInfo.getChanges().get(i).getTaskInfo();
|
|
if (taskInfo == null) {
|
|
continue;
|
|
}
|
|
|
|
for (int j = 0; j < targets.length; j++) {
|
|
if (taskInfo.taskId == targets[j].taskId) {
|
|
updatedStates[i] = states[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mTakeoverHandler.takeOverAnimation(
|
|
mPrepareOpenTransition, mOpenTransitionInfo, new SurfaceControl.Transaction(),
|
|
mFinishOpenTransitionCallback, updatedStates);
|
|
|
|
cleanUpInternalState();
|
|
}
|
|
|
|
@Override
|
|
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
|
|
@Nullable SurfaceControl.Transaction finishTransaction) {
|
|
if (transition == mClosePrepareTransition && aborted) {
|
|
mClosePrepareTransition = null;
|
|
applyFinishOpenTransition();
|
|
} else if (!aborted && unifyBackNavigationTransition()) {
|
|
// Since the closing target participates in the predictive back transition, the
|
|
// merged transition must be applied with the first transition to ensure a seamless
|
|
// animation.
|
|
if (mFinishOpenTransaction != null && finishTransaction != null) {
|
|
mFinishOpenTransaction.merge(finishTransaction);
|
|
}
|
|
}
|
|
}
|
|
|
|
void createClosePrepareTransition() {
|
|
if (mClosePrepareTransition != null) {
|
|
Log.e(TAG, "Re-create close prepare transition");
|
|
return;
|
|
}
|
|
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
|
wct.restoreBackNavi();
|
|
mClosePrepareTransition = mTransitions.startTransition(
|
|
TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, wct, mBackTransitionHandler);
|
|
}
|
|
private void mergePendingTransitions(TransitionInfo info) {
|
|
if (mOpenTransitionInfo == null) {
|
|
return;
|
|
}
|
|
// Copy initial changes to final transition
|
|
final TransitionInfo init = mOpenTransitionInfo;
|
|
// Find prepare open target
|
|
boolean openShowWallpaper = false;
|
|
final ArrayList<SurfaceControl> openSurfaces = new ArrayList<>();
|
|
int tmpSize;
|
|
for (int j = init.getChanges().size() - 1; j >= 0; --j) {
|
|
final TransitionInfo.Change change = init.getChanges().get(j);
|
|
if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
|
|
&& TransitionUtil.isOpeningMode(change.getMode())) {
|
|
final ComponentName openComponent = findComponentName(change);
|
|
final int openTaskId = findTaskId(change);
|
|
final WindowContainerToken openToken = findToken(change);
|
|
if (openComponent == null && openTaskId == INVALID_TASK_ID
|
|
&& openToken == null) {
|
|
continue;
|
|
}
|
|
openSurfaces.add(change.getLeash());
|
|
if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
|
|
openShowWallpaper = true;
|
|
}
|
|
}
|
|
}
|
|
if (openSurfaces.isEmpty()) {
|
|
// This shouldn't happen, but if that happen, consume the initial transition anyway.
|
|
Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
|
|
+ "animated target from the open transition=" + mOpenTransitionInfo);
|
|
mOpenTransitionInfo = null;
|
|
return;
|
|
}
|
|
// Find first non-prepare open target
|
|
boolean isOpen = false;
|
|
tmpSize = info.getChanges().size();
|
|
for (int j = 0; j < tmpSize; ++j) {
|
|
final TransitionInfo.Change change = info.getChanges().get(j);
|
|
if (isOpenSurfaceMatched(openSurfaces, change)) {
|
|
// This is original close target, potential be close, but cannot determine
|
|
// from it.
|
|
if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
|
|
isOpen = !TransitionUtil.isClosingMode(change.getMode());
|
|
} else {
|
|
isOpen = TransitionUtil.isOpeningMode(change.getMode());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!isOpen) {
|
|
// Close transition, the transition info should be:
|
|
// init info(open A & wallpaper) => init info(open A & change B & wallpaper)
|
|
// current info(close B target) => current info(change A & close B)
|
|
// remove init info(open/change A target & wallpaper)
|
|
boolean moveToTop = false;
|
|
boolean excludeOpenTarget = false;
|
|
boolean mergePredictive = false;
|
|
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
|
|
final TransitionInfo.Change change = info.getChanges().get(j);
|
|
if (isOpenSurfaceMatched(openSurfaces, change)) {
|
|
if (TransitionUtil.isClosingMode(change.getMode())) {
|
|
excludeOpenTarget = true;
|
|
}
|
|
moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
|
|
info.getChanges().remove(j);
|
|
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
|
|
info.getChanges().remove(j);
|
|
} else if (!mergePredictive && TransitionUtil.isClosingMode(change.getMode())) {
|
|
mergePredictive = true;
|
|
}
|
|
}
|
|
// Ignore merge if there is no close target
|
|
if (!info.getChanges().isEmpty() && mergePredictive) {
|
|
tmpSize = init.getChanges().size();
|
|
for (int i = 0; i < tmpSize; ++i) {
|
|
final TransitionInfo.Change change = init.getChanges().get(i);
|
|
if (change.hasFlags(FLAG_IS_WALLPAPER)) {
|
|
continue;
|
|
}
|
|
if (isOpenSurfaceMatched(openSurfaces, change)) {
|
|
if (excludeOpenTarget) {
|
|
// App has triggered another change during predictive back
|
|
// transition, filter out predictive back target.
|
|
continue;
|
|
}
|
|
if (moveToTop) {
|
|
change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
|
|
}
|
|
} else if (Flags.unifyBackNavigationTransition()
|
|
&& change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
|
|
&& change.getMode() == TRANSIT_CHANGE
|
|
&& isCloseChangeExist(info, change)) {
|
|
// This is the original top target, don't add it into current transition
|
|
// if it is closing.
|
|
continue;
|
|
}
|
|
info.getChanges().add(i, change);
|
|
}
|
|
}
|
|
} else {
|
|
// Open transition, the transition info should be:
|
|
// init info(open A & wallpaper)
|
|
// current info(open C target + close B target + close A & wallpaper)
|
|
|
|
// If close target isn't back navigated, filter out close A & wallpaper because the
|
|
// (open C + close B) pair didn't participant prepare close
|
|
boolean nonBackOpen = false;
|
|
boolean nonBackClose = false;
|
|
tmpSize = info.getChanges().size();
|
|
for (int j = 0; j < tmpSize; ++j) {
|
|
final TransitionInfo.Change change = info.getChanges().get(j);
|
|
if (!change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
|
|
&& canBeTransitionTarget(change)) {
|
|
final int mode = change.getMode();
|
|
nonBackOpen |= TransitionUtil.isOpeningMode(mode);
|
|
nonBackClose |= TransitionUtil.isClosingMode(mode);
|
|
}
|
|
}
|
|
if (nonBackClose && nonBackOpen) {
|
|
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
|
|
final TransitionInfo.Change change = info.getChanges().get(j);
|
|
if (isOpenSurfaceMatched(openSurfaces, change)) {
|
|
info.getChanges().remove(j);
|
|
} else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
|
|
info.getChanges().remove(j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation transition, merge pending "
|
|
+ "transitions result=%s", info);
|
|
// Only handle one merge transition request.
|
|
mOpenTransitionInfo = null;
|
|
}
|
|
|
|
@Override
|
|
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
|
@NonNull SurfaceControl.Transaction startT,
|
|
@NonNull SurfaceControl.Transaction finishT,
|
|
@NonNull IBinder mergeTarget,
|
|
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
|
if (mClosePrepareTransition == transition) {
|
|
mClosePrepareTransition = null;
|
|
}
|
|
// try to handle unexpected transition
|
|
if (mOpenTransitionInfo != null) {
|
|
mergePendingTransitions(info);
|
|
}
|
|
|
|
if (info.getType() == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
|
|
&& !mCloseTransitionRequested && info.getChanges().isEmpty() && mApps == null) {
|
|
finishCallback.onTransitionFinished(null);
|
|
startT.apply();
|
|
applyFinishOpenTransition();
|
|
return;
|
|
}
|
|
if (isNotGestureBackTransition(info) || shouldCancelAnimation(info)
|
|
|| !mCloseTransitionRequested) {
|
|
if (mPrepareOpenTransition != null) {
|
|
applyFinishOpenTransition();
|
|
}
|
|
return;
|
|
}
|
|
// Handle the commit transition if this handler is running the open transition.
|
|
finishCallback.onTransitionFinished(null);
|
|
startT.apply();
|
|
if (mCloseTransitionRequested) {
|
|
if (mApps == null || mApps.length == 0) {
|
|
// animation was done
|
|
applyFinishOpenTransition();
|
|
mCloseTransitionRequested = false;
|
|
} else {
|
|
// we are animating, wait until animation finish
|
|
mOnAnimationFinishCallback = () -> {
|
|
applyFinishOpenTransition();
|
|
mCloseTransitionRequested = false;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cancel close animation if something happen unexpected, let another handler to handle
|
|
private boolean shouldCancelAnimation(@NonNull TransitionInfo info) {
|
|
final boolean noCloseAllowed = !mCloseTransitionRequested
|
|
&& info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
|
|
boolean unableToHandle = false;
|
|
boolean filterTargets = false;
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
final boolean backGestureAnimated = c.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
|
|
if (!backGestureAnimated && !c.hasFlags(FLAG_IS_WALLPAPER)) {
|
|
// something we cannot handle?
|
|
unableToHandle = true;
|
|
filterTargets = true;
|
|
} else if (noCloseAllowed && backGestureAnimated
|
|
&& TransitionUtil.isClosingMode(c.getMode())) {
|
|
// Prepare back navigation shouldn't contain close change, unless top app
|
|
// request close.
|
|
unableToHandle = true;
|
|
}
|
|
}
|
|
if (!unableToHandle) {
|
|
return false;
|
|
}
|
|
if (!filterTargets) {
|
|
return true;
|
|
}
|
|
if (TransitionUtil.isOpeningType(info.getType())
|
|
|| TransitionUtil.isClosingType(info.getType())) {
|
|
boolean removeWallpaper = false;
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
// filter out opening target, keep original closing target in this transition
|
|
if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
|
|
&& TransitionUtil.isOpeningMode(c.getMode())) {
|
|
info.getChanges().remove(i);
|
|
removeWallpaper |= c.hasFlags(FLAG_SHOW_WALLPAPER);
|
|
}
|
|
}
|
|
if (removeWallpaper) {
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
if (c.hasFlags(FLAG_IS_WALLPAPER)) {
|
|
info.getChanges().remove(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check whether this transition is prepare for predictive back animation, which could
|
|
* happen when core make an activity become visible.
|
|
*/
|
|
@VisibleForTesting
|
|
boolean handlePrepareTransition(@NonNull IBinder transition,
|
|
@NonNull TransitionInfo info,
|
|
@NonNull SurfaceControl.Transaction st,
|
|
@NonNull SurfaceControl.Transaction ft,
|
|
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
|
if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
|
|
return false;
|
|
}
|
|
// Must have open target, must not have close target.
|
|
if (hasAnimationInMode(info, TransitionUtil::isClosingMode)
|
|
|| !hasAnimationInMode(info, TransitionUtil::isOpeningMode)) {
|
|
return false;
|
|
}
|
|
SurfaceControl openingLeash = null;
|
|
SurfaceControl closingLeash = null;
|
|
if (mApps != null) {
|
|
for (int i = mApps.length - 1; i >= 0; --i) {
|
|
if (mApps[i].mode == MODE_OPENING) {
|
|
openingLeash = mApps[i].leash;
|
|
} else if (mApps[i].mode == MODE_CLOSING) {
|
|
closingLeash = mApps[i].leash;
|
|
}
|
|
}
|
|
}
|
|
if (openingLeash != null && closingLeash != null) {
|
|
int rootIdx = -1;
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
if (TransitionUtil.isOpeningMode(c.getMode())) {
|
|
final Point offset = c.getEndRelOffset();
|
|
st.setPosition(c.getLeash(), offset.x, offset.y);
|
|
st.reparent(c.getLeash(), openingLeash);
|
|
st.setAlpha(c.getLeash(), 1.0f);
|
|
rootIdx = TransitionUtil.rootIndexFor(c, info);
|
|
} else if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
|
|
&& c.getMode() == TRANSIT_CHANGE) {
|
|
st.reparent(c.getLeash(), closingLeash);
|
|
}
|
|
}
|
|
// The root leash and the leash of opening target should actually in the same level,
|
|
// but since the root leash is created after opening target, it will have higher
|
|
// layer in surface flinger. Move the root leash to lower level, so it won't affect
|
|
// the playing animation.
|
|
if (rootIdx >= 0 && info.getRootCount() > 0) {
|
|
st.setLayer(info.getRoot(rootIdx).getLeash(), -1);
|
|
}
|
|
}
|
|
st.apply();
|
|
// In case other transition handler took the handleRequest before this class.
|
|
mPrepareOpenTransition = transition;
|
|
mFinishOpenTransaction = ft;
|
|
mFinishOpenTransitionCallback = finishCallback;
|
|
mOpenTransitionInfo = info;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check whether this transition is triggered from back gesture commitment.
|
|
* Reparent the transition targets to animation leashes, so the animation won't be broken.
|
|
*/
|
|
@VisibleForTesting
|
|
boolean handleCloseTransition(@NonNull TransitionInfo info,
|
|
@NonNull SurfaceControl.Transaction st,
|
|
@NonNull SurfaceControl.Transaction ft,
|
|
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
|
if (!mCloseTransitionRequested) {
|
|
return false;
|
|
}
|
|
// must have close target
|
|
if (!hasAnimationInMode(info, TransitionUtil::isClosingMode)) {
|
|
return false;
|
|
}
|
|
if (mApps == null) {
|
|
// animation is done
|
|
applyAndFinish(st, ft, finishCallback);
|
|
return true;
|
|
}
|
|
SurfaceControl openingLeash = null;
|
|
SurfaceControl closingLeash = null;
|
|
for (int i = mApps.length - 1; i >= 0; --i) {
|
|
if (mApps[i].mode == MODE_OPENING) {
|
|
openingLeash = mApps[i].leash;
|
|
}
|
|
if (mApps[i].mode == MODE_CLOSING) {
|
|
closingLeash = mApps[i].leash;
|
|
}
|
|
}
|
|
if (openingLeash != null && closingLeash != null) {
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
if (c.hasFlags(FLAG_IS_WALLPAPER)) {
|
|
st.setAlpha(c.getLeash(), 1.0f);
|
|
continue;
|
|
}
|
|
if (TransitionUtil.isOpeningMode(c.getMode())) {
|
|
final Point offset = c.getEndRelOffset();
|
|
st.setPosition(c.getLeash(), offset.x, offset.y);
|
|
st.reparent(c.getLeash(), openingLeash);
|
|
st.setAlpha(c.getLeash(), 1.0f);
|
|
} else if (TransitionUtil.isClosingMode(c.getMode())) {
|
|
st.reparent(c.getLeash(), closingLeash);
|
|
}
|
|
}
|
|
}
|
|
st.apply();
|
|
// mApps must exists
|
|
mOnAnimationFinishCallback = () -> {
|
|
ft.apply();
|
|
finishCallback.onTransitionFinished(null);
|
|
mCloseTransitionRequested = false;
|
|
};
|
|
return true;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public WindowContainerTransaction handleRequest(
|
|
@NonNull IBinder transition,
|
|
@NonNull TransitionRequestInfo request) {
|
|
final int type = request.getType();
|
|
if (type == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
|
|
mPrepareOpenTransition = transition;
|
|
return new WindowContainerTransaction();
|
|
}
|
|
if (type == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
|
|
return new WindowContainerTransaction();
|
|
}
|
|
if (TransitionUtil.isClosingType(request.getType()) && mCloseTransitionRequested) {
|
|
return new WindowContainerTransaction();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static boolean checkTakeoverFlags() {
|
|
return TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()
|
|
&& Flags.unifyBackNavigationTransition();
|
|
}
|
|
}
|
|
|
|
private static boolean isNotGestureBackTransition(@NonNull TransitionInfo info) {
|
|
return !hasAnimationInMode(info, TransitionUtil::isOpenOrCloseMode);
|
|
}
|
|
|
|
private static boolean hasAnimationInMode(@NonNull TransitionInfo info,
|
|
Predicate<Integer> mode) {
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED) && mode.test(c.getMode())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static WindowContainerToken findToken(TransitionInfo.Change change) {
|
|
return change.getContainer();
|
|
}
|
|
|
|
private static ComponentName findComponentName(TransitionInfo.Change change) {
|
|
final ComponentName componentName = change.getActivityComponent();
|
|
if (componentName != null) {
|
|
return componentName;
|
|
}
|
|
final TaskInfo taskInfo = change.getTaskInfo();
|
|
if (taskInfo != null) {
|
|
return taskInfo.topActivity;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static int findTaskId(TransitionInfo.Change change) {
|
|
final TaskInfo taskInfo = change.getTaskInfo();
|
|
if (taskInfo != null) {
|
|
return taskInfo.taskId;
|
|
}
|
|
return INVALID_TASK_ID;
|
|
}
|
|
|
|
static boolean isOpenSurfaceMatched(@NonNull ArrayList<SurfaceControl> openSurfaces,
|
|
TransitionInfo.Change change) {
|
|
for (int i = openSurfaces.size() - 1; i >= 0; --i) {
|
|
if (openSurfaces.get(i).isSameSurface(change.getLeash())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
|
|
return findComponentName(change) != null || findTaskId(change) != INVALID_TASK_ID;
|
|
}
|
|
|
|
private static boolean isCloseChangeExist(TransitionInfo info, TransitionInfo.Change change) {
|
|
for (int j = info.getChanges().size() - 1; j >= 0; --j) {
|
|
final TransitionInfo.Change current = info.getChanges().get(j);
|
|
if (TransitionUtil.isClosingMode(current.getMode())
|
|
&& change.getLeash().isSameSurface(current.getLeash())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Record the latest back gesture happen on which task.
|
|
static class BackTransitionObserver implements Transitions.TransitionObserver {
|
|
int mFocusedTaskId = INVALID_TASK_ID;
|
|
IBinder mFocusTaskMonitorToken;
|
|
private BackTransitionHandler mBackTransitionHandler;
|
|
void setBackTransitionHandler(BackTransitionHandler handler) {
|
|
mBackTransitionHandler = handler;
|
|
}
|
|
|
|
void update(int focusedTaskId) {
|
|
mFocusedTaskId = focusedTaskId;
|
|
}
|
|
|
|
@Override
|
|
public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
|
@NonNull SurfaceControl.Transaction startTransaction,
|
|
@NonNull SurfaceControl.Transaction finishTransaction) {
|
|
if (mFocusedTaskId == INVALID_TASK_ID) {
|
|
return;
|
|
}
|
|
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
|
final TransitionInfo.Change c = info.getChanges().get(i);
|
|
if (c.getTaskInfo() != null && c.getTaskInfo().taskId == mFocusedTaskId) {
|
|
mFocusTaskMonitorToken = transition;
|
|
break;
|
|
}
|
|
}
|
|
// Transition happen but the task isn't involved, reset.
|
|
if (mFocusTaskMonitorToken == null) {
|
|
mFocusedTaskId = INVALID_TASK_ID;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
|
|
if (mFocusTaskMonitorToken == merged) {
|
|
mFocusTaskMonitorToken = playing;
|
|
}
|
|
if (mBackTransitionHandler.mClosePrepareTransition == merged) {
|
|
mBackTransitionHandler.mClosePrepareTransition = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
|
|
if (mFocusTaskMonitorToken == transition) {
|
|
mFocusedTaskId = INVALID_TASK_ID;
|
|
}
|
|
if (mBackTransitionHandler.mClosePrepareTransition == transition) {
|
|
mBackTransitionHandler.mClosePrepareTransition = null;
|
|
}
|
|
}
|
|
}
|
|
}
|