Merge "Use a SLEEP transition to trigger failsafe animation cancelling" into udc-dev am: 427613c538

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21711652

Change-Id: I2fe65c7e659eef52f6e90eed305f2a60e5d3fa06
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Evan Rosky
2023-03-04 22:26:04 +00:00
committed by Automerger Merge Worker
9 changed files with 246 additions and 28 deletions

View File

@@ -453,6 +453,11 @@ public interface WindowManager extends ViewManager {
* @hide * @hide
*/ */
int TRANSIT_WAKE = 11; int TRANSIT_WAKE = 11;
/**
* The screen is turning off. This is used as a message to stop all animations.
* @hide
*/
int TRANSIT_SLEEP = 12;
/** /**
* The first slot for custom transition types. Callers (like Shell) can make use of custom * The first slot for custom transition types. Callers (like Shell) can make use of custom
* transition types for dealing with special cases. These types are effectively ignored by * transition types for dealing with special cases. These types are effectively ignored by
@@ -462,7 +467,7 @@ public interface WindowManager extends ViewManager {
* implementation. * implementation.
* @hide * @hide
*/ */
int TRANSIT_FIRST_CUSTOM = 12; int TRANSIT_FIRST_CUSTOM = 13;
/** /**
* @hide * @hide
@@ -480,6 +485,7 @@ public interface WindowManager extends ViewManager {
TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_KEYGUARD_UNOCCLUDE,
TRANSIT_PIP, TRANSIT_PIP,
TRANSIT_WAKE, TRANSIT_WAKE,
TRANSIT_SLEEP,
TRANSIT_FIRST_CUSTOM TRANSIT_FIRST_CUSTOM
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@@ -1437,6 +1443,7 @@ public interface WindowManager extends ViewManager {
case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE"; case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE";
case TRANSIT_PIP: return "PIP"; case TRANSIT_PIP: return "PIP";
case TRANSIT_WAKE: return "WAKE"; case TRANSIT_WAKE: return "WAKE";
case TRANSIT_SLEEP: return "SLEEP";
case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM"; case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
default: default:
if (type > TRANSIT_FIRST_CUSTOM) { if (type > TRANSIT_FIRST_CUSTOM) {

View File

@@ -1297,6 +1297,12 @@
"group": "WM_ERROR", "group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java" "at": "com\/android\/server\/wm\/WindowManagerService.java"
}, },
"-894942237": {
"message": "Force Playing Transition: %d",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
"-883738232": { "-883738232": {
"message": "Adding more than one toast window for UID at a time.", "message": "Adding more than one toast window for UID at a time.",
"level": "WARN", "level": "WARN",
@@ -4243,12 +4249,6 @@
"group": "WM_DEBUG_ORIENTATION", "group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java" "at": "com\/android\/server\/wm\/DisplayContent.java"
}, },
"1878927091": {
"message": "prepareSurface: No changes in animation for %s",
"level": "VERBOSE",
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
"1891501279": { "1891501279": {
"message": "cancelAnimation(): reason=%s", "message": "cancelAnimation(): reason=%s",
"level": "DEBUG", "level": "DEBUG",

View File

@@ -450,7 +450,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
// Already done, so no need to end it. // Already done, so no need to end it.
return; return;
} }
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
// queue since no actual animation.
} else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) { if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
boolean ended = mSplitHandler.end(); boolean ended = mSplitHandler.end();
// If split couldn't end (because it is remote), then don't end everything else // If split couldn't end (because it is remote), then don't end everything else
@@ -464,8 +466,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
} else { } else {
mPipHandler.end(); mPipHandler.end();
} }
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
// queue mPipHandler.end();
if (mixed.mLeftoversHandler != null) {
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
}
} else { } else {
throw new IllegalStateException("Playing a mixed transition with unknown type? " throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType); + mixed.mType);

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 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.transition;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.util.Log;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import java.util.ArrayList;
/**
* A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
* as sentinels for fast-forwarding through animations when the screen is off.
*
* There should only be one SleepHandler and it is used explicitly by {@link Transitions} so we
* don't register it like a normal handler.
*/
class SleepHandler implements Transitions.TransitionHandler {
final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
finishCallback.onTransitionFinished(null, null);
mSleepTransitions.remove(transition);
return true;
}
@Override
@Nullable
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
mSleepTransitions.add(transition);
return new WindowContainerTransaction();
}
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishTransaction) {
Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense");
mSleepTransitions.remove(transition);
}
}

View File

