From 6b94ca2d5f5ead527532b2a1c489c861485bacb6 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Wed, 27 Jul 2022 15:43:30 +0800 Subject: [PATCH] Convert TaskFragmentOrganizer callbacks to a transaction callback Before, we have several different callbacks, such as onTaskFragmentAppeared, onTaskFragmentInfoChanged, etc. As migrating to Shell transition, we need a new model to better sync between app process, WM Core and Shell. The flow will be: 1. WM Shell startTransition with a WindowContainerTransaction 2. After apply the WCT, notify TaskFragmentOrganizerController 3. TFOrganizerController pack the TF related events into one transaction 4. TFOrganizerController call deferTransitionReady and send the transaction to the TFOrganizer in app process 5. TFOrganizer update TFs based on the transaction and apply a WCT 6. When TFOrganizerController get that WCT, call continueTransitionReady Bug: 240519866 Test: pass existing Change-Id: I110953f7b73f565a2b1299ef5c3827eaa39e84b8 --- .../window/ITaskFragmentOrganizer.aidl | 49 +-- .../android/window/TaskFragmentOrganizer.java | 116 +++--- .../window/TaskFragmentTransaction.aidl | 20 ++ .../window/TaskFragmentTransaction.java | 335 ++++++++++++++++++ .../wm/TaskFragmentOrganizerController.java | 277 ++++++++++----- .../TaskFragmentOrganizerControllerTest.java | 6 +- 6 files changed, 611 insertions(+), 192 deletions(-) create mode 100644 core/java/android/window/TaskFragmentTransaction.aidl create mode 100644 core/java/android/window/TaskFragmentTransaction.java diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl index 3335c9c073e0e..79ddadb74f824 100644 --- a/core/java/android/window/ITaskFragmentOrganizer.aidl +++ b/core/java/android/window/ITaskFragmentOrganizer.aidl @@ -16,54 +16,9 @@ package android.window; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.IBinder; -import android.window.TaskFragmentInfo; +import android.window.TaskFragmentTransaction; /** @hide */ oneway interface ITaskFragmentOrganizer { - void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo); - void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo); - void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo); - - /** - * Called when the parent leaf Task of organized TaskFragments is changed. - * When the leaf Task is changed, the organizer may want to update the TaskFragments in one - * transaction. - * - * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new - * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override - * bounds. - */ - void onTaskFragmentParentInfoChanged(in IBinder fragmentToken, in Configuration parentConfig); - - /** - * Called when the {@link WindowContainerTransaction} created with - * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. - * - * @param errorCallbackToken Token set through {@link - * WindowContainerTransaction#setErrorCallbackToken(IBinder)} - * @param errorBundle Bundle containing the exception, operation type and TaskFragmentInfo - * if any. Should be created with - * {@link TaskFragmentOrganizer#putErrorInfoInBundle}. - */ - void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle errorBundle); - - /** - * Called when an Activity is reparented to the Task with organized TaskFragment. For example, - * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its - * orginial Task. In this case, we need to notify the organizer so that it can check if the - * Activity matches any split rule. - * - * @param taskId The Task that the activity is reparented to. - * @param activityIntent The intent that the activity is original launched with. - * @param activityToken If the activity belongs to the same process as the organizer, this - * will be the actual activity token; if the activity belongs to a - * different process, the server will generate a temporary token that - * the organizer can use to reparent the activity through - * {@link WindowContainerTransaction} if needed. - */ - void onActivityReparentToTask(int taskId, in Intent activityIntent, in IBinder activityToken); + void onTransactionReady(in TaskFragmentTransaction transaction); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index e4a6ad87053cd..c9a568815fb17 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -16,6 +16,13 @@ package android.window; +import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +34,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.view.RemoteAnimationDefinition; +import java.util.List; import java.util.concurrent.Executor; /** @@ -204,6 +212,67 @@ public class TaskFragmentOrganizer extends WindowOrganizer { public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {} + /** + * Called when the transaction is ready so that the organizer can update the TaskFragments based + * on the changes in transaction. + * @hide + */ + public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { + final List changes = transaction.getChanges(); + for (TaskFragmentTransaction.Change change : changes) { + // TODO(b/240519866): apply all changes in one WCT. + switch (change.getType()) { + case TYPE_TASK_FRAGMENT_APPEARED: + onTaskFragmentAppeared(change.getTaskFragmentInfo()); + if (change.getTaskConfiguration() != null) { + // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the + // same Task + onTaskFragmentParentInfoChanged( + change.getTaskFragmentToken(), + change.getTaskConfiguration()); + } + break; + case TYPE_TASK_FRAGMENT_INFO_CHANGED: + if (change.getTaskConfiguration() != null) { + // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the + // same Task + onTaskFragmentParentInfoChanged( + change.getTaskFragmentToken(), + change.getTaskConfiguration()); + } + onTaskFragmentInfoChanged(change.getTaskFragmentInfo()); + break; + case TYPE_TASK_FRAGMENT_VANISHED: + onTaskFragmentVanished(change.getTaskFragmentInfo()); + break; + case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: + onTaskFragmentParentInfoChanged( + change.getTaskFragmentToken(), + change.getTaskConfiguration()); + break; + case TYPE_TASK_FRAGMENT_ERROR: + final Bundle errorBundle = change.getErrorBundle(); + onTaskFragmentError( + change.getErrorCallbackToken(), + errorBundle.getParcelable( + KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class), + errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE), + errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION, + java.lang.Throwable.class)); + break; + case TYPE_ACTIVITY_REPARENT_TO_TASK: + onActivityReparentToTask( + change.getTaskId(), + change.getActivityIntent(), + change.getActivityToken()); + break; + default: + throw new IllegalArgumentException( + "Unknown TaskFragmentEvent=" + change.getType()); + } + } + } + @Override public void applyTransaction(@NonNull WindowContainerTransaction t) { t.setTaskFragmentOrganizer(mInterface); @@ -221,51 +290,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { - mExecutor.execute( - () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo)); - } - - @Override - public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { - mExecutor.execute( - () -> TaskFragmentOrganizer.this.onTaskFragmentInfoChanged(taskFragmentInfo)); - } - - @Override - public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { - mExecutor.execute( - () -> TaskFragmentOrganizer.this.onTaskFragmentVanished(taskFragmentInfo)); - } - - @Override - public void onTaskFragmentParentInfoChanged( - @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { - mExecutor.execute( - () -> TaskFragmentOrganizer.this.onTaskFragmentParentInfoChanged( - fragmentToken, parentConfig)); - } - - @Override - public void onTaskFragmentError( - @NonNull IBinder errorCallbackToken, @NonNull Bundle errorBundle) { - mExecutor.execute(() -> { - final TaskFragmentInfo info = errorBundle.getParcelable( - KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class); - TaskFragmentOrganizer.this.onTaskFragmentError( - errorCallbackToken, info, - errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE), - (Throwable) errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION, - java.lang.Throwable.class)); - }); - } - - @Override - public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, - @NonNull IBinder activityToken) { - mExecutor.execute( - () -> TaskFragmentOrganizer.this.onActivityReparentToTask( - taskId, activityIntent, activityToken)); + public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { + mExecutor.execute(() -> TaskFragmentOrganizer.this.onTransactionReady(transaction)); } }; diff --git a/core/java/android/window/TaskFragmentTransaction.aidl b/core/java/android/window/TaskFragmentTransaction.aidl new file mode 100644 index 0000000000000..aaa2db4b15b8a --- /dev/null +++ b/core/java/android/window/TaskFragmentTransaction.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +parcelable TaskFragmentTransaction; +parcelable TaskFragmentTransaction.Change; diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java new file mode 100644 index 0000000000000..755864f60f878 --- /dev/null +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -0,0 +1,335 @@ +/* + * 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 static java.util.Objects.requireNonNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +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.ArrayList; +import java.util.List; + +/** + * Used to communicate information about what are changing on embedded TaskFragments belonging to + * the same TaskFragmentOrganizer. A transaction can contain multiple changes. + * @see TaskFragmentTransaction.Change + * @hide + */ +public final class TaskFragmentTransaction implements Parcelable { + + private final ArrayList mChanges = new ArrayList<>(); + + public TaskFragmentTransaction() {} + + private TaskFragmentTransaction(Parcel in) { + in.readTypedList(mChanges, Change.CREATOR); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mChanges); + } + + /** Adds a {@link Change} to this transaction. */ + public void addChange(@Nullable Change change) { + if (change != null) { + mChanges.add(change); + } + } + + /** Whether this transaction contains any {@link Change}. */ + public boolean isEmpty() { + return mChanges.isEmpty(); + } + + @NonNull + public List getChanges() { + return mChanges; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("TaskFragmentTransaction{changes=["); + for (int i = 0; i < mChanges.size(); ++i) { + if (i > 0) { + sb.append(','); + } + sb.append(mChanges.get(i)); + } + sb.append("]}"); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public TaskFragmentTransaction createFromParcel(Parcel in) { + return new TaskFragmentTransaction(in); + } + + @Override + public TaskFragmentTransaction[] newArray(int size) { + return new TaskFragmentTransaction[size]; + } + }; + + /** Change type: the TaskFragment is attached to the hierarchy. */ + public static final int TYPE_TASK_FRAGMENT_APPEARED = 1; + + /** Change type: the status of the TaskFragment is changed. */ + public static final int TYPE_TASK_FRAGMENT_INFO_CHANGED = 2; + + /** Change type: the TaskFragment is removed form the hierarchy. */ + public static final int TYPE_TASK_FRAGMENT_VANISHED = 3; + + /** Change type: the status of the parent leaf Task is changed. */ + public static final int TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED = 4; + + /** Change type: the TaskFragment related operation failed on the server side. */ + public static final int TYPE_TASK_FRAGMENT_ERROR = 5; + + /** + * Change type: an Activity is reparented to the Task. For example, when an Activity enters and + * then exits Picture-in-picture, it will be reparented back to its original Task. In this case, + * we need to notify the organizer so that it can check if the Activity matches any split rule. + */ + public static final int TYPE_ACTIVITY_REPARENT_TO_TASK = 6; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_TASK_FRAGMENT_APPEARED, + TYPE_TASK_FRAGMENT_INFO_CHANGED, + TYPE_TASK_FRAGMENT_VANISHED, + TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, + TYPE_TASK_FRAGMENT_ERROR, + TYPE_ACTIVITY_REPARENT_TO_TASK + }) + @Retention(RetentionPolicy.SOURCE) + @interface ChangeType {} + + /** Represents the change an embedded TaskFragment undergoes. */ + public static final class Change implements Parcelable { + + /** @see ChangeType */ + @ChangeType + private final int mType; + + /** @see #setTaskFragmentToken(IBinder) */ + @Nullable + private IBinder mTaskFragmentToken; + + /** @see #setTaskFragmentInfo(TaskFragmentInfo) */ + @Nullable + private TaskFragmentInfo mTaskFragmentInfo; + + /** @see #setTaskId(int) */ + private int mTaskId; + + /** @see #setTaskConfiguration(Configuration) */ + @Nullable + private Configuration mTaskConfiguration; + + /** @see #setErrorCallbackToken(IBinder) */ + @Nullable + private IBinder mErrorCallbackToken; + + /** @see #setErrorBundle(Bundle) */ + @Nullable + private Bundle mErrorBundle; + + /** @see #setActivityIntent(Intent) */ + @Nullable + private Intent mActivityIntent; + + /** @see #setActivityToken(IBinder) */ + @Nullable + private IBinder mActivityToken; + + public Change(@ChangeType int type) { + mType = type; + } + + private Change(Parcel in) { + mType = in.readInt(); + mTaskFragmentToken = in.readStrongBinder(); + mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR); + mTaskId = in.readInt(); + mTaskConfiguration = in.readTypedObject(Configuration.CREATOR); + mErrorCallbackToken = in.readStrongBinder(); + mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader()); + mActivityIntent = in.readTypedObject(Intent.CREATOR); + mActivityToken = in.readStrongBinder(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeStrongBinder(mTaskFragmentToken); + dest.writeTypedObject(mTaskFragmentInfo, flags); + dest.writeInt(mTaskId); + dest.writeTypedObject(mTaskConfiguration, flags); + dest.writeStrongBinder(mErrorCallbackToken); + dest.writeBundle(mErrorBundle); + dest.writeTypedObject(mActivityIntent, flags); + dest.writeStrongBinder(mActivityToken); + } + + /** The change is related to the TaskFragment created with this unique token. */ + public Change setTaskFragmentToken(@NonNull IBinder taskFragmentToken) { + mTaskFragmentToken = requireNonNull(taskFragmentToken); + return this; + } + + /** Info of the embedded TaskFragment. */ + public Change setTaskFragmentInfo(@NonNull TaskFragmentInfo info) { + mTaskFragmentInfo = requireNonNull(info); + return this; + } + + /** Task id the parent Task. */ + public Change setTaskId(int taskId) { + mTaskId = taskId; + return this; + } + + /** Configuration of the parent Task. */ + public Change setTaskConfiguration(@NonNull Configuration configuration) { + mTaskConfiguration = requireNonNull(configuration); + return this; + } + + /** + * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction} + * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to + * report back. + */ + public Change setErrorCallbackToken(@Nullable IBinder errorCallbackToken) { + mErrorCallbackToken = errorCallbackToken; + return this; + } + + /** + * Bundle with necessary info about the failure operation of + * {@link #TYPE_TASK_FRAGMENT_ERROR}. + */ + public Change setErrorBundle(@NonNull Bundle errorBundle) { + mErrorBundle = requireNonNull(errorBundle); + return this; + } + + /** + * Intent of the activity that is reparented to the Task for + * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}. + */ + public Change setActivityIntent(@NonNull Intent intent) { + mActivityIntent = requireNonNull(intent); + return this; + } + + /** + * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}. + * If the activity belongs to the same process as the organizer, this will be the actual + * activity token; if the activity belongs to a different process, the server will generate + * a temporary token that the organizer can use to reparent the activity through + * {@link WindowContainerTransaction} if needed. + */ + public Change setActivityToken(@NonNull IBinder activityToken) { + mActivityToken = requireNonNull(activityToken); + return this; + } + + @ChangeType + public int getType() { + return mType; + } + + @Nullable + public IBinder getTaskFragmentToken() { + return mTaskFragmentToken; + } + + @Nullable + public TaskFragmentInfo getTaskFragmentInfo() { + return mTaskFragmentInfo; + } + + public int getTaskId() { + return mTaskId; + } + + @Nullable + public Configuration getTaskConfiguration() { + return mTaskConfiguration; + } + + @Nullable + public IBinder getErrorCallbackToken() { + return mErrorCallbackToken; + } + + @Nullable + public Bundle getErrorBundle() { + return mErrorBundle; + } + + @Nullable + public Intent getActivityIntent() { + return mActivityIntent; + } + + @Nullable + public IBinder getActivityToken() { + return mActivityToken; + } + + @Override + public String toString() { + return "Change{ type=" + mType + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Change createFromParcel(Parcel in) { + return new Change(in); + } + + @Override + public Change[] newArray(int size) { + return new Change[size]; + } + }; + } +} diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 602579fb4f9fe..a4d6f70b011e4 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -17,6 +17,12 @@ package com.android.server.wm; import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle; +import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; +import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; @@ -38,6 +44,7 @@ import android.view.RemoteAnimationDefinition; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentTransaction; import com.android.internal.protolog.common.ProtoLog; @@ -68,6 +75,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final ArrayList mPendingTaskFragmentEvents = new ArrayList<>(); + /** Map from {@link ITaskFragmentOrganizer} to {@link TaskFragmentTransaction}. */ + private final ArrayMap mTmpOrganizerToTransactionMap = + new ArrayMap<>(); + private final ArrayList mTmpOrganizerList = new ArrayList<>(); + TaskFragmentOrganizerController(ActivityTaskManagerService atm) { mAtmService = atm; mGlobalLock = atm.mGlobalLock; @@ -145,107 +157,138 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/); } - void onTaskFragmentAppeared(TaskFragment tf) { + @NonNull + TaskFragmentTransaction.Change prepareTaskFragmentAppeared(@NonNull TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); final TaskFragmentInfo info = tf.getTaskFragmentInfo(); - try { - mOrganizer.onTaskFragmentAppeared(info); - mLastSentTaskFragmentInfos.put(tf, info); - tf.mTaskFragmentAppearedSent = true; - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending onTaskFragmentAppeared callback", e); + tf.mTaskFragmentAppearedSent = true; + mLastSentTaskFragmentInfos.put(tf, info); + final TaskFragmentTransaction.Change change = + new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_APPEARED) + .setTaskFragmentToken(tf.getFragmentToken()) + .setTaskFragmentInfo(info); + if (shouldSendTaskFragmentParentInfoChanged(tf)) { + // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task + final Task task = tf.getTask(); + mLastSentTaskFragmentParentConfigs + .put(tf, new Configuration(task.getConfiguration())); + change.setTaskId(task.mTaskId) + .setTaskConfiguration(task.getConfiguration()); } - onTaskFragmentParentInfoChanged(tf); + return change; } - void onTaskFragmentVanished(TaskFragment tf) { + @NonNull + TaskFragmentTransaction.Change prepareTaskFragmentVanished(@NonNull TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName()); - try { - mOrganizer.onTaskFragmentVanished(tf.getTaskFragmentInfo()); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending onTaskFragmentVanished callback", e); - } tf.mTaskFragmentAppearedSent = false; mLastSentTaskFragmentInfos.remove(tf); mLastSentTaskFragmentParentConfigs.remove(tf); + return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED) + .setTaskFragmentToken(tf.getFragmentToken()) + .setTaskFragmentInfo(tf.getTaskFragmentInfo()); } - void onTaskFragmentInfoChanged(TaskFragment tf) { - // Parent config may have changed. The controller will check if there is any important - // config change for the organizer. - onTaskFragmentParentInfoChanged(tf); - + @Nullable + TaskFragmentTransaction.Change prepareTaskFragmentInfoChanged( + @NonNull TaskFragment tf) { // Check if the info is different from the last reported info. final TaskFragmentInfo info = tf.getTaskFragmentInfo(); final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf); if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer( info.getConfiguration(), lastInfo.getConfiguration())) { - return; + // Parent config may have changed. The controller will check if there is any + // important config change for the organizer. + return prepareTaskFragmentParentInfoChanged(tf); } + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", tf.getName()); - try { - mOrganizer.onTaskFragmentInfoChanged(info); - mLastSentTaskFragmentInfos.put(tf, info); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e); + mLastSentTaskFragmentInfos.put(tf, info); + final TaskFragmentTransaction.Change change = + new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_INFO_CHANGED) + .setTaskFragmentToken(tf.getFragmentToken()) + .setTaskFragmentInfo(info); + if (shouldSendTaskFragmentParentInfoChanged(tf)) { + // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task + // at once. + // Parent config may have changed. The controller will check if there is any + // important config change for the organizer. + final Task task = tf.getTask(); + mLastSentTaskFragmentParentConfigs + .put(tf, new Configuration(task.getConfiguration())); + change.setTaskId(task.mTaskId) + .setTaskConfiguration(task.getConfiguration()); } + return change; } - void onTaskFragmentParentInfoChanged(TaskFragment tf) { - // Check if the parent info is different from the last reported parent info. - if (tf.getParent() == null || tf.getParent().asTask() == null) { - mLastSentTaskFragmentParentConfigs.remove(tf); - return; + @Nullable + TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged( + @NonNull TaskFragment tf) { + if (!shouldSendTaskFragmentParentInfoChanged(tf)) { + return null; } - final Task parent = tf.getParent().asTask(); + + final Task parent = tf.getTask(); final Configuration parentConfig = parent.getConfiguration(); - final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf); - if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig) - && parentConfig.windowConfiguration.getWindowingMode() - == lastParentConfig.windowConfiguration.getWindowingMode()) { - return; - } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment parent info changed name=%s parentTaskId=%d", tf.getName(), parent.mTaskId); - try { - mOrganizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig); - mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig)); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e); - } + mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig)); + return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) + .setTaskFragmentToken(tf.getFragmentToken()) + .setTaskId(parent.mTaskId) + .setTaskConfiguration(parent.getConfiguration()); } - void onTaskFragmentError(IBinder errorCallbackToken, @Nullable TaskFragment taskFragment, - int opType, Throwable exception) { + /** Whether the system should report TaskFragment parent info changed to the organizer. */ + private boolean shouldSendTaskFragmentParentInfoChanged(@NonNull TaskFragment tf) { + final Task parent = tf.getTask(); + if (parent == null) { + // The TaskFragment is not attached. + mLastSentTaskFragmentParentConfigs.remove(tf); + return false; + } + // Check if the parent info is different from the last reported parent info. + final Configuration parentConfig = parent.getConfiguration(); + final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf); + return !configurationsAreEqualForOrganizer(parentConfig, lastParentConfig) + || parentConfig.windowConfiguration.getWindowingMode() + != lastParentConfig.windowConfiguration.getWindowingMode(); + } + + @NonNull + TaskFragmentTransaction.Change prepareTaskFragmentError( + @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment, + int opType, @NonNull Throwable exception) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Sending TaskFragment error exception=%s", exception.toString()); final TaskFragmentInfo info = taskFragment != null ? taskFragment.getTaskFragmentInfo() : null; final Bundle errorBundle = putErrorInfoInBundle(exception, info, opType); - try { - mOrganizer.onTaskFragmentError(errorCallbackToken, errorBundle); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending onTaskFragmentError callback", e); - } + return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_ERROR) + .setErrorCallbackToken(errorCallbackToken) + .setErrorBundle(errorBundle); } - void onActivityReparentToTask(ActivityRecord activity) { + @Nullable + TaskFragmentTransaction.Change prepareActivityReparentToTask( + @NonNull ActivityRecord activity) { if (activity.finishing) { Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing"); - return; + return null; } final Task task = activity.getTask(); if (task == null || task.effectiveUid != mOrganizerUid) { Slog.d(TAG, "Reparent activity=" + activity.token + " is not in a task belong to the organizer app."); - return; + return null; } if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) { Slog.d(TAG, "Reparent activity=" + activity.token + " is not allowed to be embedded."); - return; + return null; } final IBinder activityToken; @@ -268,11 +311,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d", activity.token, task.mTaskId); - try { - mOrganizer.onActivityReparentToTask(task.mTaskId, activity.intent, activityToken); - } catch (RemoteException e) { - Slog.d(TAG, "Exception sending onActivityReparentToTask callback", e); - } + return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK) + .setTaskId(task.mTaskId) + .setActivityIntent(activity.intent) + .setActivityToken(activityToken); } } @@ -375,7 +417,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr */ @Nullable public RemoteAnimationDefinition getRemoteAnimationDefinition( - ITaskFragmentOrganizer organizer, int taskId) { + @NonNull ITaskFragmentOrganizer organizer, int taskId) { synchronized (mGlobalLock) { final TaskFragmentOrganizerState organizerState = mTaskFragmentOrganizerState.get(organizer.asBinder()); @@ -385,12 +427,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - int getTaskFragmentOrganizerUid(ITaskFragmentOrganizer organizer) { + int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) { final TaskFragmentOrganizerState state = validateAndGetState(organizer); return state.mOrganizerUid; } - void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) { + void onTaskFragmentAppeared(@NonNull ITaskFragmentOrganizer organizer, + @NonNull TaskFragment taskFragment) { final TaskFragmentOrganizerState state = validateAndGetState(organizer); if (!state.addTaskFragment(taskFragment)) { return; @@ -406,19 +449,20 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) { + void onTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, + @NonNull TaskFragment taskFragment) { handleTaskFragmentInfoChanged(organizer, taskFragment, PendingTaskFragmentEvent.EVENT_INFO_CHANGED); } - void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, - TaskFragment taskFragment) { + void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, + @NonNull TaskFragment taskFragment) { handleTaskFragmentInfoChanged(organizer, taskFragment, PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED); } - private void handleTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, - TaskFragment taskFragment, int eventType) { + private void handleTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, + @NonNull TaskFragment taskFragment, int eventType) { validateAndGetState(organizer); if (!taskFragment.mTaskFragmentAppearedSent) { // Skip if TaskFragment still not appeared. @@ -444,7 +488,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mPendingTaskFragmentEvents.add(pendingEvent); } - void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) { + void onTaskFragmentVanished(@NonNull ITaskFragmentOrganizer organizer, + @NonNull TaskFragment taskFragment) { final TaskFragmentOrganizerState state = validateAndGetState(organizer); for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); @@ -467,8 +512,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr state.removeTaskFragment(taskFragment); } - void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken, - TaskFragment taskFragment, int opType, Throwable exception) { + void onTaskFragmentError(@NonNull ITaskFragmentOrganizer organizer, + @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment, + int opType, @NonNull Throwable exception) { validateAndGetState(organizer); Slog.w(TAG, "onTaskFragmentError ", exception); final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder( @@ -483,7 +529,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal(); } - void onActivityReparentToTask(ActivityRecord activity) { + void onActivityReparentToTask(@NonNull ActivityRecord activity) { final ITaskFragmentOrganizer organizer; if (activity.mLastTaskFragmentOrganizerBeforePip != null) { // If the activity is previously embedded in an organized TaskFragment. @@ -515,11 +561,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr mPendingTaskFragmentEvents.add(pendingEvent); } - boolean isOrganizerRegistered(ITaskFragmentOrganizer organizer) { + boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) { return mTaskFragmentOrganizerState.containsKey(organizer.asBinder()); } - private void removeOrganizer(ITaskFragmentOrganizer organizer) { + private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) { final TaskFragmentOrganizerState state = validateAndGetState(organizer); // remove all of the children of the organized TaskFragment state.dispose(); @@ -539,7 +585,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr * we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the * {@link TaskFragment} after the organizer process died. */ - private TaskFragmentOrganizerState validateAndGetState(ITaskFragmentOrganizer organizer) { + @NonNull + private TaskFragmentOrganizerState validateAndGetState( + @NonNull ITaskFragmentOrganizer organizer) { final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(organizer.asBinder()); if (state == null) { @@ -672,7 +720,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Nullable - private PendingTaskFragmentEvent getLastPendingLifecycleEvent(TaskFragment tf) { + private PendingTaskFragmentEvent getLastPendingLifecycleEvent(@NonNull TaskFragment tf) { for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) { @@ -683,7 +731,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Nullable - private PendingTaskFragmentEvent getPendingTaskFragmentEvent(TaskFragment taskFragment, + private PendingTaskFragmentEvent getPendingTaskFragmentEvent(@NonNull TaskFragment taskFragment, int type) { for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) { PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i); @@ -731,16 +779,36 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr candidateEvents.add(event); } final int numEvents = candidateEvents.size(); + if (numEvents == 0) { + return; + } + + mTmpOrganizerToTransactionMap.clear(); + mTmpOrganizerList.clear(); for (int i = 0; i < numEvents; i++) { - dispatchEvent(candidateEvents.get(i)); + final PendingTaskFragmentEvent event = candidateEvents.get(i); + if (!mTmpOrganizerToTransactionMap.containsKey(event.mTaskFragmentOrg.asBinder())) { + mTmpOrganizerToTransactionMap.put(event.mTaskFragmentOrg.asBinder(), + new TaskFragmentTransaction()); + mTmpOrganizerList.add(event.mTaskFragmentOrg); + } + mTmpOrganizerToTransactionMap.get(event.mTaskFragmentOrg.asBinder()) + .addChange(prepareChange(event)); } - if (numEvents > 0) { - mPendingTaskFragmentEvents.removeAll(candidateEvents); + final int numOrganizers = mTmpOrganizerList.size(); + for (int i = 0; i < numOrganizers; i++) { + final ITaskFragmentOrganizer organizer = mTmpOrganizerList.get(i); + dispatchTransactionInfo(organizer, + mTmpOrganizerToTransactionMap.get(organizer.asBinder())); } + mPendingTaskFragmentEvents.removeAll(candidateEvents); + mTmpOrganizerToTransactionMap.clear(); + mTmpOrganizerList.clear(); } - private static boolean isTaskVisible(Task task, ArrayList knownVisibleTasks, - ArrayList knownInvisibleTasks) { + private static boolean isTaskVisible(@NonNull Task task, + @NonNull ArrayList knownVisibleTasks, + @NonNull ArrayList knownInvisibleTasks) { if (knownVisibleTasks.contains(task)) { return true; } @@ -756,44 +824,57 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) { - PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment, + void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) { + final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment, PendingTaskFragmentEvent.EVENT_INFO_CHANGED); if (event == null) { return; } - dispatchEvent(event); + final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); + transaction.addChange(prepareChange(event)); + dispatchTransactionInfo(event.mTaskFragmentOrg, transaction); mPendingTaskFragmentEvents.remove(event); } - private void dispatchEvent(PendingTaskFragmentEvent event) { + private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer, + @NonNull TaskFragmentTransaction transaction) { + if (transaction.isEmpty()) { + return; + } + try { + organizer.onTransactionReady(transaction); + } catch (RemoteException e) { + Slog.d(TAG, "Exception sending TaskFragmentTransaction", e); + } + } + + @Nullable + private TaskFragmentTransaction.Change prepareChange( + @NonNull PendingTaskFragmentEvent event) { final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg; final TaskFragment taskFragment = event.mTaskFragment; final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder()); if (state == null) { - return; + return null; } switch (event.mEventType) { case PendingTaskFragmentEvent.EVENT_APPEARED: - state.onTaskFragmentAppeared(taskFragment); - break; + return state.prepareTaskFragmentAppeared(taskFragment); case PendingTaskFragmentEvent.EVENT_VANISHED: - state.onTaskFragmentVanished(taskFragment); - break; + return state.prepareTaskFragmentVanished(taskFragment); case PendingTaskFragmentEvent.EVENT_INFO_CHANGED: - state.onTaskFragmentInfoChanged(taskFragment); - break; + return state.prepareTaskFragmentInfoChanged(taskFragment); case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED: - state.onTaskFragmentParentInfoChanged(taskFragment); - break; + return state.prepareTaskFragmentParentInfoChanged(taskFragment); case PendingTaskFragmentEvent.EVENT_ERROR: - state.onTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType, - event.mException); - break; + return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment, + event.mOpType, event.mException); case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK: - state.onActivityReparentToTask(event.mActivity); + return state.prepareActivityReparentToTask(event.mActivity); + default: + throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType); } } 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 eba27553937e9..f38731b2f7398 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -199,9 +199,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final Task parent = mock(Task.class); final Configuration parentConfig = new Configuration(); parentConfig.smallestScreenWidthDp = 10; - doReturn(parent).when(mTaskFragment).getParent(); + doReturn(parent).when(mTaskFragment).getTask(); doReturn(parentConfig).when(parent).getConfiguration(); - doReturn(parent).when(parent).asTask(); + // Task needs to be visible + parent.lastActiveTime = 100; + doReturn(true).when(parent).shouldBeVisible(any()); mTaskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentParentInfoChanged(