Loading core/java/android/window/TaskFragmentTransaction.java +27 −0 Original line number Diff line number Diff line Loading @@ -189,6 +189,10 @@ public final class TaskFragmentTransaction implements Parcelable { @Nullable private IBinder mActivityToken; /** @see #setOtherActivityToken(IBinder) */ @Nullable private IBinder mOtherActivityToken; @Nullable private TaskFragmentParentInfo mTaskFragmentParentInfo; Loading @@ -210,6 +214,7 @@ public final class TaskFragmentTransaction implements Parcelable { mActivityToken = in.readStrongBinder(); mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR); mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR); mOtherActivityToken = in.readStrongBinder(); } @Override Loading @@ -224,6 +229,7 @@ public final class TaskFragmentTransaction implements Parcelable { dest.writeStrongBinder(mActivityToken); dest.writeTypedObject(mTaskFragmentParentInfo, flags); dest.writeTypedObject(mSurfaceControl, flags); dest.writeStrongBinder(mOtherActivityToken); } /** The change is related to the TaskFragment created with this unique token. */ Loading Loading @@ -291,6 +297,21 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } /** * Token of another activity. * <p>For {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}, it is the next activity (behind the * reparented one) that fills the Task and occludes other activities. It will be the * actual activity token if the activity belongs to the same process as the organizer. * Otherwise, it is {@code null}. * * @hide */ @NonNull public Change setOtherActivityToken(@NonNull IBinder activityToken) { mOtherActivityToken = requireNonNull(activityToken); return this; } /** * Sets info of the parent Task of the embedded TaskFragment. * @see TaskFragmentParentInfo Loading Loading @@ -350,6 +371,12 @@ public final class TaskFragmentTransaction implements Parcelable { return mActivityToken; } /** @hide */ @Nullable public IBinder getOtherActivityToken() { return mOtherActivityToken; } /** * Obtains the {@link TaskFragmentParentInfo} for this transaction. */ Loading core/java/android/window/flags/windowing_sdk.aconfig +4 −1 Original line number Diff line number Diff line Loading @@ -124,7 +124,10 @@ flag { flag { namespace: "windowing_sdk" name: "pip_restore_to_overlay" name: "fix_pip_restore_to_overlay" description: "Restore exit-pip activity back to ActivityEmbedding overlay" bug: "297887697" metadata { purpose: PURPOSE_BUGFIX } } No newline at end of file libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +77 −2 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.getActivityInt import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams; import android.annotation.CallbackExecutor; import android.app.Activity; Loading Loading @@ -132,6 +133,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); /** * Stores the token of the associated Activity that maps to the * {@link OverlayContainerRestoreParams} of the most recent created overlay container. */ @GuardedBy("mLock") final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>(); /** * A developer-defined {@link SplitAttributes} calculator to compute the current * {@link SplitAttributes} with the current device and window states. Loading Loading @@ -686,11 +694,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen exception); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: final IBinder candidateAssociatedActToken, lastOverlayToken; if (Flags.fixPipRestoreToOverlay()) { candidateAssociatedActToken = change.getOtherActivityToken(); lastOverlayToken = change.getTaskFragmentToken(); } else { candidateAssociatedActToken = lastOverlayToken = null; } onActivityReparentedToTask( wct, taskId, change.getActivityIntent(), change.getActivityToken()); change.getActivityToken(), candidateAssociatedActToken, lastOverlayToken); break; default: throw new IllegalArgumentException( Loading Loading @@ -917,11 +934,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * different process, the server will generate a temporary token that * the organizer can use to reparent the activity through * {@link WindowContainerTransaction} if needed. * @param candidateAssociatedActToken The token of the candidate associated-activity. * @param lastOverlayToken The last parent overlay container token. */ @VisibleForTesting @GuardedBy("mLock") void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken, @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) { // Reparent the activity to an overlay container if needed. final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams( candidateAssociatedActToken, lastOverlayToken); if (params != null) { final Activity associatedActivity = getActivity(candidateAssociatedActToken); final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded( wct, params.mOptions, params.mIntent, associatedActivity); if (targetContainer != null) { wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken); return; } } // If the activity belongs to the current app process, we treat it as a new activity // launch. final Activity activity = getActivity(activityToken); Loading Loading @@ -965,6 +999,43 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } /** * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code * associatedActivityToken} associated with and only if data matches the {@code overlayToken}. * Otherwise, return {@code null}. */ @VisibleForTesting @GuardedBy("mLock") @Nullable OverlayContainerRestoreParams getOverlayContainerRestoreParams( @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) { if (!Flags.fixPipRestoreToOverlay()) { return null; } if (associatedActivityToken == null || overlayToken == null) { return null; } final TaskFragmentContainer.OverlayContainerRestoreParams params = mOverlayRestoreParams.get(associatedActivityToken); if (params == null) { return null; } if (params.mOverlayToken != overlayToken) { // Not the same overlay container, no need to restore. return null; } final Activity associatedActivity = getActivity(associatedActivityToken); if (associatedActivity == null || associatedActivity.isFinishing()) { return null; } return params; } /** * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. Loading Loading @@ -1433,6 +1504,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken); } mOverlayRestoreParams.remove(activity.getActivityToken()); updateCallbackIfNecessary(); } Loading @@ -1450,6 +1523,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken); } mOverlayRestoreParams.remove(activity.getActivityToken()); // We didn't trigger the callback if there were any pending appeared activities, so check // again after the pending is removed. updateCallbackIfNecessary(); Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +31 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.Collections; Loading Loading @@ -274,6 +275,15 @@ class TaskFragmentContainer { addPendingAppearedActivity(pendingAppearedActivity); } mPendingAppearedIntent = pendingAppearedIntent; // Save the information necessary for restoring the overlay when needed. if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null && associatedActivity != null && !associatedActivity.isFinishing()) { final IBinder associatedActivityToken = associatedActivity.getActivityToken(); final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken, launchOptions, pendingAppearedIntent); mController.mOverlayRestoreParams.put(associatedActivityToken, params); } } /** Loading Loading @@ -1105,4 +1115,25 @@ class TaskFragmentContainer { } return sb.append("]").toString(); } static class OverlayContainerRestoreParams { /** The token of the overlay container */ @NonNull final IBinder mOverlayToken; /** The launch options to create this container. */ @NonNull final Bundle mOptions; /** The Intent that used to be started in the overlay container. */ @NonNull final Intent mIntent; OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options, @NonNull Intent intent) { mOverlayToken = overlayToken; mOptions = options; mIntent = intent; } } } libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +24 −0 Original line number Diff line number Diff line Loading @@ -836,6 +836,30 @@ public class OverlayPresentationTest { any()); } @Test public void testOnActivityReparentedToTask_overlayRestoration() { mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY); // Prepares and mock the data necessary for the test. final IBinder activityToken = mActivity.getActivityToken(); final Intent intent = new Intent(); final IBinder fillTaskActivityToken = new Binder(); final IBinder lastOverlayToken = new Binder(); final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent, mActivity, TASK_ID); final TaskFragmentContainer.OverlayContainerRestoreParams params = mock( TaskFragmentContainer.OverlayContainerRestoreParams.class); doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any()); doReturn(overlayContainer).when(mSplitController).createOrUpdateOverlayTaskFragmentIfNeeded( any(), any(), any(), any()); // Verify the activity should be reparented to the overlay container. mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken, fillTaskActivityToken, lastOverlayToken); verify(mTransaction).reparentActivityToTaskFragment( eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); } /** * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded} */ Loading Loading
core/java/android/window/TaskFragmentTransaction.java +27 −0 Original line number Diff line number Diff line Loading @@ -189,6 +189,10 @@ public final class TaskFragmentTransaction implements Parcelable { @Nullable private IBinder mActivityToken; /** @see #setOtherActivityToken(IBinder) */ @Nullable private IBinder mOtherActivityToken; @Nullable private TaskFragmentParentInfo mTaskFragmentParentInfo; Loading @@ -210,6 +214,7 @@ public final class TaskFragmentTransaction implements Parcelable { mActivityToken = in.readStrongBinder(); mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR); mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR); mOtherActivityToken = in.readStrongBinder(); } @Override Loading @@ -224,6 +229,7 @@ public final class TaskFragmentTransaction implements Parcelable { dest.writeStrongBinder(mActivityToken); dest.writeTypedObject(mTaskFragmentParentInfo, flags); dest.writeTypedObject(mSurfaceControl, flags); dest.writeStrongBinder(mOtherActivityToken); } /** The change is related to the TaskFragment created with this unique token. */ Loading Loading @@ -291,6 +297,21 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } /** * Token of another activity. * <p>For {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}, it is the next activity (behind the * reparented one) that fills the Task and occludes other activities. It will be the * actual activity token if the activity belongs to the same process as the organizer. * Otherwise, it is {@code null}. * * @hide */ @NonNull public Change setOtherActivityToken(@NonNull IBinder activityToken) { mOtherActivityToken = requireNonNull(activityToken); return this; } /** * Sets info of the parent Task of the embedded TaskFragment. * @see TaskFragmentParentInfo Loading Loading @@ -350,6 +371,12 @@ public final class TaskFragmentTransaction implements Parcelable { return mActivityToken; } /** @hide */ @Nullable public IBinder getOtherActivityToken() { return mOtherActivityToken; } /** * Obtains the {@link TaskFragmentParentInfo} for this transaction. */ Loading
core/java/android/window/flags/windowing_sdk.aconfig +4 −1 Original line number Diff line number Diff line Loading @@ -124,7 +124,10 @@ flag { flag { namespace: "windowing_sdk" name: "pip_restore_to_overlay" name: "fix_pip_restore_to_overlay" description: "Restore exit-pip activity back to ActivityEmbedding overlay" bug: "297887697" metadata { purpose: PURPOSE_BUGFIX } } No newline at end of file
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +77 −2 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.getActivityInt import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams; import android.annotation.CallbackExecutor; import android.app.Activity; Loading Loading @@ -132,6 +133,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); /** * Stores the token of the associated Activity that maps to the * {@link OverlayContainerRestoreParams} of the most recent created overlay container. */ @GuardedBy("mLock") final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>(); /** * A developer-defined {@link SplitAttributes} calculator to compute the current * {@link SplitAttributes} with the current device and window states. Loading Loading @@ -686,11 +694,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen exception); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: final IBinder candidateAssociatedActToken, lastOverlayToken; if (Flags.fixPipRestoreToOverlay()) { candidateAssociatedActToken = change.getOtherActivityToken(); lastOverlayToken = change.getTaskFragmentToken(); } else { candidateAssociatedActToken = lastOverlayToken = null; } onActivityReparentedToTask( wct, taskId, change.getActivityIntent(), change.getActivityToken()); change.getActivityToken(), candidateAssociatedActToken, lastOverlayToken); break; default: throw new IllegalArgumentException( Loading Loading @@ -917,11 +934,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * different process, the server will generate a temporary token that * the organizer can use to reparent the activity through * {@link WindowContainerTransaction} if needed. * @param candidateAssociatedActToken The token of the candidate associated-activity. * @param lastOverlayToken The last parent overlay container token. */ @VisibleForTesting @GuardedBy("mLock") void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken, @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) { // Reparent the activity to an overlay container if needed. final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams( candidateAssociatedActToken, lastOverlayToken); if (params != null) { final Activity associatedActivity = getActivity(candidateAssociatedActToken); final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded( wct, params.mOptions, params.mIntent, associatedActivity); if (targetContainer != null) { wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken); return; } } // If the activity belongs to the current app process, we treat it as a new activity // launch. final Activity activity = getActivity(activityToken); Loading Loading @@ -965,6 +999,43 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } /** * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code * associatedActivityToken} associated with and only if data matches the {@code overlayToken}. * Otherwise, return {@code null}. */ @VisibleForTesting @GuardedBy("mLock") @Nullable OverlayContainerRestoreParams getOverlayContainerRestoreParams( @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) { if (!Flags.fixPipRestoreToOverlay()) { return null; } if (associatedActivityToken == null || overlayToken == null) { return null; } final TaskFragmentContainer.OverlayContainerRestoreParams params = mOverlayRestoreParams.get(associatedActivityToken); if (params == null) { return null; } if (params.mOverlayToken != overlayToken) { // Not the same overlay container, no need to restore. return null; } final Activity associatedActivity = getActivity(associatedActivityToken); if (associatedActivity == null || associatedActivity.isFinishing()) { return null; } return params; } /** * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. Loading Loading @@ -1433,6 +1504,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken); } mOverlayRestoreParams.remove(activity.getActivityToken()); updateCallbackIfNecessary(); } Loading @@ -1450,6 +1523,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken); } mOverlayRestoreParams.remove(activity.getActivityToken()); // We didn't trigger the callback if there were any pending appeared activities, so check // again after the pending is removed. updateCallbackIfNecessary(); Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +31 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.Collections; Loading Loading @@ -274,6 +275,15 @@ class TaskFragmentContainer { addPendingAppearedActivity(pendingAppearedActivity); } mPendingAppearedIntent = pendingAppearedIntent; // Save the information necessary for restoring the overlay when needed. if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null && associatedActivity != null && !associatedActivity.isFinishing()) { final IBinder associatedActivityToken = associatedActivity.getActivityToken(); final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken, launchOptions, pendingAppearedIntent); mController.mOverlayRestoreParams.put(associatedActivityToken, params); } } /** Loading Loading @@ -1105,4 +1115,25 @@ class TaskFragmentContainer { } return sb.append("]").toString(); } static class OverlayContainerRestoreParams { /** The token of the overlay container */ @NonNull final IBinder mOverlayToken; /** The launch options to create this container. */ @NonNull final Bundle mOptions; /** The Intent that used to be started in the overlay container. */ @NonNull final Intent mIntent; OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options, @NonNull Intent intent) { mOverlayToken = overlayToken; mOptions = options; mIntent = intent; } } }
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +24 −0 Original line number Diff line number Diff line Loading @@ -836,6 +836,30 @@ public class OverlayPresentationTest { any()); } @Test public void testOnActivityReparentedToTask_overlayRestoration() { mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY); // Prepares and mock the data necessary for the test. final IBinder activityToken = mActivity.getActivityToken(); final Intent intent = new Intent(); final IBinder fillTaskActivityToken = new Binder(); final IBinder lastOverlayToken = new Binder(); final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent, mActivity, TASK_ID); final TaskFragmentContainer.OverlayContainerRestoreParams params = mock( TaskFragmentContainer.OverlayContainerRestoreParams.class); doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any()); doReturn(overlayContainer).when(mSplitController).createOrUpdateOverlayTaskFragmentIfNeeded( any(), any(), any(), any()); // Verify the activity should be reparented to the overlay container. mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken, fillTaskActivityToken, lastOverlayToken); verify(mTransaction).reparentActivityToTaskFragment( eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); } /** * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded} */ Loading