Add default implementation of TaskFragmentOrganizer
Bug: 190433398 Test: N/A Change-Id: I84fdba974ef308aebe19348fa76f565311e06f2e
This commit is contained in:
@@ -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
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user