diff options
7 files changed, 91 insertions, 35 deletions
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index 73a7c3eb32a5..9df321c64c9a 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -42,18 +42,13 @@ import com.android.server.wm.WindowManagerService; class RecentsAnimation implements RecentsAnimationCallbacks { private static final String TAG = RecentsAnimation.class.getSimpleName(); - private static final int RECENTS_ANIMATION_TIMEOUT = 10 * 1000; - private final ActivityManagerService mService; private final ActivityStackSupervisor mStackSupervisor; private final ActivityStartController mActivityStartController; private final WindowManagerService mWindowManager; private final UserController mUserController; - private final Handler mHandler; private final int mCallingPid; - private final Runnable mCancelAnimationRunnable; - // The stack to restore the home stack behind when the animation is finished private ActivityStack mRestoreHomeBehindStack; @@ -63,16 +58,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mService = am; mStackSupervisor = stackSupervisor; mActivityStartController = activityStartController; - mHandler = new Handler(mStackSupervisor.mLooper); mWindowManager = wm; mUserController = userController; mCallingPid = callingPid; - - mCancelAnimationRunnable = () -> { - // The caller has not finished the animation in a predefined amount of time, so - // force-cancel the animation - mWindowManager.cancelRecentsAnimation(); - }; } void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner, @@ -133,10 +121,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks { // duration of the gesture that is driven by the recents component homeActivity.mLaunchTaskBehind = true; - // Post a timeout for the animation. This needs to happen before initializing the - // recents animation on the WM side since we may decide to cancel the animation there - mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); - // Fetch all the surface controls and pass them to the client to get the animation // started mWindowManager.cancelRecentsAnimation(); @@ -157,7 +141,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks { @Override public void onAnimationFinished(boolean moveHomeToTop) { - mHandler.removeCallbacks(mCancelAnimationRunnable); synchronized (mService) { if (mWindowManager.getRecentsAnimationController() == null) return; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2e6e348c51a4..e8d6540baaee 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1330,6 +1330,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHandler.post(mHiddenNavPanic); } + // Abort possibly stuck animations. + mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); + // Latch power key state to detect screenshot chord. if (interactive && !mScreenshotChordPowerKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { @@ -4362,6 +4365,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { * given the situation with the keyguard. */ void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) { + // Abort possibly stuck animations. + mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); + if (respectKeyguard) { if (isKeyguardShowingAndNotOccluded()) { // don't launch home if keyguard showing diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index ec0521dda40d..49d3588eb24c 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -649,6 +649,12 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { return Integer.toString(lens); } } + + /** + * Hint to window manager that the user has started a navigation action that should + * abort animations that have no timeout, in case they got stuck. + */ + void triggerAnimationFailsafe(); } /** Window has been added to the screen. */ diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 10188487fd9b..7274aee3c987 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -34,6 +34,7 @@ import android.app.WindowConfiguration; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArraySet; @@ -61,15 +62,17 @@ import java.util.ArrayList; * window manager when the animation is completed. In addition, window manager may also notify the * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.) */ -public class RecentsAnimationController { +public class RecentsAnimationController implements DeathRecipient { private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentsAnimationController" : TAG_WM; private static final boolean DEBUG = false; + private static final long FAILSAFE_DELAY = 1000; private final WindowManagerService mService; private final IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); private final int mDisplayId; + private final Runnable mFailsafeRunnable = this::cancelAnimation; // The recents component app token that is shown behind the visibile tasks private AppWindowToken mHomeAppToken; @@ -223,6 +226,13 @@ public class RecentsAnimationController { return; } + try { + mRunner.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + cancelAnimation(); + return; + } + // Adjust the wallpaper visibility for the showing home activity final AppWindowToken recentsComponentAppToken = dc.getHomeStack().getTopChild().getTopFullscreenAppToken(); @@ -296,6 +306,7 @@ public class RecentsAnimationController { // We've already canceled the animation return; } + mService.mH.removeCallbacks(mFailsafeRunnable); mCanceled = true; try { mRunner.onAnimationCanceled(); @@ -321,10 +332,21 @@ public class RecentsAnimationController { } mPendingAnimations.clear(); + mRunner.asBinder().unlinkToDeath(this, 0); + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); } + void scheduleFailsafe() { + mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY); + } + + @Override + public void binderDied() { + cancelAnimation(); + } + void checkAnimationReady(WallpaperController wallpaperController) { if (mPendingStart) { final boolean wallpaperReady = !isHomeAppOverWallpaper() diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 379a1a1528b4..3be7b23590e5 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -26,6 +26,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.Handler; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.os.SystemClock; import android.util.Slog; @@ -47,7 +48,7 @@ import java.util.ArrayList; /** * Helper class to run app animations in a remote process. */ -class RemoteAnimationController { +class RemoteAnimationController implements DeathRecipient { private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM; private static final long TIMEOUT_MS = 2000; @@ -56,12 +57,10 @@ class RemoteAnimationController { private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>(); private final Rect mTmpRect = new Rect(); private final Handler mHandler; - private FinishedCallback mFinishedCallback; + private final Runnable mTimeoutRunnable = this::cancelAnimation; - private final Runnable mTimeoutRunnable = () -> { - onAnimationFinished(); - invokeAnimationCancelled(); - }; + private FinishedCallback mFinishedCallback; + private boolean mCanceled; RemoteAnimationController(WindowManagerService service, RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) { @@ -90,7 +89,7 @@ class RemoteAnimationController { * Called when the transition is ready to be started, and all leashes have been set up. */ void goodToGo() { - if (mPendingAnimations.isEmpty()) { + if (mPendingAnimations.isEmpty() || mCanceled) { onAnimationFinished(); return; } @@ -107,8 +106,8 @@ class RemoteAnimationController { } mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { try { - mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, - mFinishedCallback); + mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0); + mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to start remote animation", e); onAnimationFinished(); @@ -120,6 +119,17 @@ class RemoteAnimationController { } } + private void cancelAnimation() { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return; + } + mCanceled = true; + } + onAnimationFinished(); + invokeAnimationCancelled(); + } + private void writeStartDebugStatement() { Slog.i(TAG, "Starting remote animation"); final StringWriter sw = new StringWriter(); @@ -154,6 +164,7 @@ class RemoteAnimationController { private void onAnimationFinished() { mHandler.removeCallbacks(mTimeoutRunnable); + mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0); synchronized (mService.mWindowMap) { releaseFinishedCallback(); mService.openSurfaceTransaction(); @@ -193,6 +204,11 @@ class RemoteAnimationController { mService.sendSetRunningRemoteAnimation(pid, running); } + @Override + public void binderDied() { + cancelAnimation(); + } + private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub { RemoteAnimationController mOuter; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0b5c0064d40f..22375375bc00 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -25,12 +25,10 @@ import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; -import static android.content.Intent.EXTRA_USER_HANDLE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myPid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.os.UserHandle.USER_NULL; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.DOCKED_INVALID; @@ -181,7 +179,6 @@ import android.util.Log; import android.util.MergedConfiguration; import android.util.Pair; import android.util.Slog; -import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; @@ -2793,6 +2790,11 @@ public class WindowManagerService extends IWindowManager.Stub mTaskSnapshotController.screenTurningOff(listener); } + @Override + public void triggerAnimationFailsafe() { + mH.sendEmptyMessage(H.ANIMATION_FAILSAFE); + } + /** * Starts deferring layout passes. Useful when doing multiple changes but to optimize * performance, only one layout pass should be done. This can be called multiple times, and @@ -4566,6 +4568,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57; public static final int SET_HAS_OVERLAY_UI = 58; public static final int SET_RUNNING_REMOTE_ANIMATION = 59; + public static final int ANIMATION_FAILSAFE = 60; /** * Used to denote that an integer field in a message will not be used. @@ -4984,6 +4987,14 @@ public class WindowManagerService extends IWindowManager.Stub mAmInternal.setRunningRemoteAnimation(msg.arg1, msg.arg2 == 1); } break; + case ANIMATION_FAILSAFE: { + synchronized (mWindowMap) { + if (mRecentsAnimationController != null) { + mRecentsAnimationController.scheduleFailsafe(); + } + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 553d65824c54..95361f03fe4b 100644 --- a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -17,14 +17,20 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import android.graphics.Point; import android.graphics.Rect; +import android.os.Binder; +import android.os.IInterface; +import android.platform.test.annotations.Presubmit; import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -50,7 +56,7 @@ import org.mockito.MockitoAnnotations; * atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest */ @SmallTest -@FlakyTest(detail = "Promote to presubmit if non-flakyness is established") +@Presubmit @RunWith(AndroidJUnit4.class) public class RemoteAnimationControllerTest extends WindowTestsBase { @@ -67,6 +73,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { public void setUp() throws Exception { super.setUp(); MockitoAnnotations.initMocks(this); + when(mMockRunner.asBinder()).thenReturn(new Binder()); mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); mAdapter.setCallingPid(123); sWm.mH.runWithScissors(() -> { @@ -166,7 +173,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testZeroAnimations() throws Exception { mController.goodToGo(); - verifyZeroInteractions(mMockRunner); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @Test @@ -175,7 +182,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mController.createAnimationAdapter(win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150)); mController.goodToGo(); - verifyZeroInteractions(mMockRunner); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @Test @@ -206,7 +213,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); win.mAppToken.removeImmediately(); mController.goodToGo(); - verifyZeroInteractions(mMockRunner); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); verify(mFinishedCallback).onAnimationFinished(eq(adapter)); } + + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { + verify(binder, atLeast(0)).asBinder(); + verifyNoMoreInteractions(binder); + } } |