Add TaskTile concept to Window Manager
This adds the concept of a TaskTile to the WM. Due to
complexities in the current Stack/Task relationship, tiles
can't actually be part of the hierarchy, so the Stack
level has to internally resolve configurations as if they
were.
The TaskTiles themselves *are* ActivityStacks though from
the client/sysui perspective, though.
Bug: 133381284
Test: Added TaskTileTests
Change-Id: I9baad5ec899b4fab323a36c1533a40081727a2f7
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 66c65e2..44a6fc9 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,26 +16,51 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ITaskOrganizerController;
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.ITaskOrganizer;
-import android.view.SurfaceControl;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
+
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
/**
* Stores the TaskOrganizers associated with a given windowing mode and
* their associated state.
*/
-class TaskOrganizerController {
+class TaskOrganizerController extends ITaskOrganizerController.Stub {
private static final String TAG = "TaskOrganizerController";
- private WindowManagerGlobalLock mGlobalLock;
+ /** Flag indicating that an applied transaction may have effected lifecycle */
+ private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+ private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
+ private final WindowManagerGlobalLock mGlobalLock;
private class DeathRecipient implements IBinder.DeathRecipient {
int mWindowingMode;
@@ -87,11 +112,20 @@
final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
+ private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
+ private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
+
final ActivityTaskManagerService mService;
- TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) {
+ RunningTaskInfo mTmpTaskInfo;
+
+ TaskOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
- mGlobalLock = lock;
+ mGlobalLock = atm.mGlobalLock;
+ }
+
+ private void enforceStackPermission(String func) {
+ mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
}
private void clearIfNeeded(int windowingMode) {
@@ -106,26 +140,35 @@
* If there was already a TaskOrganizer for this windowing mode it will be evicted
* and receive taskVanished callbacks in the process.
*/
- void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
- if (windowingMode != WINDOWING_MODE_PINNED &&
- windowingMode != WINDOWING_MODE_MULTI_WINDOW) {
- throw new UnsupportedOperationException(
- "As of now only Pinned and Multiwindow windowing modes are"
- + " supported for registerTaskOrganizer");
-
+ @Override
+ public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+ if (windowingMode != WINDOWING_MODE_PINNED
+ && windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && windowingMode != WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && windowingMode != WINDOWING_MODE_MULTI_WINDOW) {
+ throw new UnsupportedOperationException("As of now only Pinned/Split/Multiwindow"
+ + " windowing modes are supported for registerTaskOrganizer");
}
- clearIfNeeded(windowingMode);
- DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+ enforceStackPermission("registerTaskOrganizer()");
+ final long origId = Binder.clearCallingIdentity();
try {
- organizer.asBinder().linkToDeath(dr, 0);
- } catch (RemoteException e) {
- Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+ synchronized (mGlobalLock) {
+ clearIfNeeded(windowingMode);
+ DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+ try {
+ organizer.asBinder().linkToDeath(dr, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+ }
+
+ final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+ mTaskOrganizersForWindowingMode.put(windowingMode, state);
+
+ mTaskOrganizerStates.put(organizer, state);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
-
- final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
- mTaskOrganizersForWindowingMode.put(windowingMode, state);
-
- mTaskOrganizerStates.put(organizer, state);
}
ITaskOrganizer getTaskOrganizer(int windowingMode) {
@@ -138,7 +181,7 @@
private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
try {
- organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo());
+ organizer.taskAppeared(task.getTaskInfo());
} catch (Exception e) {
Slog.e(TAG, "Exception sending taskAppeared callback" + e);
}
@@ -167,4 +210,254 @@
// we do this AFTER sending taskVanished.
state.removeTask(task);
}
+
+ @Override
+ public RunningTaskInfo createRootTask(int displayId, int windowingMode) {
+ enforceStackPermission("createRootTask()");
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId);
+ if (display == null) {
+ return null;
+ }
+ final int nextId = display.getNextStackId();
+ TaskTile tile = new TaskTile(mService, nextId, windowingMode);
+ display.addTile(tile);
+ RunningTaskInfo out = new RunningTaskInfo();
+ tile.fillTaskInfo(out);
+ mLastSentTaskInfos.put(tile, out);
+ return out;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public boolean deleteRootTask(IWindowContainer token) {
+ enforceStackPermission("deleteRootTask()");
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ TaskTile tile = TaskTile.forToken(token.asBinder());
+ if (tile == null) {
+ return false;
+ }
+ tile.removeImmediately();
+ return true;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ void dispatchPendingTaskInfoChanges() {
+ if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
+ return;
+ }
+ for (int i = 0, n = mPendingTaskInfoChanges.size(); i < n; ++i) {
+ dispatchTaskInfoChanged(mPendingTaskInfoChanges.get(i), false /* force */);
+ }
+ mPendingTaskInfoChanges.clear();
+ }
+
+ void dispatchTaskInfoChanged(Task task, boolean force) {
+ if (!force && mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
+ // Defer task info reporting while layout is deferred. This is because layout defer
+ // blocks tend to do lots of re-ordering which can mess up animations in receivers.
+ mPendingTaskInfoChanges.remove(task);
+ mPendingTaskInfoChanges.add(task);
+ return;
+ }
+ RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
+ if (mTmpTaskInfo == null) {
+ mTmpTaskInfo = new RunningTaskInfo();
+ }
+ task.fillTaskInfo(mTmpTaskInfo);
+ boolean changed = lastInfo == null
+ || mTmpTaskInfo.topActivityType != lastInfo.topActivityType
+ || mTmpTaskInfo.isResizable() != lastInfo.isResizable();
+ if (!(changed || force)) {
+ return;
+ }
+ final RunningTaskInfo newInfo = mTmpTaskInfo;
+ mLastSentTaskInfos.put(task, mTmpTaskInfo);
+ // Since we've stored this, clean up the reference so a new one will be created next time.
+ // Transferring it this way means we only have to construct new RunningTaskInfos when they
+ // change.
+ mTmpTaskInfo = null;
+
+ if (task.mTaskOrganizer != null) {
+ try {
+ task.mTaskOrganizer.onTaskInfoChanged(newInfo);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public IWindowContainer getImeTarget(int displayId) {
+ enforceStackPermission("getImeTarget()");
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ DisplayContent dc = mService.mWindowManager.mRoot
+ .getDisplayContent(displayId);
+ if (dc == null || dc.mInputMethodTarget == null) {
+ return null;
+ }
+ // Avoid WindowState#getRootTask() so we don't attribute system windows to a task.
+ final Task task = dc.mInputMethodTarget.getTask();
+ if (task == null) {
+ return null;
+ }
+ ActivityStack rootTask = (ActivityStack) task.getRootTask();
+ final TaskTile tile = rootTask.getTile();
+ if (tile != null) {
+ rootTask = tile;
+ }
+ return rootTask.mRemoteToken;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public void setLaunchRoot(int displayId, @Nullable IWindowContainer tile) {
+ enforceStackPermission("setLaunchRoot()");
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId);
+ if (display == null) {
+ return;
+ }
+ TaskTile taskTile = tile == null ? null : TaskTile.forToken(tile.asBinder());
+ if (taskTile == null) {
+ display.mLaunchTile = null;
+ return;
+ }
+ if (taskTile.getDisplay() != display) {
+ throw new RuntimeException("Can't set launch root for display " + displayId
+ + " to task on display " + taskTile.getDisplay().getDisplayId());
+ }
+ display.mLaunchTile = taskTile;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ private int sanitizeAndApplyChange(WindowContainer container,
+ WindowContainerTransaction.Change change) {
+ if (!(container instanceof Task)) {
+ throw new RuntimeException("Invalid token in task transaction");
+ }
+ // The "client"-facing API should prevent bad changes; however, just in case, sanitize
+ // masks here.
+ int configMask = change.getConfigSetMask();
+ int windowMask = change.getWindowSetMask();
+ configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+ windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+ int effects = 0;
+ if (configMask != 0) {
+ Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+ c.setTo(change.getConfiguration(), configMask, windowMask);
+ container.onRequestedOverrideConfigurationChanged(c);
+ // TODO(b/145675353): remove the following once we could apply new bounds to the
+ // pinned stack together with its children.
+ resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
+ if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+ if (container.setFocusable(change.getFocusable())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ return effects;
+ }
+
+ private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
+ int windowMask, Configuration config) {
+ if ((container instanceof ActivityStack)
+ && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0)
+ && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) {
+ final ActivityStack stack = (ActivityStack) container;
+ if (stack.inPinnedWindowingMode()) {
+ stack.resize(config.windowConfiguration.getBounds(),
+ null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+ PRESERVE_WINDOWS, true /* deferResume */);
+ }
+ }
+ }
+
+ private int applyWindowContainerChange(WindowContainer wc,
+ WindowContainerTransaction.Change c) {
+ int effects = sanitizeAndApplyChange(wc, c);
+
+ Rect enterPipBounds = c.getEnterPipBounds();
+ if (enterPipBounds != null) {
+ Task tr = (Task) wc;
+ mService.mStackSupervisor.updatePictureInPictureMode(tr,
+ enterPipBounds, true);
+ }
+ return effects;
+ }
+
+ @Override
+ public void applyContainerTransaction(WindowContainerTransaction t) {
+ enforceStackPermission("applyContainerTransaction()");
+ if (t == null) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ int effects = 0;
+ mService.deferWindowLayout();
+ try {
+ ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+ Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+ t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+ entries.next();
+ final WindowContainer wc = WindowContainer.RemoteToken.fromBinder(
+ entry.getKey()).getContainer();
+ int containerEffect = applyWindowContainerChange(wc, entry.getValue());
+ effects |= containerEffect;
+ // Lifecycle changes will trigger ensureConfig for everything.
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+ && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ haveConfigChanges.add(wc);
+ }
+ }
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+ // Already calls ensureActivityConfig
+ mService.mRootWindowContainer.ensureActivitiesVisible(
+ null, 0, PRESERVE_WINDOWS);
+ } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ final PooledConsumer f = PooledLambda.obtainConsumer(
+ ActivityRecord::ensureActivityConfiguration,
+ PooledLambda.__(ActivityRecord.class), 0,
+ false /* preserveWindow */);
+ try {
+ for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+ haveConfigChanges.valueAt(i).forAllActivities(f);
+ }
+ } finally {
+ f.recycle();
+ }
+ }
+ } finally {
+ mService.continueWindowLayout();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}