Add TaskFragmentOrganizer#onActivityReparentToTask

When the activity exits PiP and is reparented to the original Task, the
organizer should handle it as a new launch.

Bug: 225371112
Test: atest WmTests:TaskFragmentTest
Test: atest WmTests:TaskFragmentOrganizerControllerTest
Change-Id: Ia7e32e995a35e167b0d76e701c972b269ae068cc
This commit is contained in:
Chris Li
2022-04-18 17:28:35 +08:00
parent 701b3f759c
commit 9e08260cce
11 changed files with 439 additions and 63 deletions

View File

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

View File

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

View File

@@ -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",

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
mTaskFragmentOrg = taskFragmentOrg;
mErrorCallback = errorCallback;
mException = exception;
private PendingTaskFragmentEvent(@EventType int eventType,
ITaskFragmentOrganizer taskFragmentOrg,
@Nullable TaskFragment taskFragment,
@Nullable IBinder errorCallbackToken,
@Nullable Throwable exception,
@Nullable ActivityRecord activity) {
mEventType = eventType;
mTaskFragmentOrg = taskFragmentOrg;
mTaskFragment = taskFragment;
mErrorCallbackToken = errorCallbackToken;
mException = exception;
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
@@ -591,20 +737,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);
}
}

View File

@@ -705,7 +705,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(

View File

@@ -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:
@@ -222,6 +225,85 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
verify(mOrganizer).onTaskFragmentError(eq(errorCallbackToken), eq(exception));
}
@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);

View File

@@ -26,9 +26,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.Mockito.clearInvocations;
@@ -61,6 +63,7 @@ import org.mockito.MockitoAnnotations;
public class TaskFragmentTest extends WindowTestsBase {
private TaskFragmentOrganizer mOrganizer;
private ITaskFragmentOrganizer mIOrganizer;
private TaskFragment mTaskFragment;
private SurfaceControl mLeash;
@Mock
@@ -70,10 +73,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)
@@ -239,6 +242,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
@@ -271,6 +276,38 @@ public class TaskFragmentTest extends WindowTestsBase {
assertTrue(info.isEmpty());
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