From ef49a02eddb7341bc5031175dce3d343af9f4ad1 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Wed, 18 Aug 2021 17:21:04 +0800 Subject: [PATCH] Prevent activity being destroyed immediately if embedded The embedded activity was destroyed immediately when being finished because the next top activity was on the adjacent TaskFragment and was already visible. Therefore, the finishing animation was played before the organizer requested to finish the activity on the adjacent TaskFragment. Prevent the last activity of the embedded TaskFragment to be removed immediately if the organizer requested to. Bug: 189386466 Test: finish both activities on separate TaskFragments Change-Id: I916eddc4dcf0de3bc7ed7264296f441d1b7bb726 --- .../window/WindowContainerTransaction.java | 52 ++++++++++++++++++- .../JetpackTaskFragmentOrganizer.java | 22 ++++++-- .../extensions/organizer/SplitContainer.java | 24 ++++++--- .../extensions/organizer/SplitPresenter.java | 10 ++-- .../com/android/server/wm/ActivityRecord.java | 16 +++++- .../com/android/server/wm/TaskFragment.java | 33 ++++++++++++ .../server/wm/WindowOrganizerController.java | 15 ++++++ .../TaskFragmentOrganizerControllerTest.java | 3 +- 8 files changed, 156 insertions(+), 19 deletions(-) diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 342df4f9eee33..387837d82acf7 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -516,11 +516,13 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction setAdjacentTaskFragments( - @NonNull IBinder fragmentToken1, @Nullable IBinder fragmentToken2) { + @NonNull IBinder fragmentToken1, @Nullable IBinder fragmentToken2, + @Nullable TaskFragmentAdjacentOptions options) { final HierarchyOp hierarchyOp = new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS) .setContainer(fragmentToken1) .setReparentContainer(fragmentToken2) + .setLaunchOptions(options != null ? options.toBundle() : null) .build(); mHierarchyOps.add(hierarchyOp); return this; @@ -1298,4 +1300,52 @@ public final class WindowContainerTransaction implements Parcelable { } } } + + /** + * Helper class for building an options Bundle that can be used to set adjacent rules of + * TaskFragments. + * @hide + */ + public static class TaskFragmentAdjacentOptions { + private static final String DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL = + "android:transaction.adjacent.option.delay_primary_removal"; + private static final String DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL = + "android:transaction.adjacent.option.delay_secondary_removal"; + + private boolean mDelayPrimaryLastActivityRemoval; + private boolean mDelaySecondaryLastActivityRemoval; + + public TaskFragmentAdjacentOptions() { + } + + public TaskFragmentAdjacentOptions(@NonNull Bundle bundle) { + mDelayPrimaryLastActivityRemoval = bundle.getBoolean( + DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL); + mDelaySecondaryLastActivityRemoval = bundle.getBoolean( + DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL); + } + + public void setDelayPrimaryLastActivityRemoval(boolean delay) { + mDelayPrimaryLastActivityRemoval = delay; + } + + public void setDelaySecondaryLastActivityRemoval(boolean delay) { + mDelaySecondaryLastActivityRemoval = delay; + } + + public boolean isDelayPrimaryLastActivityRemoval() { + return mDelayPrimaryLastActivityRemoval; + } + + public boolean isDelaySecondaryLastActivityRemoval() { + return mDelaySecondaryLastActivityRemoval; + } + + Bundle toBundle() { + final Bundle b = new Bundle(); + b.putBoolean(DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL, mDelayPrimaryLastActivityRemoval); + b.putBoolean(DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL, mDelaySecondaryLastActivityRemoval); + return b; + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java index dd00189c3bf12..4206d0375e301 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java @@ -36,6 +36,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.window.extensions.embedding.SplitRule; import java.util.Map; import java.util.concurrent.Executor; @@ -100,7 +101,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, - @Nullable Bundle activityOptions) { + @Nullable Bundle activityOptions, @NonNull SplitRule rule) { final IBinder ownerToken = launchingActivity.getActivityToken(); // Create or resize the launching TaskFragment. @@ -117,7 +118,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { activityOptions); // Set adjacent to each other so that the containers below will be invisible. - wct.setAdjacentTaskFragments(launchingFragmentToken, secondaryFragmentToken); + setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule); } /** @@ -127,7 +128,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { */ void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) { resizeTaskFragment(wct, fragmentToken, new Rect()); - wct.setAdjacentTaskFragments(fragmentToken, null); + setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); } /** @@ -187,6 +188,21 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions); } + void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, + @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) { + WindowContainerTransaction.TaskFragmentAdjacentOptions adjacentOptions = null; + final boolean finishSecondaryWithPrimary = + splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule); + final boolean finishPrimaryWithSecondary = + splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule); + if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) { + adjacentOptions = new WindowContainerTransaction.TaskFragmentAdjacentOptions(); + adjacentOptions.setDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary); + adjacentOptions.setDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary); + } + wct.setAdjacentTaskFragments(primary, secondary, adjacentOptions); + } + TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken, Rect bounds, @WindowingMode int windowingMode) { if (mFragmentInfos.containsKey(fragmentToken)) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java index 8fd710a7986d3..4fd2126dfa27d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitContainer.java @@ -39,16 +39,10 @@ class SplitContainer { mSecondaryContainer = secondaryContainer; mSplitRule = splitRule; - final boolean isPlaceholderContainer = isPlaceholderContainer(); - final boolean shouldFinishPrimaryWithSecondary = (mSplitRule instanceof SplitPairRule) - && ((SplitPairRule) mSplitRule).shouldFinishPrimaryWithSecondary(); - final boolean shouldFinishSecondaryWithPrimary = (mSplitRule instanceof SplitPairRule) - && ((SplitPairRule) mSplitRule).shouldFinishSecondaryWithPrimary(); - - if (shouldFinishPrimaryWithSecondary || isPlaceholderContainer) { + if (shouldFinishPrimaryWithSecondary(splitRule)) { mSecondaryContainer.addActivityToFinishOnExit(primaryActivity); } - if (shouldFinishSecondaryWithPrimary || isPlaceholderContainer) { + if (shouldFinishSecondaryWithPrimary(splitRule)) { mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer); } } @@ -71,4 +65,18 @@ class SplitContainer { boolean isPlaceholderContainer() { return (mSplitRule instanceof SplitPlaceholderRule); } + + static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) { + final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule; + final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule) + && ((SplitPairRule) splitRule).shouldFinishPrimaryWithSecondary(); + return shouldFinishPrimaryWithSecondary || isPlaceholderContainer; + } + + static boolean shouldFinishSecondaryWithPrimary(@NonNull SplitRule splitRule) { + final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule; + final boolean shouldFinishSecondaryWithPrimary = (splitRule instanceof SplitPairRule) + && ((SplitPairRule) splitRule).shouldFinishSecondaryWithPrimary(); + return shouldFinishSecondaryWithPrimary || isPlaceholderContainer; + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java index a7bce20cae8b8..ac85ac8cbc344 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java @@ -109,8 +109,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryContainer.setLastRequestedBounds(secondaryRectBounds); // Set adjacent to each other so that the containers below will be invisible. - wct.setAdjacentTaskFragments( - primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken()); + setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), + secondaryContainer.getTaskFragmentToken(), rule); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -144,8 +144,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryActivity, secondaryRectBounds, primaryContainer); // Set adjacent to each other so that the containers below will be invisible. - wct.setAdjacentTaskFragments( - primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken()); + setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), + secondaryContainer.getTaskFragmentToken(), rule); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -212,7 +212,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { rule); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, - activityIntent, activityOptions); + activityIntent, activityOptions, rule); applyTransaction(wct); primaryContainer.setLastRequestedBounds(primaryRectBounds); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b5fd111f6d779..60e7d109ec2ed 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3234,6 +3234,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/137329632): find the next activity directly underneath this one, not just anywhere final ActivityRecord next = getDisplayArea().topRunningActivity( true /* considerKeyguardState */); + + // If the finishing activity is the last activity of a organized TaskFragment and has an + // adjacent TaskFragment, check if the activity removal should be delayed. + boolean delayRemoval = false; + final TaskFragment taskFragment = getTaskFragment(); + if (next != null && taskFragment != null && taskFragment.isEmbedded()) { + final TaskFragment organized = taskFragment.getOrganizedTaskFragment(); + final TaskFragment adjacent = + organized != null ? organized.getAdjacentTaskFragment() : null; + if (adjacent != null && organized.topRunningActivity() == null) { + delayRemoval = organized.isDelayLastActivityRemoval(); + } + } + // isNextNotYetVisible is to check if the next activity is invisible, or it has been // requested to be invisible but its windows haven't reported as invisible. If so, it // implied that the current finishing activity should be added into stopping list rather @@ -3248,7 +3262,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (isCurrentVisible) { - if (isNextNotYetVisible) { + if (isNextNotYetVisible || delayRemoval) { // Add this activity to the list of stopping activities. It will be processed and // destroyed when the next activity reports idle. addToStopping(false /* scheduleIdle */, false /* idleDelayed */, diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 70b0ccd764744..6e4792bdbd31a 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -220,6 +220,13 @@ class TaskFragment extends WindowContainer { @Nullable private IBinder mFragmentToken; + /** + * Whether to delay the last activity of TaskFragment being immediately removed while finishing. + * This should only be set on a embedded TaskFragment, where the organizer can have the + * opportunity to perform other actions or animations. + */ + private boolean mDelayLastActivityRemoval; + /** * The PID of the organizer that created this TaskFragment. It should be the same as the PID * of {@link android.window.TaskFragmentCreationParams#getOwnerToken()}. @@ -319,6 +326,7 @@ class TaskFragment extends WindowContainer { mAdjacentTaskFragment.mAdjacentTaskFragment = null; } mAdjacentTaskFragment = null; + mDelayLastActivityRemoval = false; } void setTaskFragmentOrganizer(TaskFragmentOrganizerToken organizer, int pid) { @@ -421,6 +429,20 @@ class TaskFragment extends WindowContainer { return false; } + /** + * Returns the TaskFragment that is being organized, which could be this or the ascendant + * TaskFragment. + */ + @Nullable + TaskFragment getOrganizedTaskFragment() { + if (mTaskFragmentOrganizer != null) { + return this; + } + + TaskFragment parentTaskFragment = getParent().asTaskFragment(); + return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null; + } + /** * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}. */ @@ -2086,6 +2108,17 @@ class TaskFragment extends WindowContainer { }); } + void setDelayLastActivityRemoval(boolean delay) { + if (!mIsEmbedded) { + Slog.w(TAG, "Set delaying last activity removal on a non-embedded TF."); + } + mDelayLastActivityRemoval = delay; + } + + boolean isDelayLastActivityRemoval() { + return mDelayLastActivityRemoval; + } + boolean shouldDeferRemoval() { if (!hasChild()) { return false; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index aa147c4c87123..45c47bab11751 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -741,6 +741,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } tf1.setAdjacentTaskFragment(tf2); + + final Bundle bundle = hop.getLaunchOptions(); + final WindowContainerTransaction.TaskFragmentAdjacentOptions adjacentOptions = + bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentOptions( + bundle) : null; + if (adjacentOptions == null) { + break; + } + + tf1.setDelayLastActivityRemoval( + adjacentOptions.isDelayPrimaryLastActivityRemoval()); + if (tf2 != null) { + tf2.setDelayLastActivityRemoval( + adjacentOptions.isDelaySecondaryLastActivityRemoval()); + } break; } return effects; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index c35f317029612..5c79f5ca2c668 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -326,7 +326,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTransaction.startActivityInTaskFragment( mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */); mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class)); - mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class)); + mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class), + null /* options */); // It is expected to fail for the mock TaskFragmentCreationParams. It is ok as we are // testing the security check here.