Merge "Move the task to back while pressing back on the last activity" into tm-qpr-dev

This commit is contained in:
Louis Chang
2022-11-17 00:21:08 +00:00
committed by Android (Google) Code Review
12 changed files with 187 additions and 41 deletions

View File

@@ -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);
}
}
/**

View File

@@ -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();
}

View File

@@ -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. */

View File

@@ -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}
*/

View File

@@ -739,6 +739,29 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
/**
* 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)}.
@@ -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.
@@ -1430,6 +1454,11 @@ public final class WindowContainerTransaction implements Parcelable {
return mReparent;
}
@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

View File

@@ -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)) {

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -221,6 +221,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Nullable
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.
*/
@@ -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) {

View File

@@ -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) {

View File

@@ -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();