@@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale; import static android.view.WindowManager.fixScale;
@@ -124,6 +125,7 @@ public class Transitions implements RemoteCallable<Transitions> {
private final DisplayController mDisplayController; private final DisplayController mDisplayController;
private final ShellController mShellController; private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
private boolean mIsRegistered = false; private boolean mIsRegistered = false;
@@ -137,6 +139,14 @@ public class Transitions implements RemoteCallable<Transitions> {
private float mTransitionAnimationScaleSetting = 1.0f; private float mTransitionAnimationScaleSetting = 1.0f;
/**
* How much time we allow for an animation to finish itself on sleep. If it takes longer, we
* will force-finish it (on this end) which may leave it in a bad state but won't hang the
* device. This needs to be pretty small because it is an allowance for each queued animation,
* however it can't be too small since there is some potential IPC involved.
*/
private static final int SLEEP_ALLOWANCE_MS = 120;
private static final class ActiveTransition { private static final class ActiveTransition {
IBinder mToken; IBinder mToken;
TransitionHandler mHandler; TransitionHandler mHandler;
@@ -478,11 +488,29 @@ public class Transitions implements RemoteCallable<Transitions> {
+ Arrays.toString(mActiveTransitions.stream().map( + Arrays.toString(mActiveTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray())); activeTransition -> activeTransition.mToken).toArray()));
} }
final ActiveTransition active = mActiveTransitions.get(activeIdx);
for (int i = 0; i < mObservers.size(); ++i) { for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT); mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
} }
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0) {
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
if (!info.getRootLeash().isValid()) {
// Shell has some debug settings which makes calling binders with invalid
// surfaces crash, so replace it with a "real" one.
info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
.setContainerLayer().build(), 0, 0);
}
// Sleep starts a process of forcing all prior transitions to finish immediately
finishForSleep(null /* forceFinish */);
return;
}
}
// Allow to notify keyguard un-occluding state to KeyguardService, which can happen while // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
// screen-off, so there might no visibility change involved. // screen-off, so there might no visibility change involved.
if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) { if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
@@ -527,7 +555,6 @@ public class Transitions implements RemoteCallable<Transitions> {
return; return;
} }
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mInfo = info; active.mInfo = info;
active.mStartT = t; active.mStartT = t;
active.mFinishT = finishT; active.mFinishT = finishT;
@@ -797,6 +824,12 @@ public class Transitions implements RemoteCallable<Transitions> {
} }
final ActiveTransition active = new ActiveTransition(); final ActiveTransition active = new ActiveTransition();
WindowContainerTransaction wct = null; WindowContainerTransaction wct = null;
// If we have sleep, we use a special handler and we try to finish everything ASAP.
if (request.getType() == TRANSIT_SLEEP) {
mSleepHandler.handleRequest(transitionToken, request);
active.mHandler = mSleepHandler;
} else {
for (int i = mHandlers.size() - 1; i >= 0; --i) { for (int i = mHandlers.size() - 1; i >= 0; --i) {
wct = mHandlers.get(i).handleRequest(transitionToken, request); wct = mHandlers.get(i).handleRequest(transitionToken, request);
if (wct != null) { if (wct != null) {
@@ -812,8 +845,9 @@ public class Transitions implements RemoteCallable<Transitions> {
wct = new WindowContainerTransaction(); wct = new WindowContainerTransaction();
} }
mDisplayController.getChangeController().dispatchOnDisplayChange(wct, mDisplayController.getChangeController().dispatchOnDisplayChange(wct,
change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), change.getDisplayId(), change.getStartRotation(),
null /* newDisplayAreaInfo */); change.getEndRotation(), null /* newDisplayAreaInfo */);
}
} }
} }
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct); mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
@@ -839,6 +873,56 @@ public class Transitions implements RemoteCallable<Transitions> {
return active.mToken; return active.mToken;
} }
/**
* Finish running animations (almost) immediately when a SLEEP transition comes in. We use this
* as both a way to reduce unnecessary work (animations not visible while screen off) and as a
* failsafe to unblock "stuck" animations (in particular remote animations).
*
* This works by "merging" the sleep transition into the currently-playing transition (even if
* its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish
* within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
* send an abort/consumed message).
*
* This is then repeated until there are no more pending sleep transitions.
*
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
private void finishForSleep(@Nullable IBinder forceFinish) {
if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
return;
}
if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
+ mActiveTransitions.get(0).mToken);
onFinish(mActiveTransitions.get(0).mToken, null, null, true);
}
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
final ActiveTransition playing = mActiveTransitions.get(0);
int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
if (sleepIdx >= 0) {
// Try to signal that we are sleeping by attempting to merge the sleep transition
// into the playing one.
final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
playing.mToken, (wct, cb) -> {});
} else {
Log.e(TAG, "Couldn't find sleep transition in active list: "
+ mSleepHandler.mSleepTransitions.get(0));
}
// it's possible to complete immediately. If that happens, just repeat the signal
// loop until we either finish everything or start playing an animation that isn't
// finishing immediately.
if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
// Give it a (very) short amount of time to process it before forcing.
mMainExecutor.executeDelayed(
() -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
break;
}
}
}
/** /**
* Interface for a callback that must be called after a TransitionHandler finishes playing an * Interface for a callback that must be called after a TransitionHandler finishes playing an
* animation. * animation.

View File

@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_SLEEP;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityManager; import android.app.ActivityManager;
@@ -46,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.util.TransitionUtil; import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
/** /**
* Helper class to build {@link RemoteTransition} objects * Helper class to build {@link RemoteTransition} objects
@@ -205,6 +207,12 @@ public class RemoteTransitionCompat {
@SuppressLint("NewApi") @SuppressLint("NewApi")
boolean merge(TransitionInfo info, SurfaceControl.Transaction t) { boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
if (info.getType() == TRANSIT_SLEEP) {
// A sleep event means we need to stop animations immediately, so cancel here.
mListener.onAnimationCanceled(new HashMap<>());
finish(mWillFinishToHome, false /* userLeaveHint */);
return false;
}
ArrayList<TransitionInfo.Change> openingTasks = null; ArrayList<TransitionInfo.Change> openingTasks = null;
ArrayList<TransitionInfo.Change> closingTasks = null; ArrayList<TransitionInfo.Change> closingTasks = null;
mAppearedTargets = null; mAppearedTargets = null;

