diff options
12 files changed, 187 insertions, 41 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 81c3e8957cd1..d24b677b1d72 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1684,7 +1684,7 @@ public class Activity extends ContextThemeWrapper .isOnBackInvokedCallbackEnabled(this); if (aheadOfTimeBack) { // Add onBackPressed as default back behavior. - mDefaultBackCallback = this::navigateBack; + mDefaultBackCallback = this::onBackInvoked; getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); } } @@ -4002,22 +4002,19 @@ public class Activity extends ContextThemeWrapper if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) { return; } - navigateBack(); + onBackInvoked(); } - private void navigateBack() { - if (!isTaskRoot()) { - // If the activity is not the root of the task, allow finish to proceed normally. - finishAfterTransition(); - return; - } - // Inform activity task manager that the activity received a back press while at the - // root of the task. This call allows ActivityTaskManager to intercept or move the task - // to the back. - ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken, + private void onBackInvoked() { + // Inform activity task manager that the activity received a back press. + // This call allows ActivityTaskManager to intercept or move the task + // to the back when needed. + ActivityClient.getInstance().onBackPressed(mToken, new RequestFinishCallback(new WeakReference<>(this))); - getAutofillClientController().onActivityBackPressed(mIntent); + if (isTaskRoot()) { + getAutofillClientController().onActivityBackPressed(mIntent); + } } /** diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index d1e6780e3618..4cf48abc2ed3 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -525,9 +525,9 @@ public class ActivityClient { } } - void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) { + void onBackPressed(IBinder token, IRequestFinishCallback callback) { try { - getActivityClientController().onBackPressedOnTaskRoot(token, callback); + getActivityClientController().onBackPressed(token, callback); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 9aa67bc51182..62481ba8f251 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -145,10 +145,9 @@ interface IActivityClientController { void unregisterRemoteAnimations(in IBinder token); /** - * Reports that an Activity received a back key press when there were no additional activities - * on the back stack. + * Reports that an Activity received a back key press. */ - oneway void onBackPressedOnTaskRoot(in IBinder activityToken, + oneway void onBackPressed(in IBinder activityToken, in IRequestFinishCallback callback); /** Reports that the splash screen view has attached to activity. */ diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl index 22c20c840e99..72426df84b75 100644 --- a/core/java/android/app/IRequestFinishCallback.aidl +++ b/core/java/android/app/IRequestFinishCallback.aidl @@ -18,7 +18,7 @@ package android.app; /** * This callback allows ActivityTaskManager to ask the calling Activity - * to finish in response to a call to onBackPressedOnTaskRoot. + * to finish in response to a call to onBackPressed. * * {@hide} */ diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index fbf8d8bf9fe4..15be5f563d88 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -740,6 +740,29 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Sets the TaskFragment {@code container} to have a companion TaskFragment {@code companion}. + * This indicates that the organizer will remove the TaskFragment when the companion + * TaskFragment is removed. + * + * @param container the TaskFragment container + * @param companion the companion TaskFragment. If it is {@code null}, the transaction will + * reset the companion TaskFragment. + * @hide + */ + @NonNull + public WindowContainerTransaction setCompanionTaskFragment(@NonNull IBinder container, + @Nullable IBinder companion) { + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT) + .setContainer(container) + .setReparentContainer(companion) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** * Sets/removes the always on top flag for this {@code windowContainer}. See * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. * Please note that this method is only intended to be used for a @@ -1217,6 +1240,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19; public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20; public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21; + public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1431,6 +1455,11 @@ public final class WindowContainerTransaction implements Parcelable { } @NonNull + public IBinder getCompanionContainer() { + return mReparent; + } + + @NonNull public IBinder getCallingActivity() { return mReparent; } @@ -1540,6 +1569,9 @@ public final class WindowContainerTransaction implements Parcelable { return "{RemoveTask: task=" + mContainer + "}"; case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "{finishActivity: activity=" + mContainer + "}"; + case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: + return "{setCompanionTaskFragment: container = " + mContainer + " companion = " + + mReparent + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 74303e2fab7c..9d841ea2e55d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -18,6 +18,12 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; +import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary; + import android.app.Activity; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; @@ -140,6 +146,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule); + setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule, + false /* isStacked */); } /** @@ -215,6 +223,28 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setAdjacentTaskFragments(primary, secondary, adjacentParams); } + void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, + @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule, + boolean isStacked) { + final boolean finishPrimaryWithSecondary; + if (isStacked) { + finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked( + getFinishPrimaryWithSecondaryBehavior(splitRule)); + } else { + finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule); + } + wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null); + + final boolean finishSecondaryWithPrimary; + if (isStacked) { + finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked( + getFinishSecondaryWithPrimaryBehavior(splitRule)); + } else { + finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule); + } + wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null); + } + TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) { if (mFragmentInfos.containsKey(fragmentToken)) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 362f1fa096cc..cb470bac5c9a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -371,13 +371,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull SplitAttributes splitAttributes) { // Clear adjacent TaskFragments if the container is shown in fullscreen, or the // secondaryContainer could not be finished. - if (!shouldShowSplit(splitAttributes)) { + boolean isStacked = !shouldShowSplit(splitAttributes); + if (isStacked) { setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), null /* secondary */, null /* splitRule */); } else { setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken(), splitRule); } + setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(), + secondaryContainer.getTaskFragmentToken(), splitRule, isStacked); } /** @@ -489,8 +492,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { || splitContainer.getSecondaryContainer().getInfo() == null) { return RESULT_EXPAND_FAILED_NO_TF_INFO; } - expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken()); - expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken()); + final IBinder primaryToken = + splitContainer.getPrimaryContainer().getTaskFragmentToken(); + final IBinder secondaryToken = + splitContainer.getSecondaryContainer().getTaskFragmentToken(); + expandTaskFragment(wct, primaryToken); + expandTaskFragment(wct, secondaryToken); + // Set the companion TaskFragment when the two containers stacked. + setCompanionTaskFragment(wct, primaryToken, secondaryToken, + splitContainer.getSplitRule(), true /* isStacked */); return RESULT_EXPANDED; } return RESULT_NOT_EXPANDED; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index eca2e7441f29..741141d9e463 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1347,8 +1347,38 @@ class ActivityClientController extends IActivityClientController.Stub { } } + /** + * Return {@code true} when the given Activity is a relative Task root. That is, the rest of + * the Activities in the Task should be finished when it finishes. Otherwise, return {@code + * false}. + */ + private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) { + // Not a relative root if the given Activity is not the root Activity of its TaskFragment. + final TaskFragment taskFragment = r.getTaskFragment(); + if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r, + false /* traverseTopToBottom */)) { + return false; + } + + // The given Activity is the relative Task root if its TaskFragment is a companion + // TaskFragment to the taskRoot (i.e. the taskRoot TF will be finished together). + return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment; + } + + private boolean isTopActivityInTaskFragment(ActivityRecord activity) { + return activity.getTaskFragment().topRunningActivity() == activity; + } + + private void requestCallbackFinish(IRequestFinishCallback callback) { + try { + callback.requestFinish(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to invoke request finish callback", e); + } + } + @Override - public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) { + public void onBackPressed(IBinder token, IRequestFinishCallback callback) { final long origId = Binder.clearCallingIdentity(); try { final Intent baseActivityIntent; @@ -1358,20 +1388,29 @@ class ActivityClientController extends IActivityClientController.Stub { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r == null) return; - if (mService.mWindowOrganizerController.mTaskOrganizerController + final Task task = r.getTask(); + final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/, + true /*setToBottomIfNone*/); + final boolean isTaskRoot = r == root; + if (isTaskRoot) { + if (mService.mWindowOrganizerController.mTaskOrganizerController .handleInterceptBackPressedOnTaskRoot(r.getRootTask())) { - // This task is handled by a task organizer that has requested the back pressed - // callback. + // This task is handled by a task organizer that has requested the back + // pressed callback. + return; + } + } else if (!isRelativeTaskRootActivity(r, root)) { + // Finish the Activity if the activity is not the task root or relative root. + requestCallbackFinish(callback); return; } - final Task task = r.getTask(); - isLastRunningActivity = task.topRunningActivity() == r; + isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r); - final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity); - baseActivityIntent = isBaseActivity ? r.intent : null; + final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity); + baseActivityIntent = isBaseActivity ? root.intent : null; - launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME); + launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME); } // If the activity is one of the main entry points for the application, then we should @@ -1386,16 +1425,12 @@ class ActivityClientController extends IActivityClientController.Stub { if (baseActivityIntent != null && isLastRunningActivity && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) || isLauncherActivity(baseActivityIntent.getComponent()))) { - moveActivityTaskToBack(token, false /* nonRoot */); + moveActivityTaskToBack(token, true /* nonRoot */); return; } // The default option for handling the back button is to finish the Activity. - try { - callback.requestFinish(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to invoke request finish callback", e); - } + requestCallbackFinish(callback); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 27f8c5c2aa11..fc15890847b5 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6874,6 +6874,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (r == null || r.getParent() == null) { return INVALID_TASK_ID; } + return getTaskForActivityLocked(r, onlyRoot); + } + + static int getTaskForActivityLocked(ActivityRecord r, boolean onlyRoot) { final Task task = r.task; if (onlyRoot && r.compareTo(task.getRootActivity( false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)) > 0) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 8f20629d5560..66ba556e2e66 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -222,6 +222,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { private TaskFragment mAdjacentTaskFragment; /** + * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually + * adjacent to this one, but this TaskFragment will be removed by the organizer if the + * companion TaskFragment is removed. + */ + @Nullable + private TaskFragment mCompanionTaskFragment; + + /** * Prevents duplicate calls to onTaskAppeared. */ boolean mTaskFragmentAppearedSent; @@ -394,6 +402,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } + void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) { + mCompanionTaskFragment = companionTaskFragment; + } + + TaskFragment getCompanionTaskFragment() { + return mCompanionTaskFragment; + } + void resetAdjacentTaskFragment() { // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 007628f49651..9cdb2e05816d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -39,6 +39,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; @@ -1117,6 +1118,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub effects |= sanitizeAndApplyHierarchyOp(wc, hop); break; } + case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getContainer(); + final IBinder companionToken = hop.getCompanionContainer(); + final TaskFragment fragment = mLaunchTaskFragments.get(fragmentToken); + final TaskFragment companion = companionToken != null ? mLaunchTaskFragments.get( + companionToken) : null; + if (fragment == null || !fragment.isAttached()) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to set companion on invalid fragment tokens"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, fragment, type, + exception); + break; + } + fragment.setCompanionTaskFragment(companion); + break; + } default: { // The other operations may change task order so they are skipped while in lock // task mode. The above operations are still allowed because they don't move @@ -1654,6 +1671,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: enforceTaskFragmentOrganized(func, hop.getNewParent(), organizer); break; + case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: + enforceTaskFragmentOrganized(func, hop.getContainer(), organizer); + if (hop.getCompanionContainer() != null) { + enforceTaskFragmentOrganized(func, hop.getCompanionContainer(), organizer); + } + break; case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: enforceTaskFragmentOrganized(func, hop.getContainer(), organizer); if (hop.getAdjacentRoot() != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 24fc93aa644e..8deb2825c4f9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1179,7 +1179,7 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(rootTask2.isOrganized()); // Verify a back pressed does not call the organizer - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, + mWm.mAtmService.mActivityClientController.onBackPressed(activity.token, new IRequestFinishCallback.Default()); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -1190,7 +1190,7 @@ public class WindowOrganizerTests extends WindowTestsBase { rootTask.mRemoteToken.toWindowContainerToken(), true); // Verify now that the back press does call the organizer - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, + mWm.mAtmService.mActivityClientController.onBackPressed(activity.token, new IRequestFinishCallback.Default()); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -1201,7 +1201,7 @@ public class WindowOrganizerTests extends WindowTestsBase { rootTask.mRemoteToken.toWindowContainerToken(), false); // Verify now that the back press no longer calls the organizer - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, + mWm.mAtmService.mActivityClientController.onBackPressed(activity.token, new IRequestFinishCallback.Default()); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -1407,7 +1407,7 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mWindowPlacerLocked.deferLayout(); rootTask.removeImmediately(); - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token, + mWm.mAtmService.mActivityClientController.onBackPressed(record.token, new IRequestFinishCallback.Default()); waitUntilHandlersIdle(); |