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(