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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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--) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user