blob: 3968b525f11dc6b08a8c54fa59f2571128b5ec68 [file] [log] [blame] [edit]
/*
* 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.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
import static com.android.server.wm.BackNavigationProto.ANIMATION_RUNNING;
import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE;
import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY;
import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.window.BackAnimationAdapter;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IWindowlessStartingSurfaceCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.SystemOverrideOnBackInvokedCallback;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
/**
* Controller to handle actions related to the back gesture on the server side.
*/
class BackNavigationController {
private static final String TAG = "CoreBackPreview";
private WindowManagerService mWindowManagerService;
private boolean mBackAnimationInProgress;
private @BackNavigationInfo.BackTargetType int mLastBackType;
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
private AnimationHandler mAnimationHandler;
private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
// This will be set if the back navigation is in progress and the current transition is still
// running. The pending animation builder will do the animation stuff includes creating leashes,
// re-parenting leashes and set launch behind, etc. Will be handled when transition finished.
private AnimationHandler.ScheduleAnimationBuilder mPendingAnimationBuilder;
private static int sDefaultAnimationResId;
/**
* true if the back predictability feature is enabled
*/
static final boolean sPredictBackEnable =
SystemProperties.getBoolean("persist.wm.debug.predictive_back", true);
// Notify focus window changed
void onFocusChanged(WindowState newFocus) {
mNavigationMonitor.onFocusWindowChanged(newFocus);
}
void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
if (Flags.disallowAppProgressEmbeddedWindow()) {
mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
}
}
/**
* Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
* back gesture animation.
*
* @return a {@link BackNavigationInfo} instance containing the required leashes and metadata
* for the animation, or null if we don't know how to animate the current window and need to
* fallback on dispatching the key event.
*/
@VisibleForTesting
@Nullable
BackNavigationInfo startBackNavigation(@NonNull RemoteCallback navigationObserver,
BackAnimationAdapter adapter) {
if (!sPredictBackEnable) {
return null;
}
final WindowManagerService wmService = mWindowManagerService;
int backType = BackNavigationInfo.TYPE_UNDEFINED;
// The currently visible activity (if any).
ActivityRecord currentActivity = null;
// The currently visible task (if any).
Task currentTask = null;
// The previous task we're going back to. Can be the same as currentTask, if there are
// multiple Activities in the Stack.
Task prevTask = null;
WindowContainer<?> removedWindowContainer = null;
WindowState window;
BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
synchronized (wmService.mGlobalLock) {
if (isMonitoringFinishTransition()) {
Slog.w(TAG, "Previous animation hasn't finish, status: " + mAnimationHandler);
// Don't start any animation for it.
return null;
}
window = wmService.getFocusedWindowLocked();
if (window == null) {
// We don't have any focused window, fallback ont the top currentTask of the focused
// display.
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
"No focused window, defaulting to top current task's window");
currentTask = wmService.mAtmService.getTopDisplayFocusedRootTask();
window = currentTask != null
? currentTask.getWindow(WindowState::isFocused) : null;
}
if (window == null) {
Slog.e(TAG, "Window is null, returning null.");
return null;
}
// Updating the window to the most recently used one among the embedded windows
// that are displayed adjacently, unless the IME is visible.
// When the IME is visible, the IME is displayed on top of embedded activities.
// In that case, the back event should still be delivered to focused activity in
// order to dismiss the IME.
if (!window.getDisplayContent().getImeContainer().isVisible()) {
window = mWindowManagerService.getMostRecentUsedEmbeddedWindowForBack(window);
}
if (!window.isDrawn()) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Focused window didn't have a valid surface drawn.");
return null;
}
final ArrayList<EmbeddedWindowController.EmbeddedWindow> embeddedWindows = wmService
.mEmbeddedWindowController.getByHostWindow(window);
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
if ((currentTask != null && !currentTask.isVisibleRequested())
|| (currentActivity != null && !currentActivity.isVisibleRequested())) {
// Closing transition is happening on focus window and should be update soon,
// don't drive back navigation with it.
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focus window is closing.");
return null;
}
// Now let's find if this window has a callback from the client side.
final OnBackInvokedCallbackInfo callbackInfo = window.getOnBackInvokedCallbackInfo();
if (callbackInfo == null) {
Slog.e(TAG, "No callback registered, returning null.");
return null;
}
if (!callbackInfo.isSystemCallback()) {
backType = BackNavigationInfo.TYPE_CALLBACK;
}
infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback());
infoBuilder.setTouchableRegion(window.getFrame());
if (currentTask != null) {
infoBuilder.setFocusedTaskId(currentTask.mTaskId);
}
boolean transferGestureToEmbedded = false;
if (Flags.disallowAppProgressEmbeddedWindow() && embeddedWindows != null) {
for (int i = embeddedWindows.size() - 1; i >= 0; --i) {
if (embeddedWindows.get(i).mGestureToEmbedded) {
transferGestureToEmbedded = true;
break;
}
}
}
final boolean canInterruptInView = (window.getAttrs().privateFlags
& PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED) != 0;
infoBuilder.setAppProgressAllowed(canInterruptInView && !transferGestureToEmbedded
&& callbackInfo.isAnimationCallback());
mNavigationMonitor.startMonitor(window, navigationObserver);
int requestOverride = callbackInfo.getOverrideBehavior();
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
+ "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
currentTask, currentActivity, callbackInfo, window);
if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) {
final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity()
: null;
if (currentActivity != null && rootR != currentActivity) {
// The top activity is not root activity, the activity cannot remove task when
// finishAndRemoveTask called.
requestOverride = OVERRIDE_UNDEFINED;
}
}
// Clear the pointer down outside focus if any.
mWindowManagerService.clearPointerDownOutsideFocusRunnable();
// If we don't need to set up the animation, we return early. This is the case when
// - We have an application callback.
// - We don't have any ActivityRecord or Task to animate.
// - The IME is opened, and we just need to close it.
// - The home activity is the focused activity & it's not TYPE_BASE_APPLICATION
// - The current activity will do shared element transition when exiting.
if (backType == BackNavigationInfo.TYPE_CALLBACK
|| currentActivity == null
|| currentTask == null
|| (currentActivity.isActivityTypeHome()
&& window.mAttrs.type == TYPE_BASE_APPLICATION)
|| currentActivity.mHasSceneTransition) {
infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
onBackNavigationDone(result, BackNavigationInfo.TYPE_CALLBACK)));
mLastBackType = BackNavigationInfo.TYPE_CALLBACK;
return infoBuilder.build();
}
// The previous activity we're going back to. This can be either a child of currentTask
// if there are more than one Activity in currentTask, or a child of prevTask, if
// currentActivity is the last child of currentTask.
// We don't have an application callback, let's find the destination of the back gesture
// The search logic should align with ActivityClientController#finishActivity
final ArrayList<ActivityRecord> prevActivities = new ArrayList<>();
final boolean canAnimate = getAnimatablePrevActivities(currentTask, currentActivity,
prevActivities);
final boolean isOccluded = isKeyguardOccluded(window);
if (!canAnimate) {
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if ((window.getParent().getChildCount() > 1
&& window.getParent().getChildAt(0) != window)) {
// TODO Dialog window does not need to attach on activity, check
// window.mAttrs.type != TYPE_BASE_APPLICATION
// Are we the top window of our parent? If not, we are a window on top of the
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
} else if (hasTranslucentActivity(currentActivity, prevActivities)) {
// skip if one of participant activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevActivities.size() > 0
&& requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
&& isAllActivitiesCreated(prevActivities)) {
// We have another Activity in the same currentTask to go to
final WindowContainer parent = currentActivity.getParent();
final boolean canCustomize = parent != null
&& (parent.asTask() != null
|| (parent.asTaskFragment() != null
&& parent.canCustomizeAppTransition()));
if (canCustomize) {
if (isCustomizeExitAnimation(window)) {
infoBuilder.setWindowAnimations(
window.mAttrs.packageName, window.mAttrs.windowAnimations);
}
final ActivityRecord.CustomAppTransition customAppTransition =
currentActivity.getCustomAnimation(false/* open */);
if (customAppTransition != null) {
infoBuilder.setCustomAnimation(currentActivity.packageName,
customAppTransition.mEnterAnim,
customAppTransition.mExitAnim,
customAppTransition.mBackgroundColor);
}
}
infoBuilder.setLetterboxColor(currentActivity.mAppCompatController
.getAppCompatLetterboxOverrides()
.getLetterboxBackgroundColor().toArgb());
removedWindowContainer = currentActivity;
prevTask = prevActivities.get(0).getTask();
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
} else {
// keyguard locked and activities are unable to show when locked.
backType = BackNavigationInfo.TYPE_CALLBACK;
}
} else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)
|| currentTask.getWindowConfiguration().tasksAreFloating()) {
// Do not predict if current task is in task locked.
// Also, it is unable to play cross task animation for floating task.
backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
// Check back-to-home or cross-task
prevTask = currentTask.mRootWindowContainer.getTask(t -> {
if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
final ActivityRecord ar = t.getTopNonFinishingActivity();
return ar != null && ar.showToCurrentUser();
}
return false;
}, currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
final ActivityRecord tmpPre = prevTask != null
? prevTask.getTopNonFinishingActivity() : null;
if (tmpPre != null) {
prevActivities.add(tmpPre);
findAdjacentActivityIfExist(tmpPre, prevActivities);
}
if (prevTask == null || prevActivities.isEmpty()
|| (isOccluded && !isAllActivitiesCanShowWhenLocked(prevActivities))) {
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevTask.isActivityTypeHome()) {
removedWindowContainer = currentTask;
prevTask = prevTask.getRootTask();
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
final ActivityRecord ar = prevTask.getTopNonFinishingActivity();
mShowWallpaper = ar != null && ar.hasWallpaper();
} else {
// If it reaches the top activity, we will check the below task from parent.
// If it's null or multi-window and has different parent task, fallback the type
// to TYPE_CALLBACK. Or set the type to proper value when it's return to home or
// another task.
final Task prevParent = prevTask.getParent().asTask();
final Task currParent = currentTask.getParent().asTask();
if ((prevTask.inMultiWindowMode() && prevParent != currParent)
// Do not animate to translucent task, it could be trampoline.
|| hasTranslucentActivity(currentActivity, prevActivities)) {
backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
removedWindowContainer = prevTask;
backType = BackNavigationInfo.TYPE_CROSS_TASK;
}
}
}
infoBuilder.setType(backType);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s "
+ "removedContainer:%s, backType=%s",
prevActivities.size() > 0 ? TextUtils.join(";", prevActivities.stream()
.map(r -> r.mActivityComponent).toArray()) : null,
prevTask != null ? prevTask.getName() : null,
removedWindowContainer,
BackNavigationInfo.typeToString(backType));
boolean prepareAnimation =
(backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
|| backType == BackNavigationInfo.TYPE_CROSS_TASK
|| backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY
|| backType == BackNavigationInfo.TYPE_DIALOG_CLOSE)
&& (adapter != null && adapter.isAnimatable(backType));
if (prepareAnimation) {
final AnimationHandler.ScheduleAnimationBuilder builder =
mAnimationHandler.prepareAnimation(
backType,
adapter,
mNavigationMonitor,
currentTask,
prevTask,
currentActivity,
prevActivities,
removedWindowContainer);
mBackAnimationInProgress = builder != null;
if (mBackAnimationInProgress) {
if (removedWindowContainer.mTransitionController.inTransition()
|| mWindowManagerService.mSyncEngine.hasPendingSyncSets()) {
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
"Pending back animation due to another animation is running");
mPendingAnimationBuilder = builder;
// Current transition is still running, we have to defer the hiding to the
// client process to prevent the unexpected relayout when handling the back
// animation.
for (int i = prevActivities.size() - 1; i >= 0; --i) {
prevActivities.get(i).setDeferHidingClient();
}
} else {
scheduleAnimation(builder);
}
}
}
infoBuilder.setPrepareRemoteAnimation(prepareAnimation);
if (removedWindowContainer != null) {
final int finalBackType = backType;
final RemoteCallback onBackNavigationDone = new RemoteCallback(result ->
onBackNavigationDone(result, finalBackType));
infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
} else {
mNavigationMonitor.stopMonitorForRemote();
}
mLastBackType = backType;
return infoBuilder.build();
}
}
/**
* Gets previous activities from currentActivity.
*
* @return false if unable to predict what will happen
*/
@VisibleForTesting
static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
@NonNull ActivityRecord currentActivity,
@NonNull ArrayList<ActivityRecord> outPrevActivities) {
if (currentActivity.mAtmService
.mTaskOrganizerController.shouldInterceptBackPressedOnRootTask(
currentTask.getRootTask())) {
// The task organizer will handle back pressed, don't play animation.
return false;
}
final ActivityRecord root = currentTask.getRootActivity(false /*ignoreRelinquishIdentity*/,
true /*setToBottomIfNone*/);
if (root != null && ActivityClientController.shouldMoveTaskToBack(currentActivity, root)) {
return true;
}
// Searching previous
final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing,
currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/);
final TaskFragment currTF = currentActivity.getTaskFragment();
if (currTF != null && currTF.asTask() == null) {
// The currentActivity is embedded, search for the candidate previous activities.
if (prevActivity != null && currTF.hasChild(prevActivity)) {
// PrevActivity is under the same task fragment, that's it.
outPrevActivities.add(prevActivity);
return true;
}
if (currTF.getAdjacentTaskFragment() == null) {
final TaskFragment nextTF = findNextTaskFragment(currentTask, currTF);
if (isSecondCompanionToFirst(currTF, nextTF)) {
// TF is isStacked, search bottom activity from companion TF.
//
// Sample hierarchy: search for underPrevious if any.
// Current TF
// Companion TF (bottomActivityInCompanion)
// Bottom Activity not inside companion TF (underPrevious)
// find bottom activity in Companion TF.
final ActivityRecord bottomActivityInCompanion = nextTF.getActivity(
(below) -> !below.finishing, false /* traverseTopToBottom */);
final ActivityRecord underPrevious = currentTask.getActivity(
(below) -> !below.finishing, bottomActivityInCompanion,
false /*includeBoundary*/, true /*traverseTopToBottom*/);
if (underPrevious != null) {
outPrevActivities.add(underPrevious);
addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities);
}
return true;
}
} else {
// If adjacent TF has companion to current TF, those two TF will be closed together.
final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment();
if (isSecondCompanionToFirst(currTF, adjacentTF)) {
// The two TFs are adjacent (visually displayed side-by-side), search if any
// activity below the lowest one.
final WindowContainer commonParent = currTF.getParent();
final TaskFragment lowerTF = commonParent.mChildren.indexOf(currTF)
< commonParent.mChildren.indexOf(adjacentTF)
? currTF : adjacentTF;
final ActivityRecord lowerActivity = lowerTF.getTopNonFinishingActivity();
// TODO (b/274997067) close currTF + companionTF, open next activities if any.
// Allow to predict next task if no more activity in task. Or return previous
// activities for cross-activity animation.
return currentTask.getActivity((below) -> !below.finishing, lowerActivity,
false /*includeBoundary*/, true /*traverseTopToBottom*/) == null;
}
// Unable to predict if no companion, it can only close current activity and make
// prev Activity full screened.
return false;
}
}
if (prevActivity == null) {
// No previous activity in this Task nor TaskFragment, it can still predict if previous
// task exists.
return true;
}
// Add possible adjacent activity if prevActivity is embedded
addPreviousAdjacentActivityIfExist(prevActivity, outPrevActivities);
outPrevActivities.add(prevActivity);
return true;
}
private static TaskFragment findNextTaskFragment(@NonNull Task currentTask,
@NonNull TaskFragment topTF) {
final int topIndex = currentTask.mChildren.indexOf(topTF);
if (topIndex <= 0) {
return null;
}
final WindowContainer next = currentTask.mChildren.get(topIndex - 1);
return next.asTaskFragment();
}
/**
* Whether the second TF has set companion to first TF.
* When set, the second TF will be removed by organizer if the first TF is removed.
*/
private static boolean isSecondCompanionToFirst(TaskFragment first, TaskFragment second) {
return second != null && second.getCompanionTaskFragment() == first;
}
private static void addPreviousAdjacentActivityIfExist(@NonNull ActivityRecord prevActivity,
@NonNull ArrayList<ActivityRecord> outPrevActivities) {
final TaskFragment prevTF = prevActivity.getTaskFragment();
if (prevTF == null || prevTF.asTask() != null) {
return;
}
final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
if (prevTFAdjacent == null || prevTFAdjacent.asTask() != null) {
return;
}
final ActivityRecord prevActivityAdjacent =
prevTFAdjacent.getTopNonFinishingActivity();
if (prevActivityAdjacent != null) {
outPrevActivities.add(prevActivityAdjacent);
}
}
private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity,
@NonNull ArrayList<ActivityRecord> outList) {
final TaskFragment mainTF = mainActivity.getTaskFragment();
if (mainTF == null || mainTF.getAdjacentTaskFragment() == null) {
return;
}
final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment();
final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity();
if (topActivity == null) {
return;
}
outList.add(topActivity);
}
private static boolean hasTranslucentActivity(@NonNull ActivityRecord currentActivity,
@NonNull ArrayList<ActivityRecord> prevActivities) {
if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
return true;
}
for (int i = prevActivities.size() - 1; i >= 0; --i) {
final ActivityRecord test = prevActivities.get(i);
if (!test.occludesParent() || test.hasWallpaper()) {
return true;
}
}
return false;
}
private static boolean isAllActivitiesCanShowWhenLocked(
@NonNull ArrayList<ActivityRecord> prevActivities) {
for (int i = prevActivities.size() - 1; i >= 0; --i) {
if (!prevActivities.get(i).canShowWhenLocked()) {
return false;
}
}
return !prevActivities.isEmpty();
}
private static boolean isAllActivitiesCreated(
@NonNull ArrayList<ActivityRecord> prevActivities) {
for (int i = prevActivities.size() - 1; i >= 0; --i) {
final ActivityRecord check = prevActivities.get(i);
if (check.isState(ActivityRecord.State.INITIALIZING)) {
return false;
}
}
return !prevActivities.isEmpty();
}
boolean isMonitoringFinishTransition() {
return mAnimationHandler.mComposed || mNavigationMonitor.isMonitorForRemote();
}
boolean isMonitoringPrepareTransition(Transition transition) {
return mAnimationHandler.mComposed
&& mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == transition;
}
private void scheduleAnimation(@NonNull AnimationHandler.ScheduleAnimationBuilder builder) {
mPendingAnimation = builder.build();
if (mAnimationHandler.mOpenAnimAdaptor != null
&& mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null) {
startAnimation();
} else {
mWindowManagerService.mWindowPlacerLocked.requestTraversal();
if (mShowWallpaper) {
mWindowManagerService.getDefaultDisplayContentLocked().mWallpaperController
.adjustWallpaperWindows();
}
}
}
boolean hasFixedRotationAnimation(@NonNull DisplayContent displayContent) {
if (!mAnimationHandler.mComposed) {
return false;
}
final ActivityRecord openActivity = mAnimationHandler.mOpenActivities[0];
return displayContent == openActivity.mDisplayContent
&& displayContent.isFixedRotationLaunchingApp(openActivity);
}
private boolean isWaitBackTransition() {
// Ignore mWaitTransition while flag is enabled.
return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition()
|| mAnimationHandler.mWaitTransition);
}
boolean isKeyguardOccluded(WindowState focusWindow) {
final KeyguardController kc = mWindowManagerService.mAtmService.mKeyguardController;
final int displayId = focusWindow.getDisplayId();
return kc.isKeyguardOccluded(displayId);
}
/**
* There are two ways to customize activity exit animation, one is to provide the
* windowAnimationStyle by Activity#setTheme, another one is to set resId by
* Window#setWindowAnimations.
* Not all run-time customization methods can be checked from here, such as
* overridePendingTransition, which the animation resource will be set just before the
* transition is about to happen.
*/
private static boolean isCustomizeExitAnimation(WindowState window) {
// The default animation ResId is loaded from system package, so the result must match.
if (Objects.equals(window.mAttrs.packageName, "android")) {
return false;
}
if (window.mAttrs.windowAnimations != 0) {
final TransitionAnimation transitionAnimation = window.getDisplayContent()
.mAppTransition.mTransitionAnimation;
final int attr = com.android.internal.R.styleable
.WindowAnimation_activityCloseExitAnimation;
final int appResId = transitionAnimation.getAnimationResId(
window.mAttrs, attr, TRANSIT_OLD_NONE);
if (ResourceId.isValid(appResId)) {
if (sDefaultAnimationResId == 0) {
sDefaultAnimationResId = transitionAnimation.getDefaultAnimationResId(attr,
TRANSIT_OLD_NONE);
}
return sDefaultAnimationResId != appResId;
}
}
return false;
}
// For legacy transition.
/**
* Once we find the transition targets match back animation targets, remove the target from
* list, so that transition won't count them in since the close animation was finished.
*
* @return {@code true} if the participants of this transition was animated by back gesture
* animations, and shouldn't join next transition.
*/
boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
ArraySet<ActivityRecord> closeApps) {
if (!isMonitoringFinishTransition()) {
return false;
}
mTmpCloseApps.addAll(closeApps);
final boolean matchAnimationTargets = removeIfWaitForBackTransition(openApps, closeApps);
if (!matchAnimationTargets) {
mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
}
mTmpCloseApps.clear();
return matchAnimationTargets;
}
boolean removeIfWaitForBackTransition(ArraySet<ActivityRecord> openApps,
ArraySet<ActivityRecord> closeApps) {
if (!isWaitBackTransition()) {
return false;
}
// Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
// mOpeningApps if there is no visibility change.
if (mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
// remove close target from close list, open target from open list;
// but the open target can be in close list.
for (int i = openApps.size() - 1; i >= 0; --i) {
final ActivityRecord ar = openApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, true /* open */)) {
openApps.removeAt(i);
}
}
for (int i = closeApps.size() - 1; i >= 0; --i) {
final ActivityRecord ar = closeApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, false /* open */)) {
closeApps.removeAt(i);
}
}
return true;
}
return false;
}
void removePredictiveSurfaceIfNeeded(ActivityRecord openActivity) {
mAnimationHandler.markWindowHasDrawn(openActivity);
}
boolean isStartingSurfaceShown(ActivityRecord openActivity) {
if (!Flags.migratePredictiveBackTransition()) {
return false;
}
return mAnimationHandler.isStartingSurfaceDrawn(openActivity);
}
@VisibleForTesting
class NavigationMonitor {
// The window which triggering the back navigation.
private WindowState mNavigatingWindow;
private RemoteCallback mObserver;
void startMonitor(@NonNull WindowState window, @NonNull RemoteCallback observer) {
mNavigatingWindow = window;
mObserver = observer;
}
void stopMonitorForRemote() {
mObserver = null;
}
void stopMonitorTransition() {
mNavigatingWindow = null;
}
boolean isMonitorForRemote() {
return mNavigatingWindow != null && mObserver != null;
}
boolean isMonitorAnimationOrTransition() {
return mNavigatingWindow != null
&& (mAnimationHandler.mComposed || mAnimationHandler.mWaitTransition);
}
/**
* Notify focus window changed during back navigation. This will cancel the gesture for
* scenarios like: a system window popup, or when an activity add a new window.
*
* This method should only be used to check window-level change, otherwise it may cause
* misjudgment in multi-window mode. For example: in split-screen, when user is
* navigating on the top task, bottom task can start a new task, which will gain focus for
* a short time, but we should not cancel the navigation.
*/
private void onFocusWindowChanged(WindowState newFocus) {
if (!atSameDisplay(newFocus)
|| !(isMonitorForRemote() || isMonitorAnimationOrTransition())) {
return;
}
// Keep navigating if either new focus == navigating window or null.
if (newFocus != null && newFocus != mNavigatingWindow
&& (newFocus.mActivityRecord == null
|| (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
cancelBackNavigating("focusWindowChanged");
}
}
/**
* Notify focus window has transferred touch gesture to embedded window. Shell should pilfer
* pointers so embedded process won't receive motion event.
*
*/
void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
if (!isMonitorForRemote() || host != mNavigatingWindow) {
return;
}
final Bundle result = new Bundle();
result.putBoolean(BackNavigationInfo.KEY_TOUCH_GESTURE_TRANSFERRED, true);
mObserver.sendResult(result);
}
/**
* Notify an unexpected transition has happened during back navigation.
*/
private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening,
ArrayList<WindowContainer> closing) {
if (!isMonitorForRemote() && !isMonitorAnimationOrTransition()) {
return;
}
final ArrayList<WindowContainer> all = new ArrayList<>(opening);
all.addAll(closing);
for (int i = all.size() - 1; i >= 0; --i) {
if (all.get(i).hasChild(mNavigatingWindow)) {
cancelBackNavigating("transitionHappens");
break;
}
}
}
private boolean atSameDisplay(WindowState newFocus) {
if (mNavigatingWindow == null) {
return false;
}
final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
}
private void cancelBackNavigating(String reason) {
EventLogTags.writeWmBackNaviCanceled(reason);
if (isMonitorForRemote()) {
mObserver.sendResult(null /* result */);
}
if (isMonitorAnimationOrTransition() && canCancelAnimations()) {
clearBackAnimations(true /* cancel */);
}
cancelPendingAnimation();
}
}
void onAppVisibilityChanged(@NonNull ActivityRecord ar, boolean visible) {
if (!mAnimationHandler.mComposed) {
return;
}
final boolean openingTransition = mAnimationHandler.mOpenAnimAdaptor
.mPreparedOpenTransition != null;
// Detect if another transition is collecting during predictive back animation.
if (openingTransition && !visible && mAnimationHandler.isTarget(ar, false /* open */)
&& ar.mTransitionController.isCollecting(ar)) {
final TransitionController controller = ar.mTransitionController;
boolean collectTask = false;
ActivityRecord changedActivity = null;
for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
final ActivityRecord next = mAnimationHandler.mOpenActivities[i];
if (next.mLaunchTaskBehind) {
// collect previous activity, so shell side can handle the transition.
controller.collect(next);
collectTask = true;
restoreLaunchBehind(next, true /* cancel */, false /* finishTransition */);
changedActivity = next;
}
}
if (collectTask && mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType
== AnimationHandler.TASK_SWITCH) {
final Task topTask = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].getTopTask();
if (topTask != null) {
WindowContainer parent = mAnimationHandler.mOpenActivities[0].getParent();
while (parent != topTask && parent.isDescendantOf(topTask)) {
controller.collect(parent);
parent = parent.getParent();
}
controller.collect(topTask);
}
}
if (changedActivity != null) {
changedActivity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
true /* notifyClients */);
}
}
}
// For shell transition
/**
* Check whether the transition targets was animated by back gesture animation.
* Because the opening target could request to do other stuff at onResume, so it could become
* close target for a transition. So the condition here is
* The closing target should only exist in close list, but the opening target can be either in
* open or close list.
*/
void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
if (isMonitoringPrepareTransition(transition)) {
// Flag target matches and prepare to remove windowless surface.
mAnimationHandler.markStartingSurfaceMatch(startTransaction);
return;
}
if (targets.isEmpty()) {
return;
}
final boolean migratePredictToTransition = Flags.migratePredictiveBackTransition();
if (migratePredictToTransition && !mAnimationHandler.mComposed) {
return;
} else if (!isMonitoringFinishTransition()) {
return;
}
if (mAnimationHandler.hasTargetDetached()) {
mNavigationMonitor.cancelBackNavigating("targetDetached");
return;
}
for (int i = targets.size() - 1; i >= 0; --i) {
final WindowContainer wc = targets.get(i).mContainer;
if (wc.asActivityRecord() == null && wc.asTask() == null
&& wc.asTaskFragment() == null) {
continue;
}
// Only care if visibility changed.
if (targets.get(i).getTransitMode(wc) == TRANSIT_CHANGE) {
continue;
}
// WC can be visible due to setLaunchBehind
if (wc.isVisibleRequested()) {
mTmpOpenApps.add(wc);
} else {
mTmpCloseApps.add(wc);
}
}
final boolean matchAnimationTargets;
if (migratePredictToTransition) {
matchAnimationTargets =
mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
} else {
matchAnimationTargets = isWaitBackTransition()
&& (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
&& mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
// Don't cancel transition, let transition handler to handle it
if (!matchAnimationTargets && !migratePredictToTransition) {
mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
} else {
if (mAnimationHandler.mPrepareCloseTransition != null) {
Slog.e(TAG, "Gesture animation is applied on another transition?");
return;
}
mAnimationHandler.mPrepareCloseTransition = transition;
if (!migratePredictToTransition) {
// Because the target will reparent to transition root, so it cannot be controlled
// by animation leash. Hide the close target when transition starts.
startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
}
// Flag target matches and prepare to remove windowless surface.
mAnimationHandler.markStartingSurfaceMatch(startTransaction);
// release animation leash
if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) {
finishTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null;
}
}
mTmpOpenApps.clear();
mTmpCloseApps.clear();
}
boolean isMonitorTransitionTarget(WindowContainer wc) {
if (Flags.migratePredictiveBackTransition()) {
if (!mAnimationHandler.mComposed) {
return false;
}
if (mAnimationHandler.mSwitchType == AnimationHandler.TASK_SWITCH
&& wc.asActivityRecord() != null
|| (mAnimationHandler.mSwitchType == AnimationHandler.ACTIVITY_SWITCH
&& wc.asTask() != null)) {
return false;
}
return (mAnimationHandler.isTarget(wc, true /* open */)
|| mAnimationHandler.isTarget(wc, false /* open */));
} else if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
|| (mAnimationHandler.mOpenAnimAdaptor != null
&& mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null)) {
return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
return false;
}
boolean shouldPauseTouch(WindowContainer wc) {
// Once the close transition is ready, it means the onBackInvoked callback has invoked, and
// app is ready to trigger next transition, no matter what it will be.
return mAnimationHandler.mComposed && mAnimationHandler.mPrepareCloseTransition == null
&& mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
/**
* Cleanup animation, this can either happen when legacy transition ready, or when the Shell
* transition finish.
*/
void clearBackAnimations(boolean cancel) {
mAnimationHandler.clearBackAnimateTarget(cancel);
mNavigationMonitor.stopMonitorTransition();
}
/**
* Handle the pending animation when the running transition finished, all the visibility change
* has applied so ready to start pending predictive back animation.
* @param targets The final animation targets derived in transition.
* @param finishedTransition The finished transition target.
*/
void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
@NonNull Transition finishedTransition) {
if (isMonitoringPrepareTransition(finishedTransition)) {
if (mAnimationHandler.mPrepareCloseTransition == null) {
clearBackAnimations(true /* cancel */);
}
return;
}
if (finishedTransition == mAnimationHandler.mPrepareCloseTransition) {
clearBackAnimations(false /* cancel */);
}
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
return;
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Handling the deferred animation after transition finished");
// Find the participated container collected by transition when :
// Open transition -> the open target in back navigation, the close target in transition.
// Close transition -> the close target in back navigation, the open target in transition.
boolean hasTarget = false;
for (int i = 0; i < finishedTransition.mParticipants.size(); i++) {
final WindowContainer wc = finishedTransition.mParticipants.valueAt(i);
if (wc.asActivityRecord() == null && wc.asTask() == null
&& wc.asTaskFragment() == null) {
continue;
}
if (mPendingAnimationBuilder.containTarget(wc)) {
hasTarget = true;
break;
}
}
if (!hasTarget) {
// Skip if no target participated in current finished transition.
Slog.w(TAG, "Finished transition didn't include the targets"
+ " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
+ " close: " + mPendingAnimationBuilder.mCloseTarget);
cancelPendingAnimation();
return;
}
if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
cancelPendingAnimation();
return;
}
// Ensure the final animation targets which hidden by transition could be visible.
for (int i = 0; i < targets.size(); i++) {
final WindowContainer wc = targets.get(i).mContainer;
if (wc.mSurfaceControl != null) {
wc.prepareSurfaces();
}
}
// The pending builder could be cleared due to prepareSurfaces
// => updateNonSystemOverlayWindowsVisibilityIfNeeded
// => setForceHideNonSystemOverlayWindowIfNeeded
// => updateFocusedWindowLocked => onFocusWindowChanged.
if (mPendingAnimationBuilder != null) {
scheduleAnimation(mPendingAnimationBuilder);
mPendingAnimationBuilder = null;
}
}
private void cancelPendingAnimation() {
if (mPendingAnimationBuilder == null) {
return;
}
try {
mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
} catch (RemoteException e) {
Slog.e(TAG, "Remote animation gone", e);
}
mPendingAnimationBuilder = null;
mNavigationMonitor.stopMonitorTransition();
}
/**
* Create and handling animations status for an open/close animation targets.
*/
static class AnimationHandler {
private final boolean mShowWindowlessSurface;
private final WindowManagerService mWindowManagerService;
private BackWindowAnimationAdaptor mCloseAdaptor;
private BackWindowAnimationAdaptorWrapper mOpenAnimAdaptor;
private boolean mComposed;
private boolean mWaitTransition;
private int mSwitchType = UNKNOWN;
// This will be set before transition happen, to know whether the real opening target
// exactly match animating target. When target match, reparent the starting surface to
// the opening target like starting window do.
private boolean mStartingSurfaceTargetMatch;
private ActivityRecord[] mOpenActivities;
Transition mPrepareCloseTransition;
AnimationHandler(WindowManagerService wms) {
mWindowManagerService = wms;
final Context context = wms.mContext;
mShowWindowlessSurface = context.getResources().getBoolean(
com.android.internal.R.bool.config_predictShowStartingSurface);
}
private static final int UNKNOWN = 0;
private static final int TASK_SWITCH = 1;
private static final int ACTIVITY_SWITCH = 2;
private static final int DIALOG_CLOSE = 3;
private static boolean isActivitySwitch(@NonNull WindowContainer close,
@NonNull WindowContainer[] open) {
if (open == null || open.length == 0 || close.asActivityRecord() == null) {
return false;
}
final Task closeTask = close.asActivityRecord().getTask();
for (int i = open.length - 1; i >= 0; --i) {
if (open[i].asActivityRecord() == null
|| (closeTask != open[i].asActivityRecord().getTask())) {
return false;
}
}
return true;
}
private static boolean isTaskSwitch(@NonNull WindowContainer close,
@NonNull WindowContainer[] open) {
if (open == null || open.length != 1 || close.asTask() == null) {
return false;
}
return open[0].asTask() != null && (close.asTask() != open[0].asTask());
}
private static boolean isDialogClose(WindowContainer close) {
return close.asWindowState() != null;
}
private void initiate(ScheduleAnimationBuilder builder,
@NonNull ActivityRecord[] openingActivities) {
WindowContainer close = builder.mCloseTarget;
WindowContainer[] open = builder.mOpenTargets;
if (isActivitySwitch(close, open)) {
mSwitchType = ACTIVITY_SWITCH;
if (Flags.migratePredictiveBackTransition()) {
final Pair<WindowContainer, WindowContainer[]> replaced =
promoteToTFIfNeeded(close, open);
close = replaced.first;
open = replaced.second;
}
} else if (isTaskSwitch(close, open)) {
mSwitchType = TASK_SWITCH;
} else if (isDialogClose(close)) {
mSwitchType = DIALOG_CLOSE;
} else {
mSwitchType = UNKNOWN;
return;
}
final Transition prepareTransition = builder.prepareTransitionIfNeeded(
openingActivities);
final SurfaceControl.Transaction st = openingActivities[0].getSyncTransaction();
final SurfaceControl.Transaction ct = prepareTransition != null
? st : close.getPendingTransaction();
mCloseAdaptor = createAdaptor(close, false, mSwitchType, ct);
if (mCloseAdaptor.mAnimationTarget == null) {
Slog.w(TAG, "composeNewAnimations fail, skip");
if (prepareTransition != null) {
prepareTransition.abort();
}
clearBackAnimateTarget(true /* cancel */);
return;
}
// Start fixed rotation for previous activity before create animation.
if (openingActivities.length == 1) {
final ActivityRecord next = openingActivities[0];
final DisplayContent dc = next.mDisplayContent;
dc.rotateInDifferentOrientationIfNeeded(next);
if (next.hasFixedRotationTransform()) {
// Set the record so we can recognize it to continue to update display
// orientation if the previous activity becomes the top later.
dc.setFixedRotationLaunchingApp(next,
next.getWindowConfiguration().getRotation());
}
}
mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(
true, mSwitchType, st, open);
if (!mOpenAnimAdaptor.isValid()) {
Slog.w(TAG, "compose animations fail, skip");
if (prepareTransition != null) {
prepareTransition.abort();
}
clearBackAnimateTarget(true /* cancel */);
return;
}
mOpenAnimAdaptor.mPreparedOpenTransition = prepareTransition;
mOpenActivities = openingActivities;
}
private Pair<WindowContainer, WindowContainer[]> promoteToTFIfNeeded(
WindowContainer close, WindowContainer[] open) {
WindowContainer replaceClose = close;
TaskFragment closeTF = close.asActivityRecord().getTaskFragment();
if (closeTF != null && !closeTF.isEmbedded()) {
closeTF = null;
}
final WindowContainer[] replaceOpen = new WindowContainer[open.length];
if (open.length >= 2) { // Promote to TaskFragment
for (int i = open.length - 1; i >= 0; --i) {
replaceOpen[i] = open[i].asActivityRecord().getTaskFragment();
replaceClose = closeTF != null ? closeTF : close;
}
} else {
TaskFragment openTF = open[0].asActivityRecord().getTaskFragment();
if (openTF != null && !openTF.isEmbedded()) {
openTF = null;
}
if (closeTF != openTF) {
replaceOpen[0] = openTF != null ? openTF : open[0];
replaceClose = closeTF != null ? closeTF : close;
} else {
replaceOpen[0] = open[0];
}
}
return new Pair<>(replaceClose, replaceOpen);
}
private boolean composeAnimations(@NonNull ScheduleAnimationBuilder builder,
@NonNull ActivityRecord[] openingActivities) {
if (mComposed || mWaitTransition) {
Slog.e(TAG, "Previous animation is running " + this);
return false;
}
clearBackAnimateTarget(true /* cancel */);
final WindowContainer[] open = builder.mOpenTargets;
if (builder.mCloseTarget == null || open == null || open.length == 0
|| open.length > 2) {
Slog.e(TAG, "reset animation with null target close: "
+ builder.mCloseTarget + " open: " + Arrays.toString(open));
return false;
}
initiate(builder, openingActivities);
if (mSwitchType == UNKNOWN) {
return false;
}
mComposed = true;
mWaitTransition = false;
return true;
}
@Nullable RemoteAnimationTarget[] getAnimationTargets() {
if (!mComposed) {
return null;
}
final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[2];
targets[0] = mCloseAdaptor.mAnimationTarget;
targets[1] = mOpenAnimAdaptor.mRemoteAnimationTarget;
return targets;
}
boolean isSupportWindowlessSurface() {
return mWindowManagerService.mAtmService.mTaskOrganizerController
.isSupportWindowlessStartingSurface();
}
boolean containTarget(@NonNull ArrayList<WindowContainer> wcs, boolean open) {
for (int i = wcs.size() - 1; i >= 0; --i) {
if (isTarget(wcs.get(i), open)) {
return true;
}
}
return wcs.isEmpty();
}
boolean isTarget(@NonNull WindowContainer wc, boolean open) {
if (!mComposed) {
return false;
}
if (open) {
for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
if (isAnimateTarget(wc, mOpenAnimAdaptor.mAdaptors[i].mTarget, mSwitchType)) {
return true;
}
}
return false;
}
return isAnimateTarget(wc, mCloseAdaptor.mTarget, mSwitchType);
}
void markWindowHasDrawn(ActivityRecord activity) {
if (!mComposed || mWaitTransition
|| mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
return;
}
boolean allWindowDrawn = true;
for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
final BackWindowAnimationAdaptor next = mOpenAnimAdaptor.mAdaptors[i];
if (isAnimateTarget(activity, next.mTarget, mSwitchType)) {
next.mAppWindowDrawn = true;
}
allWindowDrawn &= next.mAppWindowDrawn;
}
// Do not remove windowless surfaces if the transaction has not been applied.
if (activity.getSyncTransactionCommitCallbackDepth() > 0
|| activity.mSyncState != SYNC_STATE_NONE) {
return;
}
if (allWindowDrawn) {
mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
}
}
boolean isStartingSurfaceDrawn(ActivityRecord activity) {
// Check whether we create windowless surface to prepare open transition
if (!mComposed || mOpenAnimAdaptor.mPreparedOpenTransition == null) {
return false;
}
if (isTarget(activity, true /* open */)) {
return mOpenAnimAdaptor.mStartingSurface != null;
}
return false;
}
private static boolean isAnimateTarget(@NonNull WindowContainer window,
@NonNull WindowContainer animationTarget, int switchType) {
if (switchType == TASK_SWITCH) {
// simplify home search for multiple hierarchy
if (window.isActivityTypeHome() && animationTarget.isActivityTypeHome()) {
return true;
}
return window == animationTarget
|| (animationTarget.asTask() != null && animationTarget.hasChild(window))
|| (animationTarget.asActivityRecord() != null
&& window.hasChild(animationTarget));
} else if (switchType == ACTIVITY_SWITCH) {
return window == animationTarget
|| (window.asTaskFragment() != null && window.hasChild(animationTarget))
|| (animationTarget.asTaskFragment() != null
&& animationTarget.hasChild(window));
}
return false;
}
void finishPresentAnimations(boolean cancel) {
if (mOpenActivities != null) {
for (int i = mOpenActivities.length - 1; i >= 0; --i) {
final ActivityRecord resetActivity = mOpenActivities[i];
if (resetActivity.mDisplayContent.isFixedRotationLaunchingApp(resetActivity)) {
resetActivity.mDisplayContent
.continueUpdateOrientationForDiffOrienLaunchingApp();
}
final Transition finishTransition =
resetActivity.mTransitionController.mFinishingTransition;
final boolean inFinishTransition = finishTransition != null
&& (mPrepareCloseTransition == finishTransition
|| (mOpenAnimAdaptor != null
&& mOpenAnimAdaptor.mPreparedOpenTransition == finishTransition));
if (resetActivity.mLaunchTaskBehind) {
restoreLaunchBehind(resetActivity, cancel, inFinishTransition);
}
}
}
if (mCloseAdaptor != null) {
mCloseAdaptor.mTarget.cancelAnimation();
mCloseAdaptor = null;
}
if (mOpenAnimAdaptor != null) {
mOpenAnimAdaptor.cleanUp(mStartingSurfaceTargetMatch);
mOpenAnimAdaptor = null;
}
}
void markStartingSurfaceMatch(SurfaceControl.Transaction startTransaction) {
if (mStartingSurfaceTargetMatch) {
return;
}
mStartingSurfaceTargetMatch = true;
if (mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
return;
}
boolean allWindowDrawn = true;
for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
final BackWindowAnimationAdaptor next = mOpenAnimAdaptor.mAdaptors[i];
allWindowDrawn &= next.mAppWindowDrawn;
}
if (!allWindowDrawn) {
return;
}
startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
synchronized (mWindowManagerService.mGlobalLock) {
if (mOpenAnimAdaptor != null) {
mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
}
}
});
}
void clearBackAnimateTarget(boolean cancel) {
if (mComposed) {
mComposed = false;
finishPresentAnimations(cancel);
}
mPrepareCloseTransition = null;
mWaitTransition = false;
mStartingSurfaceTargetMatch = false;
mSwitchType = UNKNOWN;
mOpenActivities = null;
}
// The close target must in close list
// The open target can either in close or open list
boolean containsBackAnimationTargets(@NonNull ArrayList<WindowContainer> openApps,
@NonNull ArrayList<WindowContainer> closeApps) {
return containTarget(closeApps, false /* open */)
&& (containTarget(openApps, true /* open */)
|| containTarget(openApps, false /* open */));
}
/**
* Check if any animation target is detached, possibly due to app crash.
*/
boolean hasTargetDetached() {
if (!mComposed) {
return false;
}
for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
if (!mOpenAnimAdaptor.mAdaptors[i].mTarget.isAttached()) {
return true;
}
}
return !mCloseAdaptor.mTarget.isAttached();
}
@Override
public String toString() {
return "AnimationTargets{"
+ " openTarget= "
+ (mOpenAnimAdaptor != null ? dumpOpenAnimTargetsToString() : null)
+ " closeTarget= "
+ (mCloseAdaptor != null ? mCloseAdaptor.mTarget : null)
+ " mSwitchType= "
+ mSwitchType
+ " mComposed= "
+ mComposed
+ " mWaitTransition= "
+ mWaitTransition
+ '}';
}
private String dumpOpenAnimTargetsToString() {
final StringBuilder sb = new StringBuilder();
sb.append("{");
for (int i = 0; i < mOpenAnimAdaptor.mAdaptors.length; i++) {
if (i > 0) {
sb.append(',');
}
sb.append(mOpenAnimAdaptor.mAdaptors[i].mTarget);
}
sb.append("}");
return sb.toString();
}
@NonNull private static BackWindowAnimationAdaptor createAdaptor(
@NonNull WindowContainer target, boolean isOpen, int switchType,
SurfaceControl.Transaction st) {
final BackWindowAnimationAdaptor adaptor =
new BackWindowAnimationAdaptor(target, isOpen, switchType);
// Workaround to show TaskFragment which can be hide in Transitions and won't show
// during isAnimating.
if (isOpen && target.asActivityRecord() != null) {
final TaskFragment fragment = target.asActivityRecord().getTaskFragment();
if (fragment != null) {
// Ensure task fragment surface has updated, in case configuration has changed.
fragment.updateOrganizedTaskFragmentSurface();
st.show(fragment.mSurfaceControl);
}
}
target.startAnimation(st, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
return adaptor;
}
private static class BackWindowAnimationAdaptorWrapper {
final BackWindowAnimationAdaptor[] mAdaptors;
// The highest remote animation target, which can be a wrapper if multiple adaptors,
// or the single opening target.
final RemoteAnimationTarget mRemoteAnimationTarget;
SurfaceControl.Transaction mCloseTransaction;
// The starting surface task Id. Used to clear the starting surface if the animation has
// requested one during animating.
private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
private SurfaceControl mStartingSurface;
private Transition mPreparedOpenTransition;
BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
SurfaceControl.Transaction st, @NonNull WindowContainer... targets) {
mAdaptors = new BackWindowAnimationAdaptor[targets.length];
for (int i = targets.length - 1; i >= 0; --i) {
mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType, st);
}
mRemoteAnimationTarget = targets.length > 1 ? createWrapTarget(st)
: mAdaptors[0].mAnimationTarget;
}
boolean isValid() {
for (int i = mAdaptors.length - 1; i >= 0; --i) {
if (mAdaptors[i].mAnimationTarget == null) {
return false;
}
}
return true;
}
void cleanUp(boolean startingSurfaceMatch) {
cleanUpWindowlessSurface(startingSurfaceMatch);
for (int i = mAdaptors.length - 1; i >= 0; --i) {
mAdaptors[i].mTarget.cancelAnimation();
}
if (mCloseTransaction != null) {
mCloseTransaction.apply();
mCloseTransaction = null;
}
mPreparedOpenTransition = null;
}
private RemoteAnimationTarget createWrapTarget(SurfaceControl.Transaction st) {
// Special handle for opening two activities together.
// If we animate both activities separately, the animation area and rounded corner
// would also being handled separately. To make them seem like "open" together, wrap
// their leash with another animation leash.
final Rect unionBounds = new Rect();
for (int i = mAdaptors.length - 1; i >= 0; --i) {
unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
}
final WindowContainer wc = mAdaptors[0].mTarget;
final Task task = mAdaptors[0].getTopTask();
final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
final SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName("cross-animation-leash")
.setContainerLayer()
.setHidden(false)
.setParent(task.getSurfaceControl())
.setCallsite(
"BackWindowAnimationAdaptorWrapper.getOrCreateAnimationTarget")
.build();
mCloseTransaction = new SurfaceControl.Transaction();
mCloseTransaction.reparent(leashSurface, null);
st.setLayer(leashSurface, wc.getLastLayer());
for (int i = mAdaptors.length - 1; i >= 0; --i) {
BackWindowAnimationAdaptor adaptor = mAdaptors[i];
st.reparent(adaptor.mAnimationTarget.leash, leashSurface);
st.setPosition(adaptor.mAnimationTarget.leash,
adaptor.mAnimationTarget.localBounds.left,
adaptor.mAnimationTarget.localBounds.top);
// For adjacent activity embedded, reparent Activity to TaskFragment when
// animation finish
final WindowContainer parent = adaptor.mTarget.getParent();
if (parent != null) {
mCloseTransaction.reparent(adaptor.mTarget.getSurfaceControl(),
parent.getSurfaceControl());
}
}
return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
represent.isTranslucent, represent.clipRect, represent.contentInsets,
represent.prefixOrderIndex,
new Point(unionBounds.left, unionBounds.top),
unionBounds, unionBounds, represent.windowConfiguration,
true /* isNotInRecents */, null, null, represent.taskInfo,
represent.allowEnterPip);
}
void createStartingSurface(@Nullable TaskSnapshot snapshot) {
if (Flags.deferPredictiveAnimationIfNoSnapshot() && snapshot == null) {
return;
}
if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
return;
}
final WindowContainer mainOpen = mAdaptors[0].mTarget;
final int switchType = mAdaptors[0].mSwitchType;
final Task openTask = mAdaptors[0].getTopTask();
if (openTask == null) {
return;
}
ActivityRecord mainActivity = null;
if (switchType == ACTIVITY_SWITCH) {
mainActivity = mainOpen.asActivityRecord();
if (mainActivity == null && mainOpen.asTaskFragment() != null) {
mainActivity = mainOpen.asTaskFragment().getTopNonFinishingActivity();
}
}
if (mainActivity == null) {
mainActivity = openTask.getTopNonFinishingActivity();
}
if (mainActivity == null) {
return;
}
// If there is only one adaptor, attach the windowless window to top activity,
// because fixed rotation only applies on activity.
// Note that embedded activity won't use fixed rotation. Also, there is only one
// animation target for closing task.
final boolean chooseActivity = mAdaptors.length == 1
&& (switchType == ACTIVITY_SWITCH || mainActivity.mDisplayContent
.isFixedRotationLaunchingApp(mainActivity));
final Configuration openConfig = chooseActivity
? mainActivity.getConfiguration() : openTask.getConfiguration();
mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
.addWindowlessStartingSurface(openTask, mainActivity,
chooseActivity ? mainActivity.getSurfaceControl()
: mRemoteAnimationTarget.leash, snapshot, openConfig,
new IWindowlessStartingSurfaceCallback.Stub() {
// Once the starting surface has been created in shell, it will call
// onSurfaceAdded to pass the created surface to core, so if a
// transition is triggered by the back gesture, there doesn't need to
// create another starting surface for the opening target, just reparent
// the starting surface to the opening target.
// Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
// called, there won't be able to reparent the starting surface on
// opening target. But if that happens and transition target is matched,
// the app window should already draw.
@Override
public void onSurfaceAdded(SurfaceControl sc) {
synchronized (openTask.mWmService.mGlobalLock) {
if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
mStartingSurface = sc;
openTask.mWmService.mWindowPlacerLocked
.requestTraversal();
} else {
sc.release();
}
}
}
});
}
/**
* Ask shell to clear the starting surface.
* @param openTransitionMatch if true, shell will play the remove starting window
* animation, otherwise remove it directly.
*/
void cleanUpWindowlessSurface(boolean openTransitionMatch) {
if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
return;
}
mAdaptors[0].mTarget.mWmService.mAtmService.mTaskOrganizerController
.removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
!openTransitionMatch);
mRequestedStartingSurfaceId = INVALID_TASK_ID;
if (mStartingSurface != null && mStartingSurface.isValid()) {
mStartingSurface.release();
mStartingSurface = null;
}
}
}
private static class BackWindowAnimationAdaptor implements AnimationAdapter {
SurfaceControl mCapturedLeash;
boolean mAppWindowDrawn;
private final Rect mBounds = new Rect();
private final WindowContainer mTarget;
private final boolean mIsOpen;
private RemoteAnimationTarget mAnimationTarget;
private final int mSwitchType;
BackWindowAnimationAdaptor(@NonNull WindowContainer target, boolean isOpen,
int switchType) {
mBounds.set(target.getBounds());
mTarget = target;
mIsOpen = isOpen;
mSwitchType = switchType;
}
Task getTopTask() {
final Task asTask = mTarget.asTask();
if (asTask != null) {
return asTask;
}
final ActivityRecord ar = mTarget.asActivityRecord();
if (ar != null) {
return ar.getTask();
}
final TaskFragment tf = mTarget.asTaskFragment();
if (tf != null) {
return tf.getTask();
}
return null;
}
@Override
public boolean getShowWallpaper() {
return false;
}
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
mCapturedLeash = animationLeash;
createRemoteAnimationTarget();
final WindowState win = mTarget.asWindowState();
if (win != null && mSwitchType == DIALOG_CLOSE) {
final Rect frame = win.getFrame();
final Point position = new Point();
win.transformFrameToSurfacePosition(frame.left, frame.top, position);
t.setPosition(mCapturedLeash, position.x, position.y);
}
}
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mCapturedLeash == animationLeash) {
mCapturedLeash = null;
}
}
@Override
public long getDurationHint() {
return 0;
}
@Override
public long getStatusBarTransitionsStartTime() {
return 0;
}
@Override
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix + "BackWindowAnimationAdaptor mCapturedLeash=");
pw.print(mCapturedLeash);
pw.println();
}
@Override
public void dumpDebug(ProtoOutputStream proto) {
}
RemoteAnimationTarget createRemoteAnimationTarget() {
if (mAnimationTarget != null) {
return mAnimationTarget;
}
WindowState w = mTarget.asWindowState();
ActivityRecord r = w != null ? w.getActivityRecord() : null;
Task t = r != null ? r.getTask() : mTarget.asTask();
if (t == null && mTarget.asTaskFragment() != null) {
t = mTarget.asTaskFragment().getTask();
r = mTarget.asTaskFragment().getTopNonFinishingActivity();
}
if (r == null) {
r = t != null ? t.getTopNonFinishingActivity()
: mTarget.asActivityRecord();
}
if (t == null && r != null) {
t = r.getTask();
}
if (t == null || r == null) {
Slog.e(TAG, "createRemoteAnimationTarget fail " + mTarget);
return null;
}
final WindowState mainWindow = r.findMainWindow();
final Rect insets = mainWindow != null
? mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
mBounds, WindowInsets.Type.tappableElement(),
false /* ignoreVisibility */).toRect()
: new Rect();
final int mode = mIsOpen ? MODE_OPENING : MODE_CLOSING;
mAnimationTarget = new RemoteAnimationTarget(t.mTaskId, mode, mCapturedLeash,
!r.fillsParent(), new Rect(),
insets, r.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
mBounds, mBounds, t.getWindowConfiguration(),
true /* isNotInRecents */, null, null, t.getTaskInfo(),
r.checkEnterPictureInPictureAppOpsState());
return mAnimationTarget;
}
}
ScheduleAnimationBuilder prepareAnimation(
int backType,
BackAnimationAdapter adapter,
NavigationMonitor monitor,
Task currentTask,
Task previousTask,
ActivityRecord currentActivity,
ArrayList<ActivityRecord> previousActivity,
WindowContainer removedWindowContainer) {
final ScheduleAnimationBuilder builder = new ScheduleAnimationBuilder(adapter, monitor);
switch (backType) {
case BackNavigationInfo.TYPE_RETURN_TO_HOME:
return builder
.setIsLaunchBehind(true)
.setComposeTarget(currentTask, previousTask);
case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
ActivityRecord[] prevActs = new ActivityRecord[previousActivity.size()];
prevActs = previousActivity.toArray(prevActs);
return builder
.setComposeTarget(currentActivity, prevActs)
.setIsLaunchBehind(false);
case BackNavigationInfo.TYPE_CROSS_TASK:
return builder
.setComposeTarget(currentTask, previousTask)
.setIsLaunchBehind(false);
case BackNavigationInfo.TYPE_DIALOG_CLOSE:
return builder
.setComposeTarget(removedWindowContainer, currentActivity)
.setIsLaunchBehind(false);
}
return null;
}
class ScheduleAnimationBuilder {
final BackAnimationAdapter mBackAnimationAdapter;
final NavigationMonitor mNavigationMonitor;
WindowContainer mCloseTarget;
WindowContainer[] mOpenTargets;
boolean mIsLaunchBehind;
TaskSnapshot mSnapshot;
ScheduleAnimationBuilder(BackAnimationAdapter adapter,
NavigationMonitor monitor) {
mBackAnimationAdapter = adapter;
mNavigationMonitor = monitor;
}
ScheduleAnimationBuilder setComposeTarget(@NonNull WindowContainer close,
@NonNull WindowContainer... open) {
mCloseTarget = close;
mOpenTargets = open;
return this;
}
ScheduleAnimationBuilder setIsLaunchBehind(boolean launchBehind) {
mIsLaunchBehind = launchBehind;
return this;
}
// WC must be Activity/TaskFragment/Task
boolean containTarget(@NonNull WindowContainer wc) {
if (mOpenTargets != null) {
for (int i = mOpenTargets.length - 1; i >= 0; --i) {
if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)
|| wc.hasChild(mOpenTargets[i])) {
return true;
}
}
}
return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
}
private Transition prepareTransitionIfNeeded(ActivityRecord[] visibleOpenActivities) {
if (Flags.unifyBackNavigationTransition()) {
if (mCloseTarget.asWindowState() != null) {
return null;
}
final ArrayList<ActivityRecord> makeVisibles = new ArrayList<>();
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
final ActivityRecord activity = visibleOpenActivities[i];
if (activity.mLaunchTaskBehind || activity.isVisibleRequested()) {
continue;
}
makeVisibles.add(activity);
}
final TransitionController tc = visibleOpenActivities[0].mTransitionController;
final Transition prepareOpen = tc.createTransition(
TRANSIT_PREPARE_BACK_NAVIGATION);
tc.collect(mCloseTarget);
prepareOpen.setBackGestureAnimation(mCloseTarget, true /* isTop */);
for (int i = mOpenTargets.length - 1; i >= 0; --i) {
tc.collect(mOpenTargets[i]);
prepareOpen.setBackGestureAnimation(mOpenTargets[i], false /* isTop */);
}
if (!makeVisibles.isEmpty()) {
setLaunchBehind(visibleOpenActivities);
}
tc.requestStartTransition(prepareOpen,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
prepareOpen.setReady(mCloseTarget, true);
return prepareOpen;
} else if (mSnapshot == null) {
return setLaunchBehind(visibleOpenActivities);
}
return null;
}
/**
* Apply preview strategy on the opening target
*
* @param openAnimationAdaptor The animator who can create starting surface.
* @param visibleOpenActivities The visible activities in opening targets.
*/
private void applyPreviewStrategy(
@NonNull BackWindowAnimationAdaptorWrapper openAnimationAdaptor,
@NonNull ActivityRecord[] visibleOpenActivities) {
if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
boolean activitiesAreDrawn = false;
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
// If the activity hasn't stopped, it's window should remain drawn.
activitiesAreDrawn |= visibleOpenActivities[i].firstWindowDrawn;
}
// Don't create starting surface if previous activities haven't stopped or
// the snapshot does not exist.
if (mSnapshot != null || !activitiesAreDrawn) {
openAnimationAdaptor.createStartingSurface(mSnapshot);
}
}
// Force update mLastSurfaceShowing for opening activity and its task.
if (mWindowManagerService.mRoot.mTransitionController.isShellTransitionsEnabled()) {
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
WindowContainer.enforceSurfaceVisible(visibleOpenActivities[i]);
}
}
}
@Nullable Runnable build() {
if (mOpenTargets == null || mCloseTarget == null || mOpenTargets.length == 0) {
return null;
}
final boolean shouldLaunchBehind = mIsLaunchBehind || !isSupportWindowlessSurface();
final ActivityRecord[] openingActivities = getTopOpenActivities(mOpenTargets);
if (shouldLaunchBehind && openingActivities == null) {
Slog.e(TAG, "No opening activity");
return null;
}
if (!shouldLaunchBehind && mShowWindowlessSurface) {
mSnapshot = getSnapshot(mOpenTargets[0], openingActivities);
}
if (!composeAnimations(this, openingActivities)) {
return null;
}
mCloseTarget.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
applyPreviewStrategy(mOpenAnimAdaptor, openingActivities);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
final RemoteAnimationTarget[] targets = getAnimationTargets();
return () -> {
try {
if (hasTargetDetached() || !validateAnimationTargets(targets)) {
mNavigationMonitor.cancelBackNavigating("cancelAnimation");
mBackAnimationAdapter.getRunner().onAnimationCancelled();
} else {
mBackAnimationAdapter.getRunner().onAnimationStart(targets,
mOpenAnimAdaptor.mPreparedOpenTransition != null
? mOpenAnimAdaptor.mPreparedOpenTransition.getToken()
: null, callback);
}
} catch (RemoteException e) {
e.printStackTrace();
}
};
}
private IBackAnimationFinishedCallback makeAnimationFinishedCallback() {
return new IBackAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished(boolean triggerBack) {
synchronized (mWindowManagerService.mGlobalLock) {
if (!mComposed) {
// animation was canceled
return;
}
if (Flags.migratePredictiveBackTransition()) {
if (mOpenAnimAdaptor == null
|| mOpenAnimAdaptor.mPreparedOpenTransition == null) {
// no open nor close transition, this is window animation
if (!triggerBack) {
clearBackAnimateTarget(true /* cancel */);
}
}
} else {
if (!triggerBack) {
clearBackAnimateTarget(true /* cancel */);
} else {
mWaitTransition = true;
}
}
}
}
};
}
}
}
/**
* Validate animation targets.
*/
private 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;
}
/**
* Finds next opening activity(ies) based on open targets, which could be:
* 1. If the open window is Task, then the open activity can either be an activity, or
* two activities inside two TaskFragments
* 2. If the open window is Activity, then the open window can be an activity, or two
* adjacent TaskFragments below it.
*/
@Nullable
private static ActivityRecord[] getTopOpenActivities(
@NonNull WindowContainer[] openWindows) {
ActivityRecord[] openActivities = null;
final WindowContainer mainTarget = openWindows[0];
if (mainTarget.asTask() != null) {
final ArrayList<ActivityRecord> inTaskActivities = new ArrayList<>();
final Task task = mainTarget.asTask();
final ActivityRecord tmpPreActivity = task.getTopNonFinishingActivity();
if (tmpPreActivity != null) {
inTaskActivities.add(tmpPreActivity);
findAdjacentActivityIfExist(tmpPreActivity, inTaskActivities);
}
openActivities = new ActivityRecord[inTaskActivities.size()];
for (int i = inTaskActivities.size() - 1; i >= 0; --i) {
openActivities[i] = inTaskActivities.get(i);
}
} else if (mainTarget.asActivityRecord() != null) {
final int size = openWindows.length;
openActivities = new ActivityRecord[size];
for (int i = size - 1; i >= 0; --i) {
openActivities[i] = openWindows[i].asActivityRecord();
}
}
return openActivities;
}
boolean restoreBackNavigation() {
if (!mAnimationHandler.mComposed) {
return false;
}
ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
boolean changed = false;
if (penActivities != null) {
for (int i = penActivities.length - 1; i >= 0; --i) {
ActivityRecord resetActivity = penActivities[i];
if (resetActivity.mLaunchTaskBehind) {
resetActivity.mTransitionController.collect(resetActivity);
restoreLaunchBehind(resetActivity, true, false);
changed = true;
}
}
}
return changed;
}
boolean restoreBackNavigationSetTransitionReady(Transition transition) {
if (!mAnimationHandler.mComposed) {
return false;
}
ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
if (penActivities != null) {
for (int i = penActivities.length - 1; i >= 0; --i) {
ActivityRecord resetActivity = penActivities[i];
if (transition.isInTransition(resetActivity)) {
transition.setReady(resetActivity.getDisplayContent(), true);
return true;
}
}
}
return false;
}
private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) {
final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
final boolean unifyBackNavigationTransition = Flags.unifyBackNavigationTransition();
final ArrayList<ActivityRecord> affects = new ArrayList<>();
for (int i = activities.length - 1; i >= 0; --i) {
final ActivityRecord activity = activities[i];
if (activity.mLaunchTaskBehind || activity.isVisibleRequested()) {
continue;
}
affects.add(activity);
}
if (affects.isEmpty()) {
return null;
}
final TransitionController tc = activities[0].mTransitionController;
final Transition prepareOpen = migrateBackTransition && !unifyBackNavigationTransition
&& !tc.isCollecting() ? tc.createTransition(TRANSIT_PREPARE_BACK_NAVIGATION) : null;
DisplayContent commonDisplay = null;
for (int i = affects.size() - 1; i >= 0; --i) {
final ActivityRecord activity = affects.get(i);
if (!migrateBackTransition && !activity.isVisibleRequested()) {
// The transition could commit the visibility and in the finishing state, that could
// skip commitVisibility call in setVisibility cause the activity won't visible
// here.
// Call it again to make sure the activity could be visible while handling the
// pending animation.
// Do not performLayout during prepare animation, because it could cause focus
// window change. Let that happen after the BackNavigationInfo has returned to
// shell.
activity.commitVisibility(true, false /* performLayout */);
}
activity.mTransitionController.mSnapshotController
.mActivitySnapshotController.addOnBackPressedActivity(activity);
activity.mLaunchTaskBehind = true;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
activity.mTaskSupervisor.mStoppingActivities.remove(activity);
if (!migrateBackTransition) {
commonDisplay = activity.getDisplayContent();
} else if (activity.shouldBeVisible()) {
activity.ensureActivityConfiguration(true /* ignoreVisibility */);
activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */);
}
}
if (commonDisplay != null) {
commonDisplay.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
}
if (prepareOpen != null) {
if (prepareOpen.hasChanges()) {
tc.requestStartTransition(prepareOpen,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
prepareOpen.setReady(affects.get(0), true);
return prepareOpen;
} else {
prepareOpen.abort();
}
}
return null;
}
private static void restoreLaunchBehind(@NonNull ActivityRecord activity, boolean cancel,
boolean finishTransition) {
if (!activity.isAttached()) {
// The activity was detached from hierarchy.
return;
}
activity.mLaunchTaskBehind = false;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
activity);
if (cancel) {
final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
// could be visible if transition is canceled due to top activity is finishing.
if (migrateBackTransition) {
if (finishTransition && !activity.shouldBeVisible()) {
activity.commitVisibility(false /* visible */, false /* performLayout */,
true /* fromTransition */);
}
} else {
// Restore the launch-behind state
// TODO b/347168362 Change status directly during collecting for a transition.
activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
}
// Ignore all change
activity.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
}
}
void checkAnimationReady(WallpaperController wallpaperController) {
if (!mBackAnimationInProgress) {
return;
}
final boolean wallpaperReady = !mShowWallpaper
|| (wallpaperController.getWallpaperTarget() != null
&& wallpaperController.wallpaperTransitionReady());
if (wallpaperReady && mPendingAnimation != null) {
mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(this::startAnimation);
}
}
/** If the open transition is playing, wait for transition to clear the animation */
private boolean canCancelAnimations() {
if (!Flags.migratePredictiveBackTransition()) {
return true;
}
return mAnimationHandler.mOpenAnimAdaptor == null
|| mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == null;
}
void startAnimation() {
if (!mBackAnimationInProgress) {
// gesture is already finished, do not start animation
if (mPendingAnimation != null) {
if (canCancelAnimations()) {
clearBackAnimations(true /* cancel */);
}
mPendingAnimation = null;
}
return;
}
if (mPendingAnimation != null) {
mPendingAnimation.run();
mPendingAnimation = null;
}
}
private void onBackNavigationDone(Bundle result, int backType) {
if (result == null) {
return;
}
if (result.containsKey(BackNavigationInfo.KEY_NAVIGATION_FINISHED)) {
final boolean triggerBack = result.getBoolean(
BackNavigationInfo.KEY_NAVIGATION_FINISHED);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "triggerBack=%b", backType, triggerBack);
synchronized (mWindowManagerService.mGlobalLock) {
mNavigationMonitor.stopMonitorForRemote();
mBackAnimationInProgress = false;
mShowWallpaper = false;
// All animation should be done, clear any un-send animation.
mPendingAnimation = null;
mPendingAnimationBuilder = null;
}
}
if (result.getBoolean(BackNavigationInfo.KEY_GESTURE_FINISHED)) {
synchronized (mWindowManagerService.mGlobalLock) {
final AnimationHandler ah = mAnimationHandler;
if (!ah.mComposed || ah.mWaitTransition || ah.mOpenActivities == null
|| (ah.mSwitchType != AnimationHandler.TASK_SWITCH
&& ah.mSwitchType != AnimationHandler.ACTIVITY_SWITCH)) {
return;
}
setLaunchBehind(mAnimationHandler.mOpenActivities);
}
}
}
static TaskSnapshot getSnapshot(@NonNull WindowContainer w,
ActivityRecord[] visibleOpenActivities) {
TaskSnapshot snapshot = null;
if (w.asTask() != null) {
final Task task = w.asTask();
snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
task.mTaskId, task.mUserId, false /* restoreFromDisk */,
false /* isLowResolution */);
} else {
ActivityRecord ar = w.asActivityRecord();
if (ar == null && w.asTaskFragment() != null) {
ar = w.asTaskFragment().getTopNonFinishingActivity();
}
if (ar != null) {
snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
.getSnapshot(visibleOpenActivities);
}
}
return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
}
static boolean isSnapshotCompatible(@Nullable TaskSnapshot snapshot,
@NonNull ActivityRecord[] visibleOpenActivities) {
if (snapshot == null) {
return false;
}
boolean oneComponentMatch = false;
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
final ActivityRecord ar = visibleOpenActivities[i];
if (!ar.isSnapshotOrientationCompatible(snapshot)) {
return false;
}
final int appNightMode = ar.getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
final int snapshotNightMode = snapshot.getUiMode() & Configuration.UI_MODE_NIGHT_MASK;
if (appNightMode != snapshotNightMode) {
return false;
}
oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
}
return oneComponentMatch;
}
void setWindowManager(WindowManagerService wm) {
mWindowManagerService = wm;
mAnimationHandler = new AnimationHandler(wm);
}
boolean isWallpaperVisible(WindowState w) {
return mAnimationHandler.mComposed && mShowWallpaper
&& w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
&& mAnimationHandler.isTarget(w.mActivityRecord, true /* open */);
}
// Called from WindowManagerService to write to a protocol buffer output stream.
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress);
proto.write(LAST_BACK_TYPE, mLastBackType);
proto.write(SHOW_WALLPAPER, mShowWallpaper);
if (mAnimationHandler.mOpenAnimAdaptor != null
&& mAnimationHandler.mOpenAnimAdaptor.mAdaptors.length > 0) {
mAnimationHandler.mOpenActivities[0].writeNameToProto(
proto, MAIN_OPEN_ACTIVITY);
} else {
proto.write(MAIN_OPEN_ACTIVITY, "");
}
// TODO (b/268563842) Only meaningful after new test added
proto.write(ANIMATION_RUNNING, mAnimationHandler.mComposed
|| mAnimationHandler.mWaitTransition);
proto.end(token);
}
}