Merge "Intercept activity start requests in client organizer" into sc-v2-dev

This commit is contained in:
Andrii Kulian
2021-08-04 02:23:59 +00:00
committed by Android (Google) Code Review
8 changed files with 205 additions and 57 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.

View File

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

View File

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

View File

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