From fdd935b58ad99157d9777c020fa2961fecc62a8c Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Thu, 5 Jan 2023 06:43:54 +0000 Subject: [PATCH] Adjust cancel back navigation conditions. There will cancel back navigation when navigation window lose focus, for this change we also monitor when transition happens on navigation target. There are two shortcomings for monitor the navigation window lose focus. First is that it may cause misjudgment in multi-window mode. The navigation window can be the one in split screen, but if another task start a transition(e.g. start/finish activity), that should be irrelevant to the navigation target, but that task could gain focus for a short time. Another thing is that while back animating, the window can lose focus if the close animation applys alpha = 0 to the closing target, but that should be acceptable for animation. So for window focus change, there can only used to monitor when the new focus window is a stand alone winodw, or when the parent activity is navigating. And in order to cancel gesture for any change above window-level, monitor if a new transition happens which contains the navigation window. Bug: 131727607 Test: atest BackAnimationControllerTest BackNavigationControllerTests Test: manual, into split screen, start back animation on top task, and start an activity from bottom task, verify the back animation won't be canceled. Test: manual, during back-to-home animating, start another activity in the same task, and verify shell should cancel the navigation. Test: manual, verify ANR window can cancel back animation. Test: manual, animation won't be canceled while playing a cross activity animation with alpha=0. Change-Id: Ia7acd041ec7afd3d94482f0950003fea499ce82b --- .../android/app/IActivityTaskManager.aidl | 7 +- .../shell/back/BackAnimationController.java | 35 +++-- .../back/BackAnimationControllerTest.java | 5 +- .../server/wm/ActivityTaskManagerService.java | 6 +- .../server/wm/AppTransitionController.java | 2 +- .../server/wm/BackNavigationController.java | 142 ++++++++++++++---- .../com/android/server/wm/DisplayContent.java | 1 + .../android/server/wm/EventLogTags.logtags | 2 + .../wm/BackNavigationControllerTests.java | 80 +++++++++- 9 files changed, 217 insertions(+), 63 deletions(-) diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index e97e711167b21..d62e15a94f89b 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -65,12 +65,12 @@ import android.os.Debug; import android.os.IBinder; import android.os.IProgressListener; import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import android.view.IRecentsAnimationRunner; import android.view.IRemoteAnimationRunner; -import android.view.IWindowFocusObserver; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.window.IWindowOrganizerController; @@ -349,12 +349,13 @@ interface IActivityTaskManager { /** * Prepare the back navigation in the server. This setups the leashed for sysui to animate * the back gesture and returns the data needed for the animation. - * @param focusObserver a remote callback to nofify shell when the focused window lost focus. + * @param navigationObserver a remote callback to nofify shell when the focused window is gone, + or an unexpected transition has happened on the navigation target. * @param adaptor a remote animation to be run for the back navigation plays the animation. * @return Returns the back navigation info. */ android.window.BackNavigationInfo startBackNavigation( - in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor); + in RemoteCallback navigationObserver, in BackAnimationAdapter adaptor); /** * registers a callback to be invoked when the screen is captured. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index ee5d205498c30..f853b78ef3e96 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -29,8 +29,9 @@ import android.content.Context; import android.database.ContentObserver; import android.hardware.input.InputManager; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -39,7 +40,6 @@ import android.provider.Settings.Global; import android.util.Log; import android.util.SparseArray; import android.view.IRemoteAnimationRunner; -import android.view.IWindowFocusObserver; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -121,23 +121,22 @@ public class BackAnimationController implements RemoteCallable { - if (!mBackGestureStarted || mPostCommitAnimationInProgress) { - // If an uninterruptible animation is already in progress, we should ignore - // this due to it may cause focus lost. (alpha = 0) - return; + final RemoteCallback mNavigationObserver = new RemoteCallback( + new RemoteCallback.OnResultListener() { + @Override + public void onResult(@Nullable Bundle result) { + mShellExecutor.execute(() -> { + if (!mBackGestureStarted || mPostCommitAnimationInProgress) { + // If an uninterruptible animation is already in progress, we should + // ignore this due to it may cause focus lost. (alpha = 0) + return; + } + ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone."); + setTriggerBack(false); + onGestureFinished(false); + }); } - ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus."); - setTriggerBack(false); - onGestureFinished(false); }); - } - }; private final BackAnimationBackground mAnimationBackground; @@ -351,7 +350,7 @@ public class BackAnimationController implements RemoteCallable tmpOpenApps = mDisplayContent.mOpeningApps; ArraySet tmpCloseApps = mDisplayContent.mClosingApps; - if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) { + if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringTransition()) { tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps); tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps); if (mDisplayContent.mAtmService.mBackNavigationController diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index bc5f67bb50d04..a229fc5b00d37 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -41,7 +41,6 @@ import android.os.SystemProperties; import android.util.ArraySet; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import android.view.IWindowFocusObserver; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.WindowInsets; @@ -66,11 +65,11 @@ import java.util.function.Consumer; class BackNavigationController { private static final String TAG = "BackNavigationController"; private WindowManagerService mWindowManagerService; - private IWindowFocusObserver mFocusObserver; 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 mTmpOpenApps = new ArrayList<>(); @@ -86,6 +85,11 @@ class BackNavigationController { return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0; } + // Notify focus window changed + void onFocusChanged(WindowState newFocus) { + mNavigationMonitor.onFocusWindowChanged(newFocus); + } + /** * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming * back gesture animation. @@ -96,13 +100,12 @@ class BackNavigationController { */ @VisibleForTesting @Nullable - BackNavigationInfo startBackNavigation( - IWindowFocusObserver observer, BackAnimationAdapter adapter) { + BackNavigationInfo startBackNavigation(@NonNull RemoteCallback navigationObserver, + BackAnimationAdapter adapter) { if (!sPredictBackEnable) { return null; } final WindowManagerService wmService = mWindowManagerService; - mFocusObserver = observer; int backType = BackNavigationInfo.TYPE_UNDEFINED; @@ -202,9 +205,7 @@ class BackNavigationController { backType = BackNavigationInfo.TYPE_CALLBACK; } infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback()); - if (mFocusObserver != null) { - window.registerFocusObserver(mFocusObserver); - } + mNavigationMonitor.startMonitor(window, navigationObserver); } ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, " @@ -228,10 +229,8 @@ class BackNavigationController { || currentActivity.isActivityTypeHome() || currentActivity.mHasSceneTransition) { infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK); - final WindowState finalFocusedWindow = window; infoBuilder.setOnBackNavigationDone(new RemoteCallback(result -> - onBackNavigationDone(result, finalFocusedWindow, - BackNavigationInfo.TYPE_CALLBACK))); + onBackNavigationDone(result, BackNavigationInfo.TYPE_CALLBACK))); mLastBackType = BackNavigationInfo.TYPE_CALLBACK; return infoBuilder.build(); } @@ -334,16 +333,19 @@ class BackNavigationController { WindowContainer finalRemovedWindowContainer = removedWindowContainer; if (finalRemovedWindowContainer != null) { final int finalBackType = backType; - final WindowState finalFocusedWindow = window; RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone( - result, finalFocusedWindow, finalBackType)); + result, finalBackType)); infoBuilder.setOnBackNavigationDone(onBackNavigationDone); } mLastBackType = backType; return infoBuilder.build(); } - boolean isWaitBackTransition() { + boolean isMonitoringTransition() { + return isWaitBackTransition() || mNavigationMonitor.isMonitoring(); + } + + private boolean isWaitBackTransition() { return mAnimationHandler.mComposed && mAnimationHandler.mWaitTransition; } @@ -363,11 +365,23 @@ class BackNavigationController { */ boolean removeIfContainsBackAnimationTargets(ArraySet openApps, ArraySet closeApps) { - if (!isWaitBackTransition()) { + if (!isMonitoringTransition()) { return false; } mTmpCloseApps.addAll(closeApps); - boolean result = false; + final boolean matchAnimationTargets = removeIfWaitForBackTransition(openApps, closeApps); + if (!matchAnimationTargets) { + mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps); + } + mTmpCloseApps.clear(); + return matchAnimationTargets; + } + + boolean removeIfWaitForBackTransition(ArraySet openApps, + ArraySet 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)) { @@ -386,10 +400,76 @@ class BackNavigationController { closeApps.removeAt(i); } } - result = true; + return true; + } + return false; + } + + private static 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 stopMonitor() { + mNavigatingWindow = null; + mObserver = null; + } + + boolean isMonitoring() { + return mNavigatingWindow != null && mObserver != null; + } + + /** + * 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 (!isMonitoring() || !atSameDisplay(newFocus)) { + return; + } + // Keep navigating if either new focus == navigating window or null. + if (newFocus != null && newFocus != mNavigatingWindow + && (newFocus.mActivityRecord == null + || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) { + EventLogTags.writeWmBackNaviCanceled("focusWindowChanged"); + mObserver.sendResult(null /* result */); + } + } + + /** + * Notify an unexpected transition has happened during back navigation. + */ + private void onTransitionReadyWhileNavigate(ArrayList opening, + ArrayList closing) { + if (!isMonitoring()) { + return; + } + final ArrayList all = new ArrayList<>(opening); + all.addAll(closing); + for (WindowContainer app : all) { + if (app.hasChild(mNavigatingWindow)) { + EventLogTags.writeWmBackNaviCanceled("transitionHappens"); + mObserver.sendResult(null /* result */); + break; + } + } + + } + + private boolean atSameDisplay(WindowState newFocus) { + final int navigatingDisplayId = mNavigatingWindow.getDisplayId(); + return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId; } - mTmpCloseApps.clear(); - return result; } // For shell transition @@ -403,8 +483,7 @@ class BackNavigationController { * animations, and shouldn't join next transition. */ boolean containsBackAnimationTargets(Transition transition) { - if (!mAnimationHandler.mComposed - || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) { + if (!isMonitoringTransition()) { return false; } final ArraySet targets = transition.mParticipants; @@ -420,19 +499,19 @@ class BackNavigationController { mTmpCloseApps.add(wc); } } - final boolean result = mAnimationHandler.containsBackAnimationTargets( - mTmpOpenApps, mTmpCloseApps); - if (result) { - mAnimationHandler.mOpenTransitionTargetMatch = - mAnimationHandler.containTarget(mTmpOpenApps, true); + final boolean matchAnimationTargets = isWaitBackTransition() + && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK) + && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps); + if (!matchAnimationTargets) { + mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps); } mTmpOpenApps.clear(); mTmpCloseApps.clear(); - return result; + return matchAnimationTargets; } boolean isMonitorTransitionTarget(WindowContainer wc) { - if (!mAnimationHandler.mComposed || !mAnimationHandler.mWaitTransition) { + if (!isWaitBackTransition()) { return false; } return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */); @@ -951,16 +1030,13 @@ class BackNavigationController { } } - private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) { + private void onBackNavigationDone(Bundle result, int backType) { boolean triggerBack = result != null && result.getBoolean( BackNavigationInfo.KEY_TRIGGER_BACK); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, " + "triggerBack=%b", backType, triggerBack); - if (mFocusObserver != null) { - focusedWindow.unregisterFocusObserver(mFocusObserver); - mFocusObserver = null; - } + mNavigationMonitor.stopMonitor(); mBackAnimationInProgress = false; mShowWallpaper = false; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8cd22fe71db5e..b7f3fdd86b21b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3809,6 +3809,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } getDisplayPolicy().focusChangedLw(oldFocus, newFocus); + mAtmService.mBackNavigationController.onFocusChanged(newFocus); if (imWindowChanged && oldFocus != mInputMethodWindow) { // Focus of the input method window changed. Perform layout if needed. diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags index 031e022f0746c..594929bea77a2 100644 --- a/services/core/java/com/android/server/wm/EventLogTags.logtags +++ b/services/core/java/com/android/server/wm/EventLogTags.logtags @@ -66,6 +66,8 @@ option java_package com.android.server.wm # bootanim finished: 31007 wm_boot_animation_done (time|2|3) +# Back navigation. +31100 wm_back_navi_canceled (Reason|3) # IME surface parent is updated. 32003 imf_update_ime_parent (surface name|3) diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index ff5ede7a316ab..56461f007351b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -17,8 +17,11 @@ package com.android.server.wm; import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.window.BackNavigationInfo.typeToString; @@ -42,8 +45,10 @@ import android.app.ActivityOptions; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Bundle; +import android.os.RemoteCallback; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.view.WindowManager; import android.window.BackAnimationAdapter; import android.window.BackMotionEvent; @@ -320,6 +325,64 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertThat(backNavigationInfo).isNull(); } + @Test + public void testTransitionHappensCancelNavigation() { + // Create a floating task and a fullscreen task, then navigating on fullscreen task. + // The navigation should not been cancelled when transition happens on floating task, and + // only be cancelled when transition happens on the navigating task. + final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, + ACTIVITY_TYPE_STANDARD); + final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask); + + final Task fullscreenTask = createTopTaskWithActivity(); + withSystemCallback(fullscreenTask); + final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity(); + + final CountDownLatch navigationObserver = new CountDownLatch(1); + startBackNavigation(navigationObserver); + + final ArraySet opening = new ArraySet<>(); + final ArraySet closing = new ArraySet<>(); + final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask); + opening.add(secondFloatingActivity); + closing.add(baseFloatingActivity); + mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); + assertEquals("Transition happen on an irrelevant task, callback should not been called", + 1, navigationObserver.getCount()); + + // Create a new activity above navigation target, the transition should cancel navigation. + final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask); + opening.clear(); + closing.clear(); + opening.add(topFullscreenActivity); + closing.add(baseFullscreenActivity); + mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); + assertEquals("Transition happen on navigation task, callback should have been called", + 0, navigationObserver.getCount()); + } + + @Test + public void testWindowFocusChangeCancelNavigation() { + Task task = createTopTaskWithActivity(); + withSystemCallback(task); + WindowState focusWindow = task.getTopVisibleAppMainWindow(); + final CountDownLatch navigationObserver = new CountDownLatch(1); + startBackNavigation(navigationObserver); + + mBackNavigationController.onFocusChanged(null); + assertEquals("change focus to null, callback should not have been called", + 1, navigationObserver.getCount()); + mBackNavigationController.onFocusChanged(focusWindow); + assertEquals("change focus back, callback should not have been called", + 1, navigationObserver.getCount()); + + WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow"); + addToWindowMap(newWindow, true); + mBackNavigationController.onFocusChanged(newWindow); + assertEquals("Focus change, callback should have been called", + 0, navigationObserver.getCount()); + } + private IOnBackInvokedCallback withSystemCallback(Task task) { IOnBackInvokedCallback callback = createOnBackInvokedCallback(); task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( @@ -336,7 +399,14 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Nullable private BackNavigationInfo startBackNavigation() { - return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter); + return mBackNavigationController.startBackNavigation( + createNavigationObserver(null), mBackAnimationAdapter); + } + + @Nullable + private BackNavigationInfo startBackNavigation(CountDownLatch navigationObserverLatch) { + return mBackNavigationController.startBackNavigation( + createNavigationObserver(navigationObserverLatch), mBackAnimationAdapter); } @NonNull @@ -371,6 +441,14 @@ public class BackNavigationControllerTests extends WindowTestsBase { }; } + private RemoteCallback createNavigationObserver(CountDownLatch latch) { + return new RemoteCallback(result -> { + if (latch != null) { + latch.countDown(); + } + }); + } + private Task initHomeActivity() { final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); task.forAllLeafTasks((t) -> {