View File

@@ -33,6 +33,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_WAKE; import static android.view.WindowManager.TRANSIT_WAKE;
@@ -2329,6 +2330,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
} }
void applySleepTokens(boolean applyToRootTasks) { void applySleepTokens(boolean applyToRootTasks) {
boolean builtSleepTransition = false;
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
// Set the sleeping state of the display. // Set the sleeping state of the display.
final DisplayContent display = getChildAt(displayNdx); final DisplayContent display = getChildAt(displayNdx);
@@ -2338,6 +2340,30 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
} }
display.setIsSleeping(displayShouldSleep); display.setIsSleeping(displayShouldSleep);
if (display.mTransitionController.isShellTransitionsEnabled() && !builtSleepTransition
// Only care if there are actual sleep tokens.
&& displayShouldSleep && !display.mAllSleepTokens.isEmpty()) {
builtSleepTransition = true;
// We don't actually care about collecting anything here. We really just want
// this as a signal to the transition-player.
final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */,
display.mTransitionController, mWmService.mSyncEngine);
final Runnable sendSleepTransition = () -> {
display.mTransitionController.requestStartTransition(transition,
null /* trigger */, null /* remote */, null /* display */);
// Force playing immediately so that unrelated ops can't be collected.
transition.playNow();
};
if (display.mTransitionController.isCollecting()) {
mWmService.mSyncEngine.queueSyncSet(
() -> display.mTransitionController.moveToCollecting(transition),
sendSleepTransition);
} else {
display.mTransitionController.moveToCollecting(transition);
sendSleepTransition.run();
}
}
if (!applyToRootTasks) { if (!applyToRootTasks) {
continue; continue;
} }

View File

@@ -218,6 +218,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final TransitionController.Logger mLogger = new TransitionController.Logger(); final TransitionController.Logger mLogger = new TransitionController.Logger();
/** Whether this transition was forced to play early (eg for a SLEEP signal). */
private boolean mForcePlaying = false;
/** /**
* {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware * {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware
* of it). Currently, this happens before the display is ready since nothing can be seen yet. * of it). Currently, this happens before the display is ready since nothing can be seen yet.
@@ -1007,6 +1010,25 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.dispatchLegacyAppTransitionCancelled(); mController.dispatchLegacyAppTransitionCancelled();
} }
/** Immediately moves this to playing even if it isn't started yet. */
void playNow() {
if (mState == STATE_PLAYING) return;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
mSyncId);
mForcePlaying = true;
setAllReady();
if (mState == STATE_COLLECTING) {
start();
}
// Don't wait for actual surface-placement. We don't want anything else collected in this
// transition.
mSyncEngine.onSurfacePlacement();
}
boolean isForcePlaying() {
return mForcePlaying;
}
void setRemoteTransition(RemoteTransition remoteTransition) { void setRemoteTransition(RemoteTransition remoteTransition) {
mRemoteTransition = remoteTransition; mRemoteTransition = remoteTransition;
} }

View File

@@ -317,7 +317,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
} }
transition = mTransitionController.createTransition(type); transition = mTransitionController.createTransition(type);
} }
if (!transition.isCollecting()) { if (!transition.isCollecting() && !transition.isForcePlaying()) {
Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably" Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
+ " means Shell took too long to respond to a request. WM State may be" + " means Shell took too long to respond to a request. WM State may be"
+ " incorrect now, please file a bug"); + " incorrect now, please file a bug");