diff options
| author | 2022-04-25 10:56:38 +0000 | |
|---|---|---|
| committer | 2022-04-25 10:56:38 +0000 | |
| commit | 88d29695860f90fbd7fbf0cab07237b7ce771cab (patch) | |
| tree | 9fc29bdb48d1e0e40238dcc2eb726eef81289fd6 | |
| parent | a52e653202676c9e397367d98338c7db9c2b8352 (diff) | |
| parent | 067613587089f47a4190796a896183aa0dba2f41 (diff) | |
Merge "Add TaskFragmentOrganizer#onActivityReparentToTask" into tm-dev am: 0b91be7283 am: 0676135870
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17817304
Change-Id: I44fd3e54bc9e0f786df4bf3f7873ac12135cd819
Ignore-AOSP-First: this is an automerge
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
11 files changed, 438 insertions, 62 deletions
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl index cdfa206423c2..8dfda7d41c2d 100644 --- a/core/java/android/window/ITaskFragmentOrganizer.aidl +++ b/core/java/android/window/ITaskFragmentOrganizer.aidl @@ -16,6 +16,7 @@ package android.window; +import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; @@ -48,4 +49,20 @@ oneway interface ITaskFragmentOrganizer { * {@link TaskFragmentOrganizer#putExceptionInBundle}. */ void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle exceptionBundle); + + /** + * Called when an Activity is reparented to the Task with organized TaskFragment. For example, + * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its + * orginial Task. In this case, we need to notify the organizer so that it can check if the + * Activity matches any split rule. + * + * @param taskId The Task that the activity is reparented to. + * @param activityIntent The intent that the activity is original launched with. + * @param activityToken If the activity belongs to the same process as the organizer, this + * will be the actual activity token; if the activity belongs to a + * different process, the server will generate a temporary token that + * the organizer can use to reparent the activity through + * {@link WindowContainerTransaction} if needed. + */ + void onActivityReparentToTask(int taskId, in Intent activityIntent, in IBinder activityToken); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 1d1deacf0eb3..2ef49c3e2aac 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -19,6 +19,7 @@ package android.window; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.TestApi; +import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; @@ -154,6 +155,24 @@ public class TaskFragmentOrganizer extends WindowOrganizer { public void onTaskFragmentError( @NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {} + /** + * Called when an Activity is reparented to the Task with organized TaskFragment. For example, + * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its + * orginial Task. In this case, we need to notify the organizer so that it can check if the + * Activity matches any split rule. + * + * @param taskId The Task that the activity is reparented to. + * @param activityIntent The intent that the activity is original launched with. + * @param activityToken If the activity belongs to the same process as the organizer, this + * will be the actual activity token; if the activity belongs to a + * different process, the server will generate a temporary token that + * the organizer can use to reparent the activity through + * {@link WindowContainerTransaction} if needed. + * @hide + */ + public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, + @NonNull IBinder activityToken) {} + @Override public void applyTransaction(@NonNull WindowContainerTransaction t) { t.setTaskFragmentOrganizer(mInterface); @@ -203,6 +222,14 @@ public class TaskFragmentOrganizer extends WindowOrganizer { errorCallbackToken, (Throwable) exceptionBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION))); } + + @Override + public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, + @NonNull IBinder activityToken) { + mExecutor.execute( + () -> TaskFragmentOrganizer.this.onActivityReparentToTask( + taskId, activityIntent, activityToken)); + } }; private final TaskFragmentOrganizerToken mToken = new TaskFragmentOrganizerToken(mInterface); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 2f021fe563c2..07a342965411 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3031,6 +3031,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "873160948": { + "message": "Activity=%s reparent to taskId=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_ORGANIZER", + "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java" + }, "873914452": { "message": "goodToGo()", "level": "DEBUG", 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 f4e91bae54ee..e50b9a1cd469 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -70,6 +70,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @NonNull Configuration parentConfig); + void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, + @NonNull IBinder activityToken); } /** @@ -300,4 +302,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig); } } + + @Override + public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, + @NonNull IBinder activityToken) { + if (mCallback != null) { + mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken); + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index ca420c64e961..9f33cbcbcbd5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -222,6 +222,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @Override + public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, + @NonNull IBinder activityToken) { + // If the activity belongs to the current app process, we treat it as a new activity launch. + final Activity activity = ActivityThread.currentActivityThread().getActivity(activityToken); + if (activity != null) { + onActivityCreated(activity); + updateCallbackIfNecessary(); + return; + } + // TODO: handle for activity in other process. + } + /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */ private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a34ecdb7411b..995ddb277d36 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -322,6 +322,7 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; import android.view.animation.Animation; +import android.window.ITaskFragmentOrganizer; import android.window.RemoteTransition; import android.window.SizeConfigurationBuckets; import android.window.SplashScreen; @@ -635,6 +636,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // host Activity the launch-into-pip Activity is originated from. private ActivityRecord mLaunchIntoPipHostActivity; + /** + * Sets to the previous {@link ITaskFragmentOrganizer} of the {@link TaskFragment} that the + * activity is embedded in before it is reparented to a new Task due to picture-in-picture. + */ + @Nullable + ITaskFragmentOrganizer mLastTaskFragmentOrganizerBeforePip; + boolean firstWindowDrawn; /** Whether the visible window(s) of this activity is drawn. */ private boolean mReportedDrawn; @@ -1531,7 +1539,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateAnimatingActivityRegistry(); - if (task == mLastParentBeforePip) { + if (task == mLastParentBeforePip && task != null) { + // Notify the TaskFragmentOrganizer that the activity is reparented back from pip. + mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController + .onActivityReparentToTask(this); // Activity's reparented back from pip, clear the links once established clearLastParentBeforePip(); } @@ -1654,6 +1665,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A : launchIntoPipHostActivity.getTask(); mLastParentBeforePip.mChildPipActivity = this; mLaunchIntoPipHostActivity = launchIntoPipHostActivity; + final TaskFragment organizedTf = launchIntoPipHostActivity == null + ? getOrganizedTaskFragment() + : launchIntoPipHostActivity.getOrganizedTaskFragment(); + mLastTaskFragmentOrganizerBeforePip = organizedTf != null + ? organizedTf.getTaskFragmentOrganizer() + : null; } private void clearLastParentBeforePip() { @@ -1662,6 +1679,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastParentBeforePip = null; } mLaunchIntoPipHostActivity = null; + mLastTaskFragmentOrganizerBeforePip = null; } @Nullable Task getLastParentBeforePip() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index c78d4cbd45f5..1beb32cfdd52 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -519,15 +519,20 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } + boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { + return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid); + } + /** * Checks if the organized task fragment is allowed to have the specified activity, which is * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be * enabled. * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) + * @param uid uid of the TaskFragment organizer. */ - boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { + boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { return isAllowedToEmbedActivityInUntrustedMode(a) - || isAllowedToEmbedActivityInTrustedMode(a); + || isAllowedToEmbedActivityInTrustedMode(a, uid); } /** @@ -544,20 +549,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; } + boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) { + return isAllowedToEmbedActivityInTrustedMode(a, mTaskFragmentOrganizerUid); + } + /** * Checks if the organized task fragment is allowed to embed activity in fully trusted mode, * which means that all transactions are allowed. This is supported in the following cases: * <li>the activity belongs to the same app as the organizer host;</li> * <li>the activity has declared the organizer host as trusted explicitly via known * certificate.</li> + * @param uid uid of the TaskFragment organizer. */ - boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) { - if (UserHandle.getAppId(mTaskFragmentOrganizerUid) == SYSTEM_UID) { + boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a, int uid) { + if (UserHandle.getAppId(uid) == SYSTEM_UID) { // The system is trusted to embed other apps securely and for all users. return true; } - if (mTaskFragmentOrganizerUid == a.getUid()) { + if (uid == a.getUid()) { // Activities from the same UID can be embedded freely by the host. return true; } @@ -570,7 +580,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { } AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked() - .getPackage(mTaskFragmentOrganizerUid); + .getPackage(uid); return hostPackage != null && hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest( knownActivityEmbeddingCerts); @@ -581,7 +591,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) */ boolean isAllowedToBeEmbeddedInTrustedMode() { - return forAllActivities(this::isAllowedToEmbedActivityInTrustedMode); + final Predicate<ActivityRecord> callback = this::isAllowedToEmbedActivityInTrustedMode; + return forAllActivities(callback); } /** diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 764960ad9176..eaf25260405a 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -51,6 +51,7 @@ import java.util.WeakHashMap; */ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerController.Stub { private static final String TAG = "TaskFragmentOrganizerController"; + private static final long TEMPORARY_ACTIVITY_TOKEN_TIMEOUT_MS = 5000; private final ActivityTaskManagerService mAtmService; private final WindowManagerGlobalLock mGlobalLock; @@ -78,10 +79,14 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private class TaskFragmentOrganizerState implements IBinder.DeathRecipient { private final ArrayList<TaskFragment> mOrganizedTaskFragments = new ArrayList<>(); private final ITaskFragmentOrganizer mOrganizer; + private final int mOrganizerPid; + private final int mOrganizerUid; private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos = new WeakHashMap<>(); private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs = new WeakHashMap<>(); + private final Map<IBinder, ActivityRecord> mTemporaryActivityTokens = + new WeakHashMap<>(); /** * Map from Task Id to {@link RemoteAnimationDefinition}. @@ -91,8 +96,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions = new SparseArray<>(); - TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) { + TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) { mOrganizer = organizer; + mOrganizerPid = pid; + mOrganizerUid = uid; try { mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/); } catch (RemoteException e) { @@ -134,23 +141,23 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/); } - void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) { + void onTaskFragmentAppeared(TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); final TaskFragmentInfo info = tf.getTaskFragmentInfo(); try { - organizer.onTaskFragmentAppeared(info); + mOrganizer.onTaskFragmentAppeared(info); mLastSentTaskFragmentInfos.put(tf, info); tf.mTaskFragmentAppearedSent = true; } catch (RemoteException e) { Slog.d(TAG, "Exception sending onTaskFragmentAppeared callback", e); } - onTaskFragmentParentInfoChanged(organizer, tf); + onTaskFragmentParentInfoChanged(tf); } - void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment tf) { + void onTaskFragmentVanished(TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName()); try { - organizer.onTaskFragmentVanished(tf.getTaskFragmentInfo()); + mOrganizer.onTaskFragmentVanished(tf.getTaskFragmentInfo()); } catch (RemoteException e) { Slog.d(TAG, "Exception sending onTaskFragmentVanished callback", e); } @@ -159,10 +166,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mLastSentTaskFragmentParentConfigs.remove(tf); } - void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) { + void onTaskFragmentInfoChanged(TaskFragment tf) { // Parent config may have changed. The controller will check if there is any important // config change for the organizer. - onTaskFragmentParentInfoChanged(organizer, tf); + onTaskFragmentParentInfoChanged(tf); // Check if the info is different from the last reported info. final TaskFragmentInfo info = tf.getTaskFragmentInfo(); @@ -174,14 +181,14 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", tf.getName()); try { - organizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo()); + mOrganizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo()); mLastSentTaskFragmentInfos.put(tf, info); } catch (RemoteException e) { Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e); } } - void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) { + void onTaskFragmentParentInfoChanged(TaskFragment tf) { // Check if the parent info is different from the last reported parent info. if (tf.getParent() == null || tf.getParent().asTask() == null) { mLastSentTaskFragmentParentConfigs.remove(tf); @@ -197,30 +204,86 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr "TaskFragment parent info changed name=%s parentTaskId=%d", tf.getName(), parent.mTaskId); try { - organizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig); + mOrganizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig); mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig)); } catch (RemoteException e) { Slog.d(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e); } } - void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken, - Throwable exception) { + void onTaskFragmentError(IBinder errorCallbackToken, Throwable exception) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Sending TaskFragment error exception=%s", exception.toString()); final Bundle exceptionBundle = putExceptionInBundle(exception); try { - organizer.onTaskFragmentError(errorCallbackToken, exceptionBundle); + mOrganizer.onTaskFragmentError(errorCallbackToken, exceptionBundle); } catch (RemoteException e) { Slog.d(TAG, "Exception sending onTaskFragmentError callback", e); } } + + void onActivityReparentToTask(ActivityRecord activity) { + if (activity.finishing) { + Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing"); + return; + } + final Task task = activity.getTask(); + if (task == null || task.effectiveUid != mOrganizerUid) { + Slog.d(TAG, "Reparent activity=" + activity.token + + " is not in a task belong to the organizer app."); + return; + } + if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) { + Slog.d(TAG, "Reparent activity=" + activity.token + + " is not allowed to be embedded."); + return; + } + + final IBinder activityToken; + if (activity.getPid() == mOrganizerPid) { + // We only pass the actual token if the activity belongs to the organizer process. + activityToken = activity.token; + } else { + // For security, we can't pass the actual token if the activity belongs to a + // different process. In this case, we will pass a temporary token that organizer + // can use to reparent through WindowContainerTransaction. + activityToken = new Binder("TemporaryActivityToken"); + mTemporaryActivityTokens.put(activityToken, activity); + final Runnable timeout = () -> { + synchronized (mGlobalLock) { + mTemporaryActivityTokens.remove(activityToken); + } + }; + mAtmService.mWindowManager.mH.postDelayed(timeout, + TEMPORARY_ACTIVITY_TOKEN_TIMEOUT_MS); + } + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d", + activity.token, task.mTaskId); + try { + mOrganizer.onActivityReparentToTask(task.mTaskId, activity.intent, activityToken); + } catch (RemoteException e) { + Slog.d(TAG, "Exception sending onActivityReparentToTask callback", e); + } + } + } + + @Nullable + ActivityRecord getReparentActivityFromTemporaryToken( + @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder activityToken) { + if (organizer == null || activityToken == null) { + return null; + } + final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get( + organizer.asBinder()); + return state != null + ? state.mTemporaryActivityTokens.remove(activityToken) + : null; } @Override public void registerOrganizer(ITaskFragmentOrganizer organizer) { final int pid = Binder.getCallingPid(); - final long uid = Binder.getCallingUid(); + final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task fragment organizer=%s uid=%d pid=%d", @@ -230,7 +293,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr "Replacing existing organizer currently unsupported"); } mTaskFragmentOrganizerState.put(organizer.asBinder(), - new TaskFragmentOrganizerState(organizer)); + new TaskFragmentOrganizerState(organizer, pid, uid)); } } @@ -321,8 +384,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr PendingTaskFragmentEvent pendingEvent = getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_APPEARED); if (pendingEvent == null) { - pendingEvent = new PendingTaskFragmentEvent(taskFragment, organizer, - PendingTaskFragmentEvent.EVENT_APPEARED); + pendingEvent = new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_APPEARED, organizer) + .setTaskFragment(taskFragment) + .build(); mPendingTaskFragmentEvents.add(pendingEvent); } } @@ -347,7 +412,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } PendingTaskFragmentEvent pendingEvent = getLastPendingLifecycleEvent(taskFragment); if (pendingEvent == null) { - pendingEvent = new PendingTaskFragmentEvent(taskFragment, organizer, eventType); + pendingEvent = new PendingTaskFragmentEvent.Builder(eventType, organizer) + .setTaskFragment(taskFragment) + .build(); } else { if (pendingEvent.mEventType == PendingTaskFragmentEvent.EVENT_VANISHED) { // Skipped the info changed event if vanished event is pending. @@ -374,8 +441,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr if (!taskFragment.mTaskFragmentAppearedSent) { return; } - PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent(taskFragment, - organizer, PendingTaskFragmentEvent.EVENT_VANISHED); + final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_VANISHED, organizer) + .setTaskFragment(taskFragment) + .build(); mPendingTaskFragmentEvents.add(pendingEvent); state.removeTaskFragment(taskFragment); } @@ -384,8 +453,43 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr Throwable exception) { validateAndGetState(organizer); Slog.w(TAG, "onTaskFragmentError ", exception); - PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent(organizer, - errorCallbackToken, exception, PendingTaskFragmentEvent.EVENT_ERROR); + final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_ERROR, organizer) + .setErrorCallbackToken(errorCallbackToken) + .setException(exception) + .build(); + mPendingTaskFragmentEvents.add(pendingEvent); + } + + void onActivityReparentToTask(ActivityRecord activity) { + final ITaskFragmentOrganizer organizer; + if (activity.mLastTaskFragmentOrganizerBeforePip != null) { + // If the activity is previously embedded in an organized TaskFragment. + organizer = activity.mLastTaskFragmentOrganizerBeforePip; + } else { + // Find the topmost TaskFragmentOrganizer. + final Task task = activity.getTask(); + final TaskFragment[] organizedTf = new TaskFragment[1]; + task.forAllLeafTaskFragments(tf -> { + if (tf.isOrganizedTaskFragment()) { + organizedTf[0] = tf; + return true; + } + return false; + }); + if (organizedTf[0] == null) { + return; + } + organizer = organizedTf[0].getTaskFragmentOrganizer(); + } + if (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) { + Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists"); + return; + } + final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( + PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer) + .setActivity(activity) + .build(); mPendingTaskFragmentEvents.add(pendingEvent); } @@ -422,13 +526,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr static final int EVENT_INFO_CHANGED = 2; static final int EVENT_PARENT_INFO_CHANGED = 3; static final int EVENT_ERROR = 4; + static final int EVENT_ACTIVITY_REPARENT_TO_TASK = 5; @IntDef(prefix = "EVENT_", value = { EVENT_APPEARED, EVENT_VANISHED, EVENT_INFO_CHANGED, EVENT_PARENT_INFO_CHANGED, - EVENT_ERROR + EVENT_ERROR, + EVENT_ACTIVITY_REPARENT_TO_TASK }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} @@ -436,34 +542,30 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @EventType private final int mEventType; private final ITaskFragmentOrganizer mTaskFragmentOrg; + @Nullable private final TaskFragment mTaskFragment; - private final IBinder mErrorCallback; + @Nullable + private final IBinder mErrorCallbackToken; + @Nullable private final Throwable mException; + @Nullable + private final ActivityRecord mActivity; // Set when the event is deferred due to the host task is invisible. The defer time will // be the last active time of the host task. private long mDeferTime; - private PendingTaskFragmentEvent(TaskFragment taskFragment, - ITaskFragmentOrganizer taskFragmentOrg, @EventType int eventType) { - this(taskFragment, taskFragmentOrg, null /* errorCallback */, - null /* exception */, eventType); - - } - - private PendingTaskFragmentEvent(ITaskFragmentOrganizer taskFragmentOrg, - IBinder errorCallback, Throwable exception, @EventType int eventType) { - this(null /* taskFragment */, taskFragmentOrg, errorCallback, exception, - eventType); - } - - private PendingTaskFragmentEvent(TaskFragment taskFragment, - ITaskFragmentOrganizer taskFragmentOrg, IBinder errorCallback, Throwable exception, - @EventType int eventType) { - mTaskFragment = taskFragment; + private PendingTaskFragmentEvent(@EventType int eventType, + ITaskFragmentOrganizer taskFragmentOrg, + @Nullable TaskFragment taskFragment, + @Nullable IBinder errorCallbackToken, + @Nullable Throwable exception, + @Nullable ActivityRecord activity) { + mEventType = eventType; mTaskFragmentOrg = taskFragmentOrg; - mErrorCallback = errorCallback; + mTaskFragment = taskFragment; + mErrorCallbackToken = errorCallbackToken; mException = exception; - mEventType = eventType; + mActivity = activity; } /** @@ -481,6 +583,50 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return false; } } + + private static class Builder { + @EventType + private final int mEventType; + private final ITaskFragmentOrganizer mTaskFragmentOrg; + @Nullable + private TaskFragment mTaskFragment; + @Nullable + private IBinder mErrorCallbackToken; + @Nullable + private Throwable mException; + @Nullable + private ActivityRecord mActivity; + + Builder(@EventType int eventType, ITaskFragmentOrganizer taskFragmentOrg) { + mEventType = eventType; + mTaskFragmentOrg = taskFragmentOrg; + } + + Builder setTaskFragment(@Nullable TaskFragment taskFragment) { + mTaskFragment = taskFragment; + return this; + } + + Builder setErrorCallbackToken(@Nullable IBinder errorCallbackToken) { + mErrorCallbackToken = errorCallbackToken; + return this; + } + + Builder setException(@Nullable Throwable exception) { + mException = exception; + return this; + } + + Builder setActivity(@Nullable ActivityRecord activity) { + mActivity = activity; + return this; + } + + PendingTaskFragmentEvent build() { + return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment, + mErrorCallbackToken, mException, mActivity); + } + } } @Nullable @@ -596,20 +742,22 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } switch (event.mEventType) { case PendingTaskFragmentEvent.EVENT_APPEARED: - state.onTaskFragmentAppeared(taskFragmentOrg, taskFragment); + state.onTaskFragmentAppeared(taskFragment); break; case PendingTaskFragmentEvent.EVENT_VANISHED: - state.onTaskFragmentVanished(taskFragmentOrg, taskFragment); + state.onTaskFragmentVanished(taskFragment); break; case PendingTaskFragmentEvent.EVENT_INFO_CHANGED: - state.onTaskFragmentInfoChanged(taskFragmentOrg, taskFragment); + state.onTaskFragmentInfoChanged(taskFragment); break; case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED: - state.onTaskFragmentParentInfoChanged(taskFragmentOrg, taskFragment); + state.onTaskFragmentParentInfoChanged(taskFragment); break; case PendingTaskFragmentEvent.EVENT_ERROR: - state.onTaskFragmentError(taskFragmentOrg, event.mErrorCallback, - event.mException); + state.onTaskFragmentError(event.mErrorCallbackToken, event.mException); + break; + case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK: + state.onActivityReparentToTask(event.mActivity); } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 27d60413acc2..598569ffd7c9 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -707,7 +707,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { final IBinder fragmentToken = hop.getNewParent(); - final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); + final IBinder activityToken = hop.getContainer(); + ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken); + if (activity == null) { + // The token may be a temporary token if the activity doesn't belong to + // the organizer process. + activity = mTaskFragmentOrganizerController + .getReparentActivityFromTemporaryToken(organizer, activityToken); + } final TaskFragment parent = mLaunchTaskFragments.get(fragmentToken); if (parent == null || activity == null) { final Throwable exception = new IllegalArgumentException( 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 a297608af480..4425962eb8eb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -26,6 +26,7 @@ import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -38,6 +39,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Intent; @@ -63,6 +65,7 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; /** * Build/Install/Run: @@ -223,6 +226,85 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testOnActivityReparentToTask_activityInOrganizerProcess_useActivityToken() { + // Make sure the activity pid/uid is the same as the organizer caller. + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + mController.registerOrganizer(mIOrganizer); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final Task task = activity.getTask(); + activity.info.applicationInfo.uid = uid; + doReturn(pid).when(activity).getPid(); + task.effectiveUid = uid; + + // No need to notify organizer if it is not embedded. + mController.onActivityReparentToTask(activity); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onActivityReparentToTask(anyInt(), any(), any()); + + // Notify organizer if it was embedded before entered Pip. + activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; + mController.onActivityReparentToTask(activity); + mController.dispatchPendingEvents(); + + verify(mOrganizer).onActivityReparentToTask(task.mTaskId, activity.intent, activity.token); + + // Notify organizer if there is any embedded in the Task. + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .build(); + taskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid, + DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); + activity.reparent(taskFragment, POSITION_TOP); + activity.mLastTaskFragmentOrganizerBeforePip = null; + mController.onActivityReparentToTask(activity); + mController.dispatchPendingEvents(); + + verify(mOrganizer, times(2)) + .onActivityReparentToTask(task.mTaskId, activity.intent, activity.token); + } + + @Test + public void testOnActivityReparentToTask_activityNotInOrganizerProcess_useTemporaryToken() { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid, + DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); + mAtm.mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); + mController.registerOrganizer(mIOrganizer); + mOrganizer.applyTransaction(mTransaction); + final Task task = createTask(mDisplayContent); + task.addChild(mTaskFragment, POSITION_TOP); + final ActivityRecord activity = createActivityRecord(task); + + // Make sure the activity belongs to the same app, but it is in a different pid. + activity.info.applicationInfo.uid = uid; + doReturn(pid + 1).when(activity).getPid(); + task.effectiveUid = uid; + final ArgumentCaptor<IBinder> token = ArgumentCaptor.forClass(IBinder.class); + + // Notify organizer if it was embedded before entered Pip. + // Create a temporary token since the activity doesn't belong to the same process. + activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; + mController.onActivityReparentToTask(activity); + mController.dispatchPendingEvents(); + + // Allow organizer to reparent activity in other process using the temporary token. + verify(mOrganizer).onActivityReparentToTask(eq(task.mTaskId), eq(activity.intent), + token.capture()); + final IBinder temporaryToken = token.getValue(); + assertNotEquals(activity.token, temporaryToken); + mTransaction.reparentActivityToTaskFragment(mFragmentToken, temporaryToken); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + assertEquals(mTaskFragment, activity.getTaskFragment()); + // The temporary token can only be used once. + assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer, temporaryToken)); + } + + @Test public void testRegisterRemoteAnimations() { mController.registerOrganizer(mIOrganizer); mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 240943cfab71..3c14777cd7d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -28,9 +28,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; @@ -64,6 +66,7 @@ import org.mockito.MockitoAnnotations; public class TaskFragmentTest extends WindowTestsBase { private TaskFragmentOrganizer mOrganizer; + private ITaskFragmentOrganizer mIOrganizer; private TaskFragment mTaskFragment; private SurfaceControl mLeash; @Mock @@ -73,10 +76,10 @@ public class TaskFragmentTest extends WindowTestsBase { public void setup() { MockitoAnnotations.initMocks(this); mOrganizer = new TaskFragmentOrganizer(Runnable::run); - final ITaskFragmentOrganizer iOrganizer = - ITaskFragmentOrganizer.Stub.asInterface(mOrganizer.getOrganizerToken().asBinder()); + mIOrganizer = ITaskFragmentOrganizer.Stub.asInterface(mOrganizer.getOrganizerToken() + .asBinder()); mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController - .registerOrganizer(iOrganizer); + .registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setCreateParentTask() .setOrganizer(mOrganizer) @@ -242,6 +245,8 @@ public class TaskFragmentTest extends WindowTestsBase { assertEquals(taskBounds, taskFragment.getBounds()); assertEquals(taskBounds, activity.getBounds()); assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration()); + // Because the whole Task is entering PiP, no need to record for future reparent. + assertNull(activity.mLastTaskFragmentOrganizerBeforePip); } @Test @@ -280,6 +285,38 @@ public class TaskFragmentTest extends WindowTestsBase { assertTrue(task.isVisibleRequested()); verify(mAtm.mTaskFragmentOrganizerController) .dispatchPendingInfoChangedEvent(taskFragment0); + // Make sure the organizer is recorded so that it can be reused when the activity is + // reparented back on exiting PiP. + assertEquals(mIOrganizer, activity0.mLastTaskFragmentOrganizerBeforePip); + } + + @Test + public void testEmbeddedActivityExitPip_notifyOrganizer() { + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .createActivityCount(1) + .build(); + new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) + .createActivityCount(1) + .build(); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + mRootWindowContainer.moveActivityToPinnedRootTask(activity, + null /* launchIntoPipHostActivity */, "test"); + spyOn(mAtm.mTaskFragmentOrganizerController); + assertEquals(mIOrganizer, activity.mLastTaskFragmentOrganizerBeforePip); + + // Move the activity back to its original Task. + activity.reparent(task, POSITION_TOP); + + // Notify the organizer about the reparent. + verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentToTask(activity); + assertNull(activity.mLastTaskFragmentOrganizerBeforePip); } @Test |