Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a3e6496 authored by Andrii Kulian's avatar Andrii Kulian Committed by Automerger Merge Worker
Browse files

Merge "Intercept activity start requests in client organizer" into sc-v2-dev am: 94a6a10f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15411942

Change-Id: Ie5535035dc6fe2db01fc490b9c7aca18e87467d3
parents b40df2cb 94a6a10f
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -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);
        }
+35 −6
Original line number Diff line number Diff line
@@ -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++;
+15 −9
Original line number Diff line number Diff line
@@ -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,
+47 −2
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+62 −35
Original line number Diff line number Diff line
@@ -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,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