From 964d297792e5e044bc57c226ec5ecb1f736702e9 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Thu, 24 Jun 2021 19:25:59 -0700 Subject: [PATCH] Add default implementation of TaskFragmentOrganizer Bug: 190433398 Test: N/A Change-Id: I84fdba974ef308aebe19348fa76f565311e06f2e --- .../java/android/window/TaskFragmentInfo.java | 30 +- .../JetpackTaskFragmentOrganizer.java | 260 ++++++++++++++++++ .../com/android/server/wm/ActivityRecord.java | 7 + .../com/android/server/wm/TaskFragment.java | 12 +- .../server/wm/WindowOrganizerController.java | 4 +- 5 files changed, 302 insertions(+), 11 deletions(-) create mode 100644 libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index 6d4a2f130129b..45975dbbda0bb 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -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 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 activities) { - if (fragmentToken == null) { - throw new IllegalArgumentException("Invalid TaskFragmentInfo."); - } - mFragmentToken = fragmentToken; - mToken = token; + List 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 + "}"; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java new file mode 100644 index 0000000000000..48d78cf10a2af --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java @@ -0,0 +1,260 @@ +/* + * 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 mFragmentInfos = new ArrayMap<>(); + + /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */ + private final Map mFragmentLeashes = new ArrayMap<>(); + + /** + * Mapping from the client assigned unique token to the TaskFragment parent + * {@link Configuration}. + */ + final Map 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); + } + } +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0d439456c3acb..3e00156e03f60 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1875,6 +1875,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() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 59c3ffedb90e2..9d24da00de099 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -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 { } } + 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 { 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 diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 520a104f80217..45b5875864819 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -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"); } }