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 acf17e6700412..b3fff1d052632 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 dfa1a62ff506b..86c4e0f7c6c65 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) -> {