diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 7b96511b11564..b752a9596865f 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -331,11 +331,10 @@ public class TaskInfo { } /** - * Returns {@code true} if parameters that are important for task organizers have changed - * and {@link com.android.server.wm.TaskOrginizerController} needs to notify listeners - * about that. - * @hide - */ + * Returns {@code true} if the parameters that are important for task organizers are equal + * between this {@link TaskInfo} and {@param that}. + * @hide + */ public boolean equalsForTaskOrganizer(@Nullable TaskInfo that) { if (that == null) { return false; diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index 2092a092d73ae..e032153bc0139 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -16,7 +16,10 @@ package android.window; +import static android.app.WindowConfiguration.WindowingMode; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.res.Configuration; import android.os.IBinder; @@ -94,6 +97,28 @@ public final class TaskFragmentInfo implements Parcelable { return mIsVisible; } + @WindowingMode + public int getWindowingMode() { + return mConfiguration.windowConfiguration.getWindowingMode(); + } + + /** + * Returns {@code true} if the parameters that are important for task fragment organizers are + * equal between this {@link TaskFragmentInfo} and {@param that}. + */ + public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) { + if (that == null) { + return false; + } + + return mFragmentToken.equals(that.mFragmentToken) + && mInitialComponentName.equals(that.mInitialComponentName) + && mToken.equals(that.mToken) + && mIsEmpty == that.mIsEmpty + && mIsVisible == that.mIsVisible + && getWindowingMode() == that.getWindowingMode(); + } + private TaskFragmentInfo(Parcel in) { mFragmentToken = in.readStrongBinder(); mInitialComponentName = in.readTypedObject(ComponentName.CREATOR); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 9e042a69deba2..ca6d291c64ec0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -142,6 +142,7 @@ class TaskFragment extends WindowContainer { final ActivityTaskManagerService mAtmService; final ActivityTaskSupervisor mTaskSupervisor; final RootWindowContainer mRootWindowContainer; + private final TaskFragmentOrganizerController mTaskFragmentOrganizerController; // TODO(b/189384393): this is not set in TaskFragment so far. It should be passed from the // parent task when adding to the hierarchy @@ -272,6 +273,8 @@ class TaskFragment extends WindowContainer { mTaskSupervisor = atmService.mTaskSupervisor; mRootWindowContainer = mAtmService.mRootWindowContainer; mCreatedByOrganizer = createdByOrganizer; + mTaskFragmentOrganizerController = + mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController; } void setAdjacentTaskFragment(TaskFragment taskFragment) { @@ -1937,6 +1940,34 @@ class TaskFragment extends WindowContainer { return getTopChild().getActivityType(); } + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + super.onConfigurationChanged(newParentConfig); + + if (mTaskFragmentOrganizer != null) { + // Parent config may have changed. The controller will check if there is any important + // config change for the organizer. + mTaskFragmentOrganizerController + .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, this); + mTaskFragmentOrganizerController + .onTaskFragmentInfoChanged(mTaskFragmentOrganizer, this); + } + } + + // TODO(b/190433129) call when TaskFragment is created from WCT#createTaskFragment + private void sendTaskFragmentAppeared() { + if (mTaskFragmentOrganizer != null) { + mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this); + } + } + + // TODO(b/190433129) call when TaskFragment is removed from WCT#deleteTaskFragment + private void sendTaskFragmentVanished() { + if (mTaskFragmentOrganizer != null) { + mTaskFragmentOrganizerController.onTaskFragmentVanished(mTaskFragmentOrganizer, this); + } + } + /** * Returns a {@link TaskFragmentInfo} with information from this TaskFragment. Should not be * called from {@link Task}. diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index b0a8548ead20d..31175b7e976c1 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -17,7 +17,9 @@ package com.android.server.wm; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; +import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; +import android.content.res.Configuration; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -27,11 +29,13 @@ import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentAppearedInfo; +import android.window.TaskFragmentInfo; import com.android.internal.protolog.common.ProtoLog; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; /** * Stores and manages the client {@link android.window.TaskFragmentOrganizer}. @@ -43,6 +47,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final WindowManagerGlobalLock mGlobalLock; private final Set mOrganizers = new ArraySet<>(); private final Map mDeathRecipients = new ArrayMap<>(); + private final Map mLastSentTaskFragmentInfos = + new WeakHashMap<>(); + private final Map mLastSentTaskFragmentParentConfigs = + new WeakHashMap<>(); private class DeathRecipient implements IBinder.DeathRecipient { final ITaskFragmentOrganizer mOrganizer; @@ -118,43 +126,75 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) { + validateOrganizer(organizer); + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); + final TaskFragmentInfo info = tf.getTaskFragmentInfo(); + final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(), + "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared"); try { - final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(), - "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared"); organizer.onTaskFragmentAppeared( - new TaskFragmentAppearedInfo(tf.getTaskFragmentInfo(), outSurfaceControl)); + new TaskFragmentAppearedInfo(info, outSurfaceControl)); + mLastSentTaskFragmentInfos.put(tf, info); } catch (RemoteException e) { // Oh well... } } void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) { + validateOrganizer(organizer); + + // 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; + } + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s", tf.getName()); try { organizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo()); + mLastSentTaskFragmentInfos.put(tf, info); } catch (RemoteException e) { // Oh well... } } void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment tf) { + validateOrganizer(organizer); + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName()); try { organizer.onTaskFragmentVanished(tf.getTaskFragmentInfo()); } catch (RemoteException e) { // Oh well... } + mLastSentTaskFragmentInfos.remove(tf); + mLastSentTaskFragmentParentConfigs.remove(tf); } void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) { + validateOrganizer(organizer); + + // 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; + } final Task parent = tf.getParent().asTask(); + final Configuration parentConfig = parent.getConfiguration(); + final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf); + if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)) { + return; + } + ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment parent info changed name=%s parentTaskId=%d", tf.getName(), parent.mTaskId); try { - organizer.onTaskFragmentParentInfoChanged( - tf.getFragmentToken(), parent.getConfiguration()); + organizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig); + mLastSentTaskFragmentParentConfigs.put(tf, parentConfig); } catch (RemoteException e) { // Oh well... } @@ -167,4 +207,17 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } // TODO(b/190432728) move child activities of organized TaskFragment to leaf Task } + + /** + * Makes sure that the organizer has been correctly registered to prevent any Sidecar + * implementation from organizing {@link TaskFragment} without registering first. In such case, + * we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the + * {@link TaskFragment} after the organizer process died. + */ + private void validateOrganizer(ITaskFragmentOrganizer organizer) { + if (!mOrganizers.contains(organizer)) { + throw new IllegalArgumentException( + "TaskFragmentOrganizer has not been registered. Organizer=" + organizer); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index fd91394ce6668..31d3a0f2b6b29 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -23,15 +23,13 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL; -import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS; -import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS; +import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; import android.graphics.Rect; import android.os.Binder; @@ -69,13 +67,6 @@ import java.util.function.Consumer; class TaskOrganizerController extends ITaskOrganizerController.Stub { private static final String TAG = "TaskOrganizerController"; - /** - * Masks specifying which configurations are important to report back to an organizer when - * changed. - */ - private static final int REPORT_CONFIGS = CONTROLLABLE_CONFIGS; - private static final int REPORT_WINDOW_CONFIGS = CONTROLLABLE_WINDOW_CONFIGS; - // The set of modes that are currently supports // TODO: Remove once the task organizer can support all modes @VisibleForTesting @@ -790,18 +781,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mTmpTaskInfo.configuration.unset(); task.fillTaskInfo(mTmpTaskInfo); - boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo); - if (!changed) { - int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration); - final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 - ? (int) mTmpTaskInfo.configuration.windowConfiguration.diff( - lastInfo.configuration.windowConfiguration, - true /* compareUndefined */) : 0; - if ((winCfgChanges & REPORT_WINDOW_CONFIGS) == 0) { - cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; - } - changed = (cfgChanges & REPORT_CONFIGS) != 0; - } + boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo) + || !configurationsAreEqualForOrganizer( + mTmpTaskInfo.configuration, lastInfo.configuration); if (!(changed || force)) { // mTmpTaskInfo will be reused next time. return; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 72f73572782c7..3d36cc517e98e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -847,6 +847,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + /** Whether the configuration changes are important to report back to an organizer. */ + static boolean configurationsAreEqualForOrganizer( + Configuration newConfig, @Nullable Configuration oldConfig) { + if (oldConfig == null) { + return false; + } + int cfgChanges = newConfig.diff(oldConfig); + final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + ? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration, + true /* compareUndefined */) : 0; + if ((winCfgChanges & CONTROLLABLE_WINDOW_CONFIGS) == 0) { + cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + } + return (cfgChanges & CONTROLLABLE_CONFIGS) == 0; + } + private void enforceTaskPermission(String func) { mService.enforceTaskPermission(func); }