Add default implementation of TaskFragmentOrganizer

Bug: 190433398
Test: N/A
Change-Id: I84fdba974ef308aebe19348fa76f565311e06f2e
This commit is contained in:
Chris Li
2021-06-24 19:25:59 -07:00
committed by Andrii Kulian
parent b9745dc605
commit 964d297792
5 changed files with 302 additions and 11 deletions

View File

@@ -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
+ "}";
}

View File

@@ -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<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);
}
}
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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");
}
}