Merge "Intercept activity start requests in client organizer" into sc-v2-dev am: 94a6a10f90
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15411942 Change-Id: Ie5535035dc6fe2db01fc490b9c7aca18e87467d3
This commit is contained in:
@@ -214,6 +214,14 @@ public class ActivityOptions {
|
||||
public static final String KEY_LAUNCH_ROOT_TASK_TOKEN =
|
||||
"android.activity.launchRootTaskToken";
|
||||
|
||||
/**
|
||||
* The {@link com.android.server.wm.TaskFragment} token the activity should be launched into.
|
||||
* @see #setLaunchTaskFragmentToken(IBinder)
|
||||
* @hide
|
||||
*/
|
||||
public static final String KEY_LAUNCH_TASK_FRAGMENT_TOKEN =
|
||||
"android.activity.launchTaskFragmentToken";
|
||||
|
||||
/**
|
||||
* The windowing mode the activity should be launched into.
|
||||
* @hide
|
||||
@@ -396,6 +404,7 @@ public class ActivityOptions {
|
||||
private int mCallerDisplayId = INVALID_DISPLAY;
|
||||
private WindowContainerToken mLaunchTaskDisplayArea;
|
||||
private WindowContainerToken mLaunchRootTask;
|
||||
private IBinder mLaunchTaskFragmentToken;
|
||||
@WindowConfiguration.WindowingMode
|
||||
private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
|
||||
@WindowConfiguration.ActivityType
|
||||
@@ -1138,6 +1147,7 @@ public class ActivityOptions {
|
||||
mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
|
||||
mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
|
||||
mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN);
|
||||
mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
|
||||
mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
|
||||
mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
|
||||
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
|
||||
@@ -1472,6 +1482,17 @@ public class ActivityOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public IBinder getLaunchTaskFragmentToken() {
|
||||
return mLaunchTaskFragmentToken;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) {
|
||||
mLaunchTaskFragmentToken = taskFragmentToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getLaunchWindowingMode() {
|
||||
return mLaunchWindowingMode;
|
||||
@@ -1882,6 +1903,9 @@ public class ActivityOptions {
|
||||
if (mLaunchRootTask != null) {
|
||||
b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
|
||||
}
|
||||
if (mLaunchTaskFragmentToken != null) {
|
||||
b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken);
|
||||
}
|
||||
if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
|
||||
b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
|
||||
}
|
||||
|
||||
@@ -742,6 +742,17 @@ public class Instrumentation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This overload is used for notifying the {@link android.window.TaskFragmentOrganizer}
|
||||
* implementation internally about started activities.
|
||||
*
|
||||
* @see #onStartActivity(Intent)
|
||||
* @hide
|
||||
*/
|
||||
public ActivityResult onStartActivity(Context who, Intent intent, Bundle options) {
|
||||
return onStartActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for intercepting any started activity.
|
||||
*
|
||||
@@ -1722,7 +1733,10 @@ public class Instrumentation {
|
||||
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||
ActivityResult result = null;
|
||||
if (am.ignoreMatchingSpecificIntents()) {
|
||||
result = am.onStartActivity(intent);
|
||||
if (options == null) {
|
||||
options = ActivityOptions.makeBasic().toBundle();
|
||||
}
|
||||
result = am.onStartActivity(who, intent, options);
|
||||
}
|
||||
if (result != null) {
|
||||
am.mHits++;
|
||||
@@ -1790,7 +1804,10 @@ public class Instrumentation {
|
||||
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||
ActivityResult result = null;
|
||||
if (am.ignoreMatchingSpecificIntents()) {
|
||||
result = am.onStartActivity(intents[0]);
|
||||
if (options == null) {
|
||||
options = ActivityOptions.makeBasic().toBundle();
|
||||
}
|
||||
result = am.onStartActivity(who, intents[0], options);
|
||||
}
|
||||
if (result != null) {
|
||||
am.mHits++;
|
||||
@@ -1861,7 +1878,10 @@ public class Instrumentation {
|
||||
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||
ActivityResult result = null;
|
||||
if (am.ignoreMatchingSpecificIntents()) {
|
||||
result = am.onStartActivity(intent);
|
||||
if (options == null) {
|
||||
options = ActivityOptions.makeBasic().toBundle();
|
||||
}
|
||||
result = am.onStartActivity(who, intent, options);
|
||||
}
|
||||
if (result != null) {
|
||||
am.mHits++;
|
||||
@@ -1928,7 +1948,10 @@ public class Instrumentation {
|
||||
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||
ActivityResult result = null;
|
||||
if (am.ignoreMatchingSpecificIntents()) {
|
||||
result = am.onStartActivity(intent);
|
||||
if (options == null) {
|
||||
options = ActivityOptions.makeBasic().toBundle();
|
||||
}
|
||||
result = am.onStartActivity(who, intent, options);
|
||||
}
|
||||
if (result != null) {
|
||||
am.mHits++;
|
||||
@@ -1974,7 +1997,10 @@ public class Instrumentation {
|
||||
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||
ActivityResult result = null;
|
||||
if (am.ignoreMatchingSpecificIntents()) {
|
||||
result = am.onStartActivity(intent);
|
||||
if (options == null) {
|
||||
options = ActivityOptions.makeBasic().toBundle();
|
||||
}
|
||||
result = am.onStartActivity(who, intent, options);
|
||||
}
|
||||
if (result != null) {
|
||||
am.mHits++;
|
||||
@@ -2021,7 +2047,10 @@ public class Instrumentation {
|
||||
final ActivityMonitor am = mActivityMonitors.get(i);
|
||||
ActivityResult result = null;
|
||||
if (am.ignoreMatchingSpecificIntents()) {
|
||||
result = am.onStartActivity(intent);
|
||||
if (options == null) {
|
||||
options = ActivityOptions.makeBasic().toBundle();
|
||||
}
|
||||
result = am.onStartActivity(who, intent, options);
|
||||
}
|
||||
if (result != null) {
|
||||
am.mHits++;
|
||||
|
||||
@@ -155,6 +155,17 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
applyTransaction(wct);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ownerToken The token of the activity that creates this task fragment. It does not
|
||||
* have to be a child of this task fragment, but must belong to the same task.
|
||||
*/
|
||||
void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
|
||||
IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
|
||||
final TaskFragmentCreationParams fragmentOptions =
|
||||
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
|
||||
wct.createTaskFragment(fragmentOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ownerToken The token of the activity that creates this task fragment. It does not
|
||||
* have to be a child of this task fragment, but must belong to the same task.
|
||||
@@ -162,10 +173,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
private void createTaskFragmentAndReparentActivity(
|
||||
WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
|
||||
@NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
|
||||
final TaskFragmentCreationParams fragmentOptions =
|
||||
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
|
||||
wct.createTaskFragment(fragmentOptions)
|
||||
.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
|
||||
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
|
||||
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,11 +185,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
|
||||
@NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
|
||||
@Nullable Bundle activityOptions) {
|
||||
final TaskFragmentCreationParams fragmentOptions =
|
||||
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
|
||||
wct.createTaskFragment(fragmentOptions)
|
||||
.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent,
|
||||
activityOptions);
|
||||
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
|
||||
wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
|
||||
}
|
||||
|
||||
TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
|
||||
|
||||
@@ -20,9 +20,12 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityClient;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.Application.ActivityLifecycleCallbacks;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
@@ -61,9 +64,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
|
||||
public SplitController() {
|
||||
mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
|
||||
ActivityThread activityThread = ActivityThread.currentActivityThread();
|
||||
// Register a callback to be notified about activities being created.
|
||||
ActivityThread.currentActivityThread().getApplication().registerActivityLifecycleCallbacks(
|
||||
activityThread.getApplication().registerActivityLifecycleCallbacks(
|
||||
new LifecycleCallbacks());
|
||||
// Intercept activity starts to route activities to new containers if necessary.
|
||||
Instrumentation instrumentation = activityThread.getInstrumentation();
|
||||
instrumentation.addMonitor(new ActivityStartMonitor());
|
||||
}
|
||||
|
||||
public void setSplitRules(@NonNull List<ExtensionSplitRule> splitRules) {
|
||||
@@ -118,7 +125,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
|
||||
container.setInfo(taskFragmentInfo);
|
||||
if (taskFragmentInfo.isEmpty()) {
|
||||
// Check if there are no running activities - consider the container empty if there are no
|
||||
// non-finishing activities left.
|
||||
if (!taskFragmentInfo.hasRunningActivity()) {
|
||||
cleanupContainer(container, true /* shouldFinishDependent */);
|
||||
updateCallbackIfNecessary();
|
||||
}
|
||||
@@ -664,4 +673,40 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
handler.post(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A monitor that intercepts all activity start requests originating in the client process and
|
||||
* can amend them to target a specific task fragment to form a split.
|
||||
*/
|
||||
private class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
|
||||
|
||||
@Override
|
||||
public Instrumentation.ActivityResult onStartActivity(Context who, Intent intent,
|
||||
Bundle options) {
|
||||
// TODO(b/190433398): Check if the activity is configured to always be expanded.
|
||||
|
||||
// Check if activity should be put in a split with the activity that launched it.
|
||||
if (!(who instanceof Activity)) {
|
||||
return super.onStartActivity(who, intent, options);
|
||||
}
|
||||
final Activity launchingActivity = (Activity) who;
|
||||
|
||||
final ExtensionSplitPairRule splitPairRule = getSplitRule(
|
||||
launchingActivity.getComponentName(), intent.getComponent(), getSplitRules());
|
||||
if (splitPairRule == null) {
|
||||
return super.onStartActivity(who, intent, options);
|
||||
}
|
||||
|
||||
// Create a new split with an empty side container
|
||||
final TaskFragmentContainer secondaryContainer = mPresenter
|
||||
.createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule);
|
||||
|
||||
// Amend the request to let the WM know that the activity should be placed in the
|
||||
// dedicated container.
|
||||
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
|
||||
secondaryContainer.getTaskFragmentToken());
|
||||
|
||||
return super.onStartActivity(who, intent, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,37 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
applyTransaction(wct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new split with the primary activity and an empty secondary container.
|
||||
* @return The newly created secondary container.
|
||||
*/
|
||||
TaskFragmentContainer createNewSplitWithEmptySideContainer(@NonNull Activity primaryActivity,
|
||||
@NonNull ExtensionSplitPairRule rule) {
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
|
||||
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
|
||||
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
|
||||
primaryActivity, primaryRectBounds, null);
|
||||
|
||||
// Create new empty task fragment
|
||||
TaskFragmentContainer secondaryContainer = mController.newContainer(null);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
|
||||
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
|
||||
primaryActivity.getActivityToken(), secondaryRectBounds,
|
||||
WINDOWING_MODE_MULTI_WINDOW);
|
||||
secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
|
||||
|
||||
// Set adjacent to each other so that the containers below will be invisible.
|
||||
wct.setAdjacentTaskFragments(
|
||||
primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken());
|
||||
applyTransaction(wct);
|
||||
|
||||
mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule);
|
||||
|
||||
return secondaryContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new split container with the two provided activities.
|
||||
* @param primaryActivity An activity that should be in the primary container. If it is not
|
||||
@@ -99,48 +130,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
|
||||
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
|
||||
primaryActivity.getActivityToken());
|
||||
if (primaryContainer == null) {
|
||||
primaryContainer = mController.newContainer(primaryActivity);
|
||||
|
||||
final TaskFragmentCreationParams fragmentOptions =
|
||||
createFragmentOptions(
|
||||
primaryContainer.getTaskFragmentToken(),
|
||||
primaryActivity.getActivityToken(),
|
||||
primaryRectBounds,
|
||||
WINDOWING_MODE_MULTI_WINDOW);
|
||||
wct.createTaskFragment(fragmentOptions);
|
||||
|
||||
wct.reparentActivityToTaskFragment(primaryContainer.getTaskFragmentToken(),
|
||||
primaryActivity.getActivityToken());
|
||||
|
||||
primaryContainer.setLastRequestedBounds(primaryRectBounds);
|
||||
} else {
|
||||
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
|
||||
}
|
||||
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
|
||||
primaryActivity, primaryRectBounds, null);
|
||||
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
|
||||
TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity(
|
||||
secondaryActivity.getActivityToken());
|
||||
if (secondaryContainer == null || secondaryContainer == primaryContainer) {
|
||||
secondaryContainer = mController.newContainer(secondaryActivity);
|
||||
|
||||
final TaskFragmentCreationParams fragmentOptions =
|
||||
createFragmentOptions(
|
||||
secondaryContainer.getTaskFragmentToken(),
|
||||
secondaryActivity.getActivityToken(),
|
||||
secondaryRectBounds,
|
||||
WINDOWING_MODE_MULTI_WINDOW);
|
||||
wct.createTaskFragment(fragmentOptions);
|
||||
|
||||
wct.reparentActivityToTaskFragment(secondaryContainer.getTaskFragmentToken(),
|
||||
secondaryActivity.getActivityToken());
|
||||
|
||||
secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
|
||||
} else {
|
||||
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
|
||||
}
|
||||
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
|
||||
secondaryActivity, secondaryRectBounds, primaryContainer);
|
||||
|
||||
// Set adjacent to each other so that the containers below will be invisible.
|
||||
wct.setAdjacentTaskFragments(
|
||||
@@ -150,6 +145,38 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new container or resizes an existing container for activity to the provided bounds.
|
||||
* @param activity The activity to be re-parented to the container if necessary.
|
||||
* @param containerToAvoid Re-parent from this container if an activity is already in it.
|
||||
*/
|
||||
private TaskFragmentContainer prepareContainerForActivity(
|
||||
@NonNull WindowContainerTransaction wct, @NonNull Activity activity,
|
||||
@NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
|
||||
TaskFragmentContainer container = mController.getContainerWithActivity(
|
||||
activity.getActivityToken());
|
||||
if (container == null || container == containerToAvoid) {
|
||||
container = mController.newContainer(activity);
|
||||
|
||||
final TaskFragmentCreationParams fragmentOptions =
|
||||
createFragmentOptions(
|
||||
container.getTaskFragmentToken(),
|
||||
activity.getActivityToken(),
|
||||
bounds,
|
||||
WINDOWING_MODE_MULTI_WINDOW);
|
||||
wct.createTaskFragment(fragmentOptions);
|
||||
|
||||
wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
|
||||
activity.getActivityToken());
|
||||
|
||||
container.setLastRequestedBounds(bounds);
|
||||
} else {
|
||||
resizeTaskFragmentIfRegistered(wct, container, bounds);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new activity to the side, creating a new split container. A new container will be
|
||||
* created for the activity that will be started.
|
||||
|
||||
@@ -2384,6 +2384,11 @@ class ActivityStarter {
|
||||
}
|
||||
mTransientLaunch = mOptions.getTransientLaunch();
|
||||
mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
|
||||
|
||||
if (inTaskFragment == null) {
|
||||
inTaskFragment = TaskFragment.fromTaskFragmentToken(
|
||||
mOptions.getLaunchTaskFragmentToken(), mService);
|
||||
}
|
||||
}
|
||||
|
||||
mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null;
|
||||
|
||||
@@ -293,6 +293,13 @@ class TaskFragment extends WindowContainer<WindowContainer> {
|
||||
mRemoteToken = new RemoteToken(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static TaskFragment fromTaskFragmentToken(@Nullable IBinder token,
|
||||
@NonNull ActivityTaskManagerService service) {
|
||||
if (token == null) return null;
|
||||
return service.mWindowOrganizerController.getTaskFragment(token);
|
||||
}
|
||||
|
||||
void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
|
||||
if (mAdjacentTaskFragment == taskFragment) {
|
||||
return;
|
||||
|
||||
@@ -1174,6 +1174,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
|
||||
taskFragment.removeImmediately();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
TaskFragment getTaskFragment(IBinder tfToken) {
|
||||
return mLaunchTaskFragments.get(tfToken);
|
||||
}
|
||||
|
||||
static class CallerInfo {
|
||||
final int mPid;
|
||||
final int mUid;
|
||||
|
||||
Reference in New Issue
Block a user