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

Commit f7224f06 authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Add default implementation of TaskFragmentOrganizer" into sc-v2-dev

parents 6e8c4ca6 964d2977
Loading
Loading
Loading
Loading
+22 −8
Original line number Diff line number Diff line
@@ -18,9 +18,12 @@ package android.window;

import static android.app.WindowConfiguration.WindowingMode;

import static java.util.Objects.requireNonNull;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,7 +38,7 @@ import java.util.List;
public final class TaskFragmentInfo implements Parcelable {

    /**
     * Client assigned unique token in {@link TaskFragmentCreationParams#fragmentToken} to create
     * Client assigned unique token in {@link TaskFragmentCreationParams#mFragmentToken} to create
     * this TaskFragment with.
     */
    @NonNull
@@ -59,19 +62,20 @@ public final class TaskFragmentInfo implements Parcelable {
     */
    private final List<IBinder> mActivities = new ArrayList<>();

    /** Relative position of the fragment's top left corner in the parent container. */
    private final Point mPositionInParent;

    public TaskFragmentInfo(
            @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token,
            @NonNull Configuration configuration, boolean isEmpty, boolean isVisible,
            List<IBinder> activities) {
        if (fragmentToken == null) {
            throw new IllegalArgumentException("Invalid TaskFragmentInfo.");
        }
        mFragmentToken = fragmentToken;
        mToken = token;
            List<IBinder> activities, @NonNull Point positionInParent) {
        mFragmentToken = requireNonNull(fragmentToken);
        mToken = requireNonNull(token);
        mConfiguration.setTo(configuration);
        mIsEmpty = isEmpty;
        mIsVisible = isVisible;
        mActivities.addAll(activities);
        mPositionInParent = requireNonNull(positionInParent);
    }

    public IBinder getFragmentToken() {
@@ -98,6 +102,12 @@ public final class TaskFragmentInfo implements Parcelable {
        return mActivities;
    }

    /** Returns the relative position of the fragment's top left corner in the parent container. */
    @NonNull
    public Point getPositionInParent() {
        return mPositionInParent;
    }

    @WindowingMode
    public int getWindowingMode() {
        return mConfiguration.windowConfiguration.getWindowingMode();
@@ -117,7 +127,8 @@ public final class TaskFragmentInfo implements Parcelable {
                && mIsEmpty == that.mIsEmpty
                && mIsVisible == that.mIsVisible
                && getWindowingMode() == that.getWindowingMode()
                && mActivities.equals(that.mActivities);
                && mActivities.equals(that.mActivities)
                && mPositionInParent.equals(that.mPositionInParent);
    }

    private TaskFragmentInfo(Parcel in) {
@@ -127,6 +138,7 @@ public final class TaskFragmentInfo implements Parcelable {
        mIsEmpty = in.readBoolean();
        mIsVisible = in.readBoolean();
        in.readBinderList(mActivities);
        mPositionInParent = requireNonNull(in.readTypedObject(Point.CREATOR));
    }

    @Override
@@ -137,6 +149,7 @@ public final class TaskFragmentInfo implements Parcelable {
        dest.writeBoolean(mIsEmpty);
        dest.writeBoolean(mIsVisible);
        dest.writeBinderList(mActivities);
        dest.writeTypedObject(mPositionInParent, flags);
    }

    @NonNull
@@ -160,6 +173,7 @@ public final class TaskFragmentInfo implements Parcelable {
                + " token=" + mToken
                + " isEmpty=" + mIsEmpty
                + " isVisible=" + mIsVisible
                + " positionInParent=" + mPositionInParent
                + "}";
    }

+260 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.window.extensions.organizer;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Map;
import java.util.concurrent.Executor;

/**
 * Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize
 * task fragments.
 *
 * All calls into methods of this class are expected to be on the UI thread.
 */
class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {

    /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
    private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();

    /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */
    private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>();

    /**
     * Mapping from the client assigned unique token to the TaskFragment parent
     * {@link Configuration}.
     */
    final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>();

    private final TaskFragmentCallback mCallback;

    /**
     * Callback that notifies the controller about changes to task fragments.
     */
    interface TaskFragmentCallback {
        void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo);
        void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
        void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
        void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
                @NonNull Configuration parentConfig);
    }

    /**
     * @param executor  callbacks from WM Core are posted on this executor. It should be tied to the
     *                  UI thread that all other calls into methods of this class are also on.
     */
    JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
        super(executor);
        mCallback = callback;
    }

    /**
     * Starts a new Activity and puts it into split with an existing Activity side-by-side.
     * @param launchingFragmentToken    token for the launching TaskFragment. If it exists, it will
     *                                  be resized based on {@param launchingFragmentBounds}.
     *                                  Otherwise, we will create a new TaskFragment with the given
     *                                  token for the {@param launchingActivity}.
     * @param launchingFragmentBounds   the initial bounds for the launching TaskFragment.
     * @param launchingActivity the Activity to put on the left hand side of the split as the
     *                          primary.
     * @param secondaryFragmentToken    token to create the secondary TaskFragment with.
     * @param secondaryFragmentBounds   the initial bounds for the secondary TaskFragment
     * @param activityIntent    Intent to start the secondary Activity with.
     * @param activityOptions   ActivityOptions to start the secondary Activity with.
     */
    void startActivityToSide(IBinder launchingFragmentToken, Rect launchingFragmentBounds,
            Activity launchingActivity, IBinder secondaryFragmentToken,
            Rect secondaryFragmentBounds,  Intent activityIntent,
            @Nullable Bundle activityOptions) {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final IBinder ownerToken = launchingActivity.getActivityToken();

        // Create or resize the launching TaskFragment.
        if (mFragmentInfos.containsKey(launchingFragmentToken)) {
            resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
        } else {
            createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
                    launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity);
        }

        // Create a TaskFragment for the secondary activity.
        createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
                secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent,
                activityOptions);

        applyTransaction(wct);
    }

