Loading core/java/android/app/ActivityOptions.java +24 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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); } Loading core/java/android/app/Instrumentation.java +35 −6 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java +15 −9 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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()); } /** Loading @@ -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, Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java +47 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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(); } Loading Loading @@ -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); } } } libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java +62 −35 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -99,55 +130,51 @@ 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 TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( primaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), primaryRectBounds, WINDOWING_MODE_MULTI_WINDOW); wct.createTaskFragment(fragmentOptions); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, secondaryActivity, secondaryRectBounds, primaryContainer); wct.reparentActivityToTaskFragment(primaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken()); // Set adjacent to each other so that the containers below will be invisible. wct.setAdjacentTaskFragments( primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken()); applyTransaction(wct); primaryContainer.setLastRequestedBounds(primaryRectBounds); } else { resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule); } final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity( secondaryActivity.getActivityToken()); if (secondaryContainer == null || secondaryContainer == primaryContainer) { secondaryContainer = mController.newContainer(secondaryActivity); /** * 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( secondaryContainer.getTaskFragmentToken(), secondaryActivity.getActivityToken(), secondaryRectBounds, container.getTaskFragmentToken(), activity.getActivityToken(), bounds, WINDOWING_MODE_MULTI_WINDOW); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(secondaryContainer.getTaskFragmentToken(), secondaryActivity.getActivityToken()); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), activity.getActivityToken()); secondaryContainer.setLastRequestedBounds(secondaryRectBounds); container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); resizeTaskFragmentIfRegistered(wct, container, bounds); } // 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 container; } /** Loading Loading
core/java/android/app/ActivityOptions.java +24 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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); } Loading
core/java/android/app/Instrumentation.java +35 −6 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading Loading @@ -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++; Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java +15 −9 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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()); } /** Loading @@ -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, Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java +47 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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(); } Loading Loading @@ -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); } } }
libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java +62 −35 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -99,55 +130,51 @@ 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 TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); final TaskFragmentCreationParams fragmentOptions = createFragmentOptions( primaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), primaryRectBounds, WINDOWING_MODE_MULTI_WINDOW); wct.createTaskFragment(fragmentOptions); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, secondaryActivity, secondaryRectBounds, primaryContainer); wct.reparentActivityToTaskFragment(primaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken()); // Set adjacent to each other so that the containers below will be invisible. wct.setAdjacentTaskFragments( primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken()); applyTransaction(wct); primaryContainer.setLastRequestedBounds(primaryRectBounds); } else { resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule); } final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule); TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity( secondaryActivity.getActivityToken()); if (secondaryContainer == null || secondaryContainer == primaryContainer) { secondaryContainer = mController.newContainer(secondaryActivity); /** * 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( secondaryContainer.getTaskFragmentToken(), secondaryActivity.getActivityToken(), secondaryRectBounds, container.getTaskFragmentToken(), activity.getActivityToken(), bounds, WINDOWING_MODE_MULTI_WINDOW); wct.createTaskFragment(fragmentOptions); wct.reparentActivityToTaskFragment(secondaryContainer.getTaskFragmentToken(), secondaryActivity.getActivityToken()); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), activity.getActivityToken()); secondaryContainer.setLastRequestedBounds(secondaryRectBounds); container.setLastRequestedBounds(bounds); } else { resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); resizeTaskFragmentIfRegistered(wct, container, bounds); } // 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 container; } /** Loading