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 = public static final String KEY_LAUNCH_ROOT_TASK_TOKEN =
"android.activity.launchRootTaskToken"; "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. * The windowing mode the activity should be launched into.
* @hide * @hide
@@ -396,6 +404,7 @@ public class ActivityOptions {
private int mCallerDisplayId = INVALID_DISPLAY; private int mCallerDisplayId = INVALID_DISPLAY;
private WindowContainerToken mLaunchTaskDisplayArea; private WindowContainerToken mLaunchTaskDisplayArea;
private WindowContainerToken mLaunchRootTask; private WindowContainerToken mLaunchRootTask;
private IBinder mLaunchTaskFragmentToken;
@WindowConfiguration.WindowingMode @WindowConfiguration.WindowingMode
private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED; private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
@WindowConfiguration.ActivityType @WindowConfiguration.ActivityType
@@ -1138,6 +1147,7 @@ public class ActivityOptions {
mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY); mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN); mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_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); mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED); mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
@@ -1472,6 +1482,17 @@ public class ActivityOptions {
return this; return this;
} }
/** @hide */
public IBinder getLaunchTaskFragmentToken() {
return mLaunchTaskFragmentToken;
}
/** @hide */
public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) {
mLaunchTaskFragmentToken = taskFragmentToken;
return this;
}
/** @hide */ /** @hide */
public int getLaunchWindowingMode() { public int getLaunchWindowingMode() {
return mLaunchWindowingMode; return mLaunchWindowingMode;
@@ -1882,6 +1903,9 @@ public class ActivityOptions {
if (mLaunchRootTask != null) { if (mLaunchRootTask != null) {
b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask); b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
} }
if (mLaunchTaskFragmentToken != null) {
b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken);
}
if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) { if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode); 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. * Used for intercepting any started activity.
* *
@@ -1722,7 +1733,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i); final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null; ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) { if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent); if (options == null) {
options = ActivityOptions.makeBasic().toBundle();
}
result = am.onStartActivity(who, intent, options);
} }
if (result != null) { if (result != null) {
am.mHits++; am.mHits++;
@@ -1790,7 +1804,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i); final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null; ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) { 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) { if (result != null) {
am.mHits++; am.mHits++;
@@ -1861,7 +1878,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i); final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null; ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) { if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent); if (options == null) {
options = ActivityOptions.makeBasic().toBundle();
}
result = am.onStartActivity(who, intent, options);
} }
if (result != null) { if (result != null) {
am.mHits++; am.mHits++;
@@ -1928,7 +1948,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i); final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null; ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) { if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent); if (options == null) {
options = ActivityOptions.makeBasic().toBundle();
}
result = am.onStartActivity(who, intent, options);
} }
if (result != null) { if (result != null) {
am.mHits++; am.mHits++;
@@ -1974,7 +1997,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i); final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null; ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) { if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent); if (options == null) {
options = ActivityOptions.makeBasic().toBundle();
}
result = am.onStartActivity(who, intent, options);
} }
if (result != null) { if (result != null) {
am.mHits++; am.mHits++;
@@ -2021,7 +2047,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i); final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null; ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) { if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent); if (options == null) {
options = ActivityOptions.makeBasic().toBundle();
}
result = am.onStartActivity(who, intent, options);
} }
if (result != null) { if (result != null) {
am.mHits++; am.mHits++;

View File

@@ -155,6 +155,17 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
applyTransaction(wct); 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 * @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. * 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( private void createTaskFragmentAndReparentActivity(
WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
@NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) { @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
final TaskFragmentCreationParams fragmentOptions = createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode); wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
wct.createTaskFragment(fragmentOptions)
.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
} }
/** /**
@@ -176,11 +185,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
@NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent, @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
@Nullable Bundle activityOptions) { @Nullable Bundle activityOptions) {
final TaskFragmentCreationParams fragmentOptions = createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode); wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
wct.createTaskFragment(fragmentOptions)
.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent,
activityOptions);
} }
TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken, TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,

View File

@@ -20,9 +20,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityClient; import android.app.ActivityClient;
import android.app.ActivityOptions;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.Application.ActivityLifecycleCallbacks; import android.app.Application.ActivityLifecycleCallbacks;
import android.app.Instrumentation;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
@@ -61,9 +64,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
public SplitController() { public SplitController() {
mPresenter = new SplitPresenter(new MainThreadExecutor(), this); mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
ActivityThread activityThread = ActivityThread.currentActivityThread();
// Register a callback to be notified about activities being created. // Register a callback to be notified about activities being created.
ActivityThread.currentActivityThread().getApplication().registerActivityLifecycleCallbacks( activityThread.getApplication().registerActivityLifecycleCallbacks(
new LifecycleCallbacks()); 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) { public void setSplitRules(@NonNull List<ExtensionSplitRule> splitRules) {
@@ -118,7 +125,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
container.setInfo(taskFragmentInfo); 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 */); cleanupContainer(container, true /* shouldFinishDependent */);
updateCallbackIfNecessary(); updateCallbackIfNecessary();
} }
@@ -664,4 +673,40 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
handler.post(r); 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); 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. * 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 * @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 parentBounds = getParentContainerBounds(primaryActivity);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule); final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity.getActivityToken()); primaryActivity, primaryRectBounds, null);
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 Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity( final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
secondaryActivity.getActivityToken()); secondaryActivity, secondaryRectBounds, primaryContainer);
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);
}
// Set adjacent to each other so that the containers below will be invisible. // Set adjacent to each other so that the containers below will be invisible.
wct.setAdjacentTaskFragments( wct.setAdjacentTaskFragments(
@@ -150,6 +145,38 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule); 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 * 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. * created for the activity that will be started.

View File

@@ -2384,6 +2384,11 @@ class ActivityStarter {
} }
mTransientLaunch = mOptions.getTransientLaunch(); mTransientLaunch = mOptions.getTransientLaunch();
mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask()); mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
if (inTaskFragment == null) {
inTaskFragment = TaskFragment.fromTaskFragmentToken(
mOptions.getLaunchTaskFragmentToken(), mService);
}
} }
mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null; 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); 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) { void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
if (mAdjacentTaskFragment == taskFragment) { if (mAdjacentTaskFragment == taskFragment) {
return; return;

View File

@@ -1174,6 +1174,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
taskFragment.removeImmediately(); taskFragment.removeImmediately();
} }
@Nullable
TaskFragment getTaskFragment(IBinder tfToken) {
return mLaunchTaskFragments.get(tfToken);
}
static class CallerInfo { static class CallerInfo {
final int mPid; final int mPid;
final int mUid; final int mUid;