    /**
     * Expands an existing TaskFragment to fill parent.
     * @param wct WindowContainerTransaction in which the task fragment should be resized.
     * @param fragmentToken token of an existing TaskFragment.
     */
    void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
        resizeTaskFragment(wct, fragmentToken, new Rect());
    }

    /**
     * Expands an existing TaskFragment to fill parent.
     * @param fragmentToken token of an existing TaskFragment.
     */
    void expandTaskFragment(IBinder fragmentToken) {
        WindowContainerTransaction wct = new WindowContainerTransaction();
        expandTaskFragment(wct, fragmentToken);
        applyTransaction(wct);
    }

    /**
     * Expands an Activity to fill parent by moving it to a new TaskFragment.
     * @param fragmentToken token to create new TaskFragment with.
     * @param activity      activity to move to the fill-parent TaskFragment.
     */
    void expandActivity(IBinder fragmentToken, Activity activity) {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        createTaskFragmentAndReparentActivity(
                wct, fragmentToken, activity.getActivityToken(), new Rect(),
                WINDOWING_MODE_UNDEFINED, activity);
        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.
     */
    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());
    }

    /**
     * @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.
     */
    private void createTaskFragmentAndStartActivity(
            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, activityIntent, activityOptions);
    }

    TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
            Rect bounds, @WindowingMode int windowingMode) {
        if (mFragmentInfos.containsKey(fragmentToken)) {
            throw new IllegalArgumentException(
                    "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
        }

        return new TaskFragmentCreationParams.Builder(
                getIOrganizer(),
                fragmentToken,
                ownerToken)
                .setInitialBounds(bounds)
                .setWindowingMode(windowingMode)
                .build();
    }

    void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
            @Nullable Rect bounds) {
        if (!mFragmentInfos.containsKey(fragmentToken)) {
            throw new IllegalArgumentException(
                    "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
        }
        if (bounds == null) {
            bounds = new Rect();
        }
        wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
    }

    void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
        if (!mFragmentInfos.containsKey(fragmentToken)) {
            throw new IllegalArgumentException(
                    "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
        }
        wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
    }

    @Override
    public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
        final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo();
        final IBinder fragmentToken = info.getFragmentToken();
        final SurfaceControl leash = taskFragmentAppearedInfo.getLeash();
        mFragmentInfos.put(fragmentToken, info);
        mFragmentLeashes.put(fragmentToken, leash);

        if (mCallback != null) {
            mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo);
        }
    }

    @Override
    public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
        final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
        mFragmentInfos.put(fragmentToken, taskFragmentInfo);

        if (mCallback != null) {
            mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
        }
    }

    @Override
    public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
        mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
        mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken());
        mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken());

        if (mCallback != null) {
            mCallback.onTaskFragmentVanished(taskFragmentInfo);
        }
    }

    @Override
    public void onTaskFragmentParentInfoChanged(
            @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
        mFragmentParentConfigs.put(fragmentToken, parentConfig);

        if (mCallback != null) {
            mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig);
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -1885,6 +1885,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            task.setRootProcess(proc);
        }
        proc.addActivityIfNeeded(this);

        // Update the associated task fragment after setting the process, since it's required for
        // filtering to only report activities that belong to the same process.
        final TaskFragment tf = getTaskFragment();
        if (tf != null) {
            tf.sendTaskFragmentInfoChanged();
        }
    }

    boolean hasProcess() {
+11 −1
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
@@ -1475,6 +1476,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        }
    }

    void onChildPositionChanged(WindowContainer child) {
        super.onChildPositionChanged(child);

        sendTaskFragmentInfoChanged();
    }

    void executeAppTransition(ActivityOptions options) {
        // No app transition applied to the task fragment.
    }
@@ -2027,13 +2034,16 @@ class TaskFragment extends WindowContainer<WindowContainer> {
                childActivities.add(wc.asActivityRecord().appToken);
            }
        }
        final Point positionInParent = new Point();
        getRelativePosition(positionInParent);
        return new TaskFragmentInfo(
                mFragmentToken,
                mRemoteToken.toWindowContainerToken(),
                getConfiguration(),
                getChildCount() == 0,
                isVisible(),
                childActivities);
                childActivities,
                positionInParent);
    }

    @Nullable
+2 −2
Original line number Diff line number Diff line
@@ -878,8 +878,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
    }

    private void sanitizeWindowContainer(WindowContainer wc) {
        if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) {
            throw new RuntimeException("Invalid token in task or displayArea transaction");
        if (!(wc instanceof TaskFragment) && !(wc instanceof DisplayArea)) {
            throw new RuntimeException("Invalid token in task fragment or displayArea transaction");
        }
    }