diff options
13 files changed, 642 insertions, 7 deletions
diff --git a/core/java/android/window/TaskFragmentAnimationParams.aidl b/core/java/android/window/TaskFragmentAnimationParams.aidl new file mode 100644 index 000000000000..04dee58089d4 --- /dev/null +++ b/core/java/android/window/TaskFragmentAnimationParams.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 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 android.window; + +/** + * Data object for animation related override of TaskFragment. + * @hide + */ +parcelable TaskFragmentAnimationParams; diff --git a/core/java/android/window/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java new file mode 100644 index 000000000000..a600a4db42b8 --- /dev/null +++ b/core/java/android/window/TaskFragmentAnimationParams.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 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 android.window; + +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data object for animation related override of TaskFragment. + * @hide + */ +// TODO(b/206557124): Add more animation customization options. +public final class TaskFragmentAnimationParams implements Parcelable { + + /** The default {@link TaskFragmentAnimationParams} to use when there is no app override. */ + public static final TaskFragmentAnimationParams DEFAULT = + new TaskFragmentAnimationParams.Builder().build(); + + @ColorInt + private final int mAnimationBackgroundColor; + + private TaskFragmentAnimationParams(@ColorInt int animationBackgroundColor) { + mAnimationBackgroundColor = animationBackgroundColor; + } + + /** + * The {@link ColorInt} to use for the background during the animation with this TaskFragment if + * the animation requires a background. + * + * The default value is {@code 0}, which is to use the theme window background. + */ + @ColorInt + public int getAnimationBackgroundColor() { + return mAnimationBackgroundColor; + } + + private TaskFragmentAnimationParams(Parcel in) { + mAnimationBackgroundColor = in.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mAnimationBackgroundColor); + } + + @NonNull + public static final Creator<TaskFragmentAnimationParams> CREATOR = + new Creator<TaskFragmentAnimationParams>() { + @Override + public TaskFragmentAnimationParams createFromParcel(Parcel in) { + return new TaskFragmentAnimationParams(in); + } + + @Override + public TaskFragmentAnimationParams[] newArray(int size) { + return new TaskFragmentAnimationParams[size]; + } + }; + + @Override + public String toString() { + return "TaskFragmentAnimationParams{" + + " animationBgColor=" + Integer.toHexString(mAnimationBackgroundColor) + + "}"; + } + + @Override + public int hashCode() { + return mAnimationBackgroundColor; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TaskFragmentAnimationParams)) { + return false; + } + final TaskFragmentAnimationParams other = (TaskFragmentAnimationParams) obj; + return mAnimationBackgroundColor == other.mAnimationBackgroundColor; + } + + @Override + public int describeContents() { + return 0; + } + + /** Builder to construct the {@link TaskFragmentAnimationParams}. */ + public static final class Builder { + + @ColorInt + private int mAnimationBackgroundColor = 0; + + /** + * Sets the {@link ColorInt} to use for the background during the animation with this + * TaskFragment if the animation requires a background. The default value is + * {@code 0}, which is to use the theme window background. + * + * @param color a packed color int, {@code AARRGGBB}, for the animation background color. + * @return this {@link Builder}. + */ + @NonNull + public Builder setAnimationBackgroundColor(@ColorInt int color) { + mAnimationBackgroundColor = color; + return this; + } + + /** Constructs the {@link TaskFragmentAnimationParams}. */ + @NonNull + public TaskFragmentAnimationParams build() { + return new TaskFragmentAnimationParams(mAnimationBackgroundColor); + } + } +} diff --git a/core/java/android/window/TaskFragmentOperation.aidl b/core/java/android/window/TaskFragmentOperation.aidl new file mode 100644 index 000000000000..c21700c6634b --- /dev/null +++ b/core/java/android/window/TaskFragmentOperation.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 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 android.window; + +/** + * Data object of params for TaskFragment related {@link WindowContainerTransaction} operation. + * @hide + */ +parcelable TaskFragmentOperation; diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java new file mode 100644 index 000000000000..bec6c58e4c8a --- /dev/null +++ b/core/java/android/window/TaskFragmentOperation.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2022 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 android.window; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Data object of params for TaskFragment related {@link WindowContainerTransaction} operation. + * + * @see WindowContainerTransaction#setTaskFragmentOperation(IBinder, TaskFragmentOperation). + * @hide + */ +// TODO(b/263436063): move other TaskFragment related operation here. +public final class TaskFragmentOperation implements Parcelable { + + /** Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment. */ + public static final int OP_TYPE_SET_ANIMATION_PARAMS = 0; + + @IntDef(prefix = { "OP_TYPE_" }, value = { + OP_TYPE_SET_ANIMATION_PARAMS + }) + @Retention(RetentionPolicy.SOURCE) + @interface OperationType {} + + @OperationType + private final int mOpType; + + @Nullable + private final TaskFragmentAnimationParams mAnimationParams; + + private TaskFragmentOperation(@OperationType int opType, + @Nullable TaskFragmentAnimationParams animationParams) { + mOpType = opType; + mAnimationParams = animationParams; + } + + private TaskFragmentOperation(Parcel in) { + mOpType = in.readInt(); + mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mOpType); + dest.writeTypedObject(mAnimationParams, flags); + } + + @NonNull + public static final Creator<TaskFragmentOperation> CREATOR = + new Creator<TaskFragmentOperation>() { + @Override + public TaskFragmentOperation createFromParcel(Parcel in) { + return new TaskFragmentOperation(in); + } + + @Override + public TaskFragmentOperation[] newArray(int size) { + return new TaskFragmentOperation[size]; + } + }; + + /** + * Gets the {@link OperationType} of this {@link TaskFragmentOperation}. + */ + @OperationType + public int getOpType() { + return mOpType; + } + + /** + * Gets the animation related override of TaskFragment. + */ + @Nullable + public TaskFragmentAnimationParams getAnimationParams() { + return mAnimationParams; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("TaskFragmentOperation{ opType=").append(mOpType); + if (mAnimationParams != null) { + sb.append(", animationParams=").append(mAnimationParams); + } + + sb.append('}'); + return sb.toString(); + } + + @Override + public int hashCode() { + int result = mOpType; + result = result * 31 + mAnimationParams.hashCode(); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof TaskFragmentOperation)) { + return false; + } + final TaskFragmentOperation other = (TaskFragmentOperation) obj; + return mOpType == other.mOpType + && Objects.equals(mAnimationParams, other.mAnimationParams); + } + + @Override + public int describeContents() { + return 0; + } + + /** Builder to construct the {@link TaskFragmentOperation}. */ + public static final class Builder { + + @OperationType + private final int mOpType; + + @Nullable + private TaskFragmentAnimationParams mAnimationParams; + + /** + * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}. + */ + public Builder(@OperationType int opType) { + mOpType = opType; + } + + /** + * Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment. + */ + @NonNull + public Builder setAnimationParams(@Nullable TaskFragmentAnimationParams animationParams) { + mAnimationParams = animationParams; + return this; + } + + /** + * Constructs the {@link TaskFragmentOperation}. + */ + @NonNull + public TaskFragmentOperation build() { + return new TaskFragmentOperation(mOpType, mAnimationParams); + } + } +} diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 5793674caaa6..647ccf51b5ef 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -751,6 +751,30 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Sets the {@link TaskFragmentOperation} to apply to the given TaskFragment. + * + * @param fragmentToken client assigned unique token to create TaskFragment with specified in + * {@link TaskFragmentCreationParams#getFragmentToken()}. + * @param taskFragmentOperation the {@link TaskFragmentOperation} to apply to the given + * TaskFramgent. + * @hide + */ + @NonNull + public WindowContainerTransaction setTaskFragmentOperation(@NonNull IBinder fragmentToken, + @NonNull TaskFragmentOperation taskFragmentOperation) { + Objects.requireNonNull(fragmentToken); + Objects.requireNonNull(taskFragmentOperation); + final HierarchyOp hierarchyOp = + new HierarchyOp.Builder( + HierarchyOp.HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION) + .setContainer(fragmentToken) + .setTaskFragmentOperation(taskFragmentOperation) + .build(); + mHierarchyOps.add(hierarchyOp); + return this; + } + + /** * Sets/removes the always on top flag for this {@code windowContainer}. See * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}. * Please note that this method is only intended to be used for a @@ -1261,6 +1285,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22; public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23; public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 24; + public static final int HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION = 25; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1301,10 +1326,14 @@ public final class WindowContainerTransaction implements Parcelable { @Nullable private Intent mActivityIntent; - // Used as options for WindowContainerTransaction#createTaskFragment(). + /** Used as options for {@link #createTaskFragment}. */ @Nullable private TaskFragmentCreationParams mTaskFragmentCreationOptions; + /** Used as options for {@link #setTaskFragmentOperation}. */ + @Nullable + private TaskFragmentOperation mTaskFragmentOperation; + @Nullable private PendingIntent mPendingIntent; @@ -1424,6 +1453,7 @@ public final class WindowContainerTransaction implements Parcelable { mLaunchOptions = copy.mLaunchOptions; mActivityIntent = copy.mActivityIntent; mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions; + mTaskFragmentOperation = copy.mTaskFragmentOperation; mPendingIntent = copy.mPendingIntent; mShortcutInfo = copy.mShortcutInfo; mAlwaysOnTop = copy.mAlwaysOnTop; @@ -1447,6 +1477,7 @@ public final class WindowContainerTransaction implements Parcelable { mLaunchOptions = in.readBundle(); mActivityIntent = in.readTypedObject(Intent.CREATOR); mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR); + mTaskFragmentOperation = in.readTypedObject(TaskFragmentOperation.CREATOR); mPendingIntent = in.readTypedObject(PendingIntent.CREATOR); mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR); mAlwaysOnTop = in.readBoolean(); @@ -1535,6 +1566,11 @@ public final class WindowContainerTransaction implements Parcelable { } @Nullable + public TaskFragmentOperation getTaskFragmentOperation() { + return mTaskFragmentOperation; + } + + @Nullable public PendingIntent getPendingIntent() { return mPendingIntent; } @@ -1612,6 +1648,9 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH: return "{setReparentLeafTaskIfRelaunch: container= " + mContainer + " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}"; + case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION: + return "{setTaskFragmentOperation: fragmentToken= " + mContainer + + " operation= " + mTaskFragmentOperation + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop @@ -1639,6 +1678,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBundle(mLaunchOptions); dest.writeTypedObject(mActivityIntent, flags); dest.writeTypedObject(mTaskFragmentCreationOptions, flags); + dest.writeTypedObject(mTaskFragmentOperation, flags); dest.writeTypedObject(mPendingIntent, flags); dest.writeTypedObject(mShortcutInfo, flags); dest.writeBoolean(mAlwaysOnTop); @@ -1696,6 +1736,9 @@ public final class WindowContainerTransaction implements Parcelable { private TaskFragmentCreationParams mTaskFragmentCreationOptions; @Nullable + private TaskFragmentOperation mTaskFragmentOperation; + + @Nullable private PendingIntent mPendingIntent; @Nullable @@ -1775,6 +1818,12 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + Builder setTaskFragmentOperation( + @Nullable TaskFragmentOperation taskFragmentOperation) { + mTaskFragmentOperation = taskFragmentOperation; + return this; + } + Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) { mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch; return this; @@ -1804,6 +1853,7 @@ public final class WindowContainerTransaction implements Parcelable { hierarchyOp.mPendingIntent = mPendingIntent; hierarchyOp.mAlwaysOnTop = mAlwaysOnTop; hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions; + hierarchyOp.mTaskFragmentOperation = mTaskFragmentOperation; hierarchyOp.mShortcutInfo = mShortcutInfo; hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index b910287aa535..87fa63d7fe14 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -17,6 +17,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -31,8 +32,10 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOperation; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -114,13 +117,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * @param activityIntent Intent to start the secondary Activity with. * @param activityOptions ActivityOptions to start the secondary Activity with. * @param windowingMode the windowing mode to set for the TaskFragments. + * @param splitAttributes the {@link SplitAttributes} to represent the split. */ void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, - @WindowingMode int windowingMode) { + @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) { final IBinder ownerToken = launchingActivity.getActivityToken(); // Create or resize the launching TaskFragment. @@ -131,6 +135,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken, launchingFragmentBounds, windowingMode, launchingActivity); } + updateAnimationParams(wct, launchingFragmentToken, splitAttributes); // Create a TaskFragment for the secondary activity. final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder( @@ -144,6 +149,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { .setPairedPrimaryFragmentToken(launchingFragmentToken) .build(); createTaskFragment(wct, fragmentOptions); + updateAnimationParams(wct, secondaryFragmentToken, splitAttributes); wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent, activityOptions); @@ -163,6 +169,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { resizeTaskFragment(wct, fragmentToken, new Rect()); setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */); updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); + updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); } /** @@ -175,6 +182,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { createTaskFragmentAndReparentActivity( wct, fragmentToken, activity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED, activity); + updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); } /** @@ -270,6 +278,24 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode); } + /** + * Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on + * {@link SplitAttributes}. + */ + void updateAnimationParams(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) { + updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes)); + } + + void updateAnimationParams(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_ANIMATION_PARAMS) + .setAnimationParams(animationParams) + .build(); + wct.setTaskFragmentOperation(fragmentToken, operation); + } + void deleteTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken) { if (!mFragmentInfos.containsKey(fragmentToken)) { @@ -291,4 +317,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { mCallback.onTransactionReady(transaction); } + + private static TaskFragmentAnimationParams createAnimationParamsOrDefault( + @Nullable SplitAttributes splitAttributes) { + if (splitAttributes == null) { + return TaskFragmentAnimationParams.DEFAULT; + } + return new TaskFragmentAnimationParams.Builder() + .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor()) + .build(); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index ce7d695beb2a..1e004a722cef 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -65,6 +65,7 @@ import android.util.Pair; import android.util.Size; import android.util.SparseArray; import android.view.WindowMetrics; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; @@ -1157,6 +1158,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen taskId); mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(), activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); + mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(), + TaskFragmentAnimationParams.DEFAULT); return expandedContainer; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 9db9f8788190..7b2af4933e66 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -36,6 +36,7 @@ import android.util.Size; import android.view.View; import android.view.WindowInsets; import android.view.WindowMetrics; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentCreationParams; import android.window.WindowContainerTransaction; @@ -176,7 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, - primaryActivity, primaryRectBounds, null); + primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */); // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); @@ -189,6 +190,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), primaryActivity.getActivityToken(), secondaryRectBounds, windowingMode); + updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, @@ -222,7 +224,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties, splitAttributes); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, - primaryActivity, primaryRectBounds, null); + primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties, splitAttributes); @@ -236,7 +238,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { containerToAvoid = curSecondaryContainer; } final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, - secondaryActivity, secondaryRectBounds, containerToAvoid); + secondaryActivity, secondaryRectBounds, splitAttributes, containerToAvoid); // Set adjacent to each other so that the containers below will be invisible. setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, @@ -253,7 +255,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { */ private TaskFragmentContainer prepareContainerForActivity( @NonNull WindowContainerTransaction wct, @NonNull Activity activity, - @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) { + @NonNull Rect bounds, @NonNull SplitAttributes splitAttributes, + @Nullable TaskFragmentContainer containerToAvoid) { TaskFragmentContainer container = mController.getContainerWithActivity(activity); final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); if (container == null || container == containerToAvoid) { @@ -270,6 +273,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { .getWindowingModeForSplitTaskFragment(bounds); updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); } + updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes); return container; } @@ -314,7 +318,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { rule, splitAttributes); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, - activityIntent, activityOptions, rule, windowingMode); + activityIntent, activityOptions, rule, windowingMode, splitAttributes); if (isPlaceholder) { // When placeholder is launched in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -365,6 +369,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { primaryRectBounds); updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode); updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); + updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes); + updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @@ -459,6 +465,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.updateWindowingMode(wct, fragmentToken, windowingMode); } + @Override + void updateAnimationParams(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) { + final TaskFragmentContainer container = mController.getContainer(fragmentToken); + if (container == null) { + throw new IllegalStateException("Setting animation params for a task fragment that is" + + " not registered with controller."); + } + + if (container.areLastRequestedAnimationParamsEqual(animationParams)) { + // Return early if the animation params were already requested + return; + } + + container.setLastRequestAnimationParams(animationParams); + super.updateAnimationParams(wct, fragmentToken, animationParams); + } + /** * Expands the split container if the current split bounds are smaller than the Activity or * Intent that is added to the container. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 6bfdfe7593b8..076856c373d6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.util.Size; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -108,6 +109,13 @@ class TaskFragmentContainer { private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED; /** + * TaskFragmentAnimationParams that was requested last via + * {@link android.window.WindowContainerTransaction}. + */ + @NonNull + private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT; + + /** * When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment * if it is still empty after the timeout. */ @@ -560,6 +568,21 @@ class TaskFragmentContainer { mLastRequestedWindowingMode = windowingModes; } + /** + * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value. + */ + boolean areLastRequestedAnimationParamsEqual( + @NonNull TaskFragmentAnimationParams animationParams) { + return mLastAnimationParams.equals(animationParams); + } + + /** + * Updates the last requested {@link TaskFragmentAnimationParams}. + */ + void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) { + mLastAnimationParams = animationParams; + } + /** Gets the parent leaf Task id. */ int getTaskId() { return mTaskContainer.getTaskId(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 6dae0a1086b3..fcd4d621e753 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -19,6 +19,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; @@ -60,12 +61,15 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Rect; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.util.Pair; import android.util.Size; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOperation; import android.window.WindowContainerTransaction; import androidx.test.core.app.ApplicationProvider; @@ -163,7 +167,38 @@ public class SplitPresenterTest { WINDOWING_MODE_MULTI_WINDOW); verify(mTransaction, never()).setWindowingMode(any(), anyInt()); + } + + @Test + public void testUpdateAnimationParams() { + final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + + // Verify the default. + assertTrue(container.areLastRequestedAnimationParamsEqual( + TaskFragmentAnimationParams.DEFAULT)); + + final int bgColor = Color.GREEN; + final TaskFragmentAnimationParams animationParams = + new TaskFragmentAnimationParams.Builder() + .setAnimationBackgroundColor(bgColor) + .build(); + mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(), + animationParams); + + final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_ANIMATION_PARAMS) + .setAnimationParams(animationParams) + .build(); + verify(mTransaction).setTaskFragmentOperation(container.getTaskFragmentToken(), + expectedOperation); + assertTrue(container.areLastRequestedAnimationParamsEqual(animationParams)); + + // No request to set the same animation params. + clearInvocations(mTransaction); + mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(), + animationParams); + verify(mTransaction, never()).setTaskFragmentOperation(any(), any()); } @Test diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index b8878618b21c..dd489aa1447e 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -96,6 +96,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; import android.window.ScreenCapture; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizerToken; @@ -306,6 +307,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { @Nullable private final IBinder mFragmentToken; + /** The animation override params for animation running on this TaskFragment. */ + @NonNull + private TaskFragmentAnimationParams mAnimationParams = TaskFragmentAnimationParams.DEFAULT; + /** * The bounds of the embedded TaskFragment relative to the parent Task. * {@code null} if it is not {@link #mIsEmbedded} @@ -453,6 +458,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { && organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder()); } + void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) { + mAnimationParams = animationParams; + } + + @NonNull + TaskFragmentAnimationParams getAnimationParams() { + return mAnimationParams; + } + TaskFragment getAdjacentTaskFragment() { return mAdjacentTaskFragment; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index b624e8064296..3ab275d827f9 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.isStartResultSuccessful; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS; @@ -44,6 +45,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT; @@ -88,7 +90,9 @@ import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.IWindowOrganizerController; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentCreationParams; +import android.window.TaskFragmentOperation; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; @@ -1138,6 +1142,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub fragment.setCompanionTaskFragment(companion); break; } + case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION: { + effects |= applyTaskFragmentOperation(hop, errorCallbackToken, organizer); + break; + } default: { // The other operations may change task order so they are skipped while in lock // task mode. The above operations are still allowed because they don't move @@ -1270,6 +1278,47 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return effects; } + /** Applies change set through {@link WindowContainerTransaction#setTaskFragmentOperation}. */ + private int applyTaskFragmentOperation(@NonNull WindowContainerTransaction.HierarchyOp hop, + @Nullable IBinder errorCallbackToken, @Nullable ITaskFragmentOrganizer organizer) { + final IBinder fragmentToken = hop.getContainer(); + final TaskFragment taskFragment = mLaunchTaskFragments.get(fragmentToken); + final TaskFragmentOperation operation = hop.getTaskFragmentOperation(); + if (operation == null) { + final Throwable exception = new IllegalArgumentException( + "TaskFragmentOperation must be non-null"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception); + return 0; + } + final int opType = operation.getOpType(); + if (taskFragment == null || !taskFragment.isAttached()) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to apply operation on invalid fragment tokens opType=" + opType); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception); + return 0; + } + + int effect = 0; + switch (opType) { + case OP_TYPE_SET_ANIMATION_PARAMS: { + final TaskFragmentAnimationParams animationParams = operation.getAnimationParams(); + if (animationParams == null) { + final Throwable exception = new IllegalArgumentException( + "TaskFragmentAnimationParams must be non-null"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception); + break; + } + taskFragment.setAnimationParams(animationParams); + break; + } + // TODO(b/263436063): move other TaskFragment related operation here. + } + return effect; + } + /** A helper method to send minimum dimension violation error to the client. */ private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, IBinder errorCallbackToken, String reason) { @@ -1698,6 +1747,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT: + case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION: enforceTaskFragmentOrganized(func, hop.getContainer(), organizer); break; case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 26fe5214a7ea..b70d8bd50917 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; @@ -73,6 +74,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; @@ -83,8 +85,10 @@ import android.platform.test.annotations.Presubmit; import android.view.RemoteAnimationDefinition; import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentOperation; import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentParentInfo; @@ -689,6 +693,59 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_enforceTaskFragmentOrganized_setTaskFragmentOperation() { + final Task task = createTask(mDisplayContent); + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(mFragmentToken) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_ANIMATION_PARAMS) + .setAnimationParams(TaskFragmentAnimationParams.DEFAULT) + .build(); + mTransaction.setTaskFragmentOperation(mFragmentToken, operation); + mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Not allowed because TaskFragment is not organized by the caller organizer. + assertApplyTransactionDisallowed(mTransaction); + + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + + assertApplyTransactionAllowed(mTransaction); + } + + @Test + public void testSetTaskFragmentOperation() { + final Task task = createTask(mDisplayContent); + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(mOrganizer) + .setFragmentToken(mFragmentToken) + .build(); + assertEquals(TaskFragmentAnimationParams.DEFAULT, mTaskFragment.getAnimationParams()); + + mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); + final TaskFragmentAnimationParams animationParams = + new TaskFragmentAnimationParams.Builder() + .setAnimationBackgroundColor(Color.GREEN) + .build(); + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_ANIMATION_PARAMS) + .setAnimationParams(animationParams) + .build(); + mTransaction.setTaskFragmentOperation(mFragmentToken, operation); + mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + assertApplyTransactionAllowed(mTransaction); + + assertEquals(animationParams, mTaskFragment.getAnimationParams()); + assertEquals(Color.GREEN, mTaskFragment.getAnimationParams().getAnimationBackgroundColor()); + } + + @Test public void testApplyTransaction_createTaskFragment_failForDifferentUid() { final ActivityRecord activity = createActivityRecord(mDisplayContent); final int uid = Binder.getCallingUid(); |