summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Li <lihongyu@google.com> 2022-04-25 10:56:38 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2022-04-25 10:56:38 +0000
commit88d29695860f90fbd7fbf0cab07237b7ce771cab (patch)
tree9fc29bdb48d1e0e40238dcc2eb726eef81289fd6
parenta52e653202676c9e397367d98338c7db9c2b8352 (diff)
parent067613587089f47a4190796a896183aa0dba2f41 (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>
-rw-r--r--core/java/android/window/ITaskFragmentOrganizer.aidl17
-rw-r--r--core/java/android/window/TaskFragmentOrganizer.java27
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java10
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java20
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java25
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java248
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java82
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java43
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