blob: 4b13a0c1f75dd64f47aeb5927cc4167b76671145 [file] [log] [blame]
/*
* Copyright (C) 2019 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 com.android.server.wm;
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 static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
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.IWindowContainer;
import android.view.SurfaceControl;
import android.view.WindowContainerTransaction;
import com.android.internal.util.ArrayUtils;
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.List;
import java.util.Map;
import java.util.WeakHashMap;
/**
* Stores the TaskOrganizers associated with a given windowing mode and
* their associated state.
*/
class TaskOrganizerController extends ITaskOrganizerController.Stub
implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "TaskOrganizerController";
/** 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;
ITaskOrganizer mTaskOrganizer;
DeathRecipient(ITaskOrganizer organizer, int windowingMode) {
mTaskOrganizer = organizer;
mWindowingMode = windowingMode;
}
@Override
public void binderDied() {
synchronized (mGlobalLock) {
final TaskOrganizerState state =
mTaskOrganizerStates.get(mTaskOrganizer.asBinder());
state.releaseTasks();
mTaskOrganizerStates.remove(mTaskOrganizer.asBinder());
if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
mTaskOrganizersForWindowingMode.remove(mWindowingMode);
}
}
}
};
class TaskOrganizerState {
ITaskOrganizer mOrganizer;
DeathRecipient mDeathRecipient;
int mWindowingMode;
ArrayList<Task> mOrganizedTasks = new ArrayList<>();
// Save the TaskOrganizer which we replaced registration for
// so it can be re-registered if we unregister.
TaskOrganizerState mReplacementFor;
boolean mDisposed = false;
TaskOrganizerState(ITaskOrganizer organizer, int windowingMode,
TaskOrganizerState replacing) {
mOrganizer = organizer;
mDeathRecipient = new DeathRecipient(organizer, windowingMode);
try {
organizer.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
Slog.e(TAG, "TaskOrganizer failed to register death recipient");
}
mWindowingMode = windowingMode;
mReplacementFor = replacing;
}
void addTask(Task t) {
mOrganizedTasks.add(t);
try {
mOrganizer.taskAppeared(t.getTaskInfo());
} catch (Exception e) {
Slog.e(TAG, "Exception sending taskAppeared callback" + e);
}
}
void removeTask(Task t) {
try {
mOrganizer.taskVanished(t.getRemoteToken());
} catch (Exception e) {
Slog.e(TAG, "Exception sending taskVanished callback" + e);
}
mOrganizedTasks.remove(t);
}
void dispose() {
mDisposed = true;
releaseTasks();
handleReplacement();
}
void releaseTasks() {
for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) {
final Task t = mOrganizedTasks.get(i);
t.taskOrganizerDied();
removeTask(t);
}
}
void handleReplacement() {
if (mReplacementFor != null && !mReplacementFor.mDisposed) {
mTaskOrganizersForWindowingMode.put(mWindowingMode, mReplacementFor);
}
}
void unlinkDeath() {
mDisposed = true;
mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0);
}
};
final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
final ActivityTaskManagerService mService;
RunningTaskInfo mTmpTaskInfo;
TaskOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
}
private void enforceStackPermission(String func) {
mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
}
/**
* Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
* If there was already a TaskOrganizer for this windowing mode it will be evicted
* but will continue to organize it's existing tasks.
*/
@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");
}
enforceStackPermission("registerTaskOrganizer()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final TaskOrganizerState state = new TaskOrganizerState(organizer, windowingMode,
mTaskOrganizersForWindowingMode.get(windowingMode));
mTaskOrganizersForWindowingMode.put(windowingMode, state);
mTaskOrganizerStates.put(organizer.asBinder(), state);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
void unregisterTaskOrganizer(ITaskOrganizer organizer) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
state.unlinkDeath();
if (mTaskOrganizersForWindowingMode.get(state.mWindowingMode) == state) {
mTaskOrganizersForWindowingMode.remove(state.mWindowingMode);
}
state.dispose();
}
ITaskOrganizer getTaskOrganizer(int windowingMode) {
final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
if (state == null) {
return null;
}
return state.mOrganizer;
}
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
state.addTask(task);
}
void onTaskVanished(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
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);
}
}
@Override
public List<RunningTaskInfo> getChildTasks(IWindowContainer parent,
@Nullable int[] activityTypes) {
enforceStackPermission("getChildTasks()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
if (parent == null) {
throw new IllegalArgumentException("Can't get children of null parent");
}
final WindowContainer container = WindowContainer.fromBinder(parent.asBinder());
if (container == null) {
Slog.e(TAG, "Can't get children of " + parent + " because it is not valid.");
return null;
}
// For now, only support returning children of persistent root tasks (of which the
// only current implementation is TaskTile).
if (!(container instanceof TaskTile)) {
Slog.w(TAG, "Can only get children of root tasks created via createRootTask");
return null;
}
ArrayList<RunningTaskInfo> out = new ArrayList<>();
// Tiles aren't real parents, so we need to go through stacks on the display to
// ensure correct ordering.
final DisplayContent dc = container.getDisplayContent();
for (int i = dc.getStackCount() - 1; i >= 0; --i) {
final ActivityStack as = dc.getStackAt(i);
if (as.getTile() == container) {
if (activityTypes != null
&& !ArrayUtils.contains(activityTypes, as.getActivityType())) {
continue;
}
final RunningTaskInfo info = new RunningTaskInfo();
as.fillTaskInfo(info);
out.add(info);
}
}
return out;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public List<RunningTaskInfo> getRootTasks(int displayId, @Nullable int[] activityTypes) {
enforceStackPermission("getRootTasks()");
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final DisplayContent dc =
mService.mRootWindowContainer.getDisplayContent(displayId);
if (dc == null) {
throw new IllegalArgumentException("Display " + displayId + " doesn't exist");
}
ArrayList<RunningTaskInfo> out = new ArrayList<>();
for (int i = dc.getStackCount() - 1; i >= 0; --i) {
final ActivityStack task = dc.getStackAt(i);
if (task.getTile() != null) {
// a tile is supposed to look like a parent, so don't include their
// "children" here. They can be accessed via getChildTasks()
continue;
}
if (activityTypes != null
&& !ArrayUtils.contains(activityTypes, task.getActivityType())) {
continue;
}
final RunningTaskInfo info = new RunningTaskInfo();
task.fillTaskInfo(info);
out.add(info);
}
return out;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
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 int sanitizeAndApplyHierarchyOp(WindowContainer container,
WindowContainerTransaction.HierarchyOp hop) {
if (!(container instanceof Task)) {
throw new IllegalArgumentException("Invalid container in hierarchy op");
}
if (hop.isReparent()) {
// special case for tiles since they are "virtual" parents
if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
ActivityStack as = (ActivityStack) container;
TaskTile newParent = hop.getNewParent() == null ? null
: (TaskTile) WindowContainer.fromBinder(hop.getNewParent());
if (as.getTile() != newParent) {
if (as.getTile() != null) {
as.getTile().removeChild(as);
}
if (newParent != null) {
if (!as.affectedBySplitScreenResize()) {
return 0;
}
newParent.addChild(as, POSITION_TOP);
}
}
if (hop.getToTop()) {
as.getDisplay().positionStackAtTop(as, false /* includingParents */);
} else {
as.getDisplay().positionStackAtBottom(as);
}
} else if (container instanceof Task) {
throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
}
} else {
// Ugh, of course ActivityStack has its own special reorder logic...
if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
ActivityStack as = (ActivityStack) container;
if (hop.getToTop()) {
as.getDisplay().positionStackAtTop(as, false /* includingParents */);
} else {
as.getDisplay().positionStackAtBottom(as);
}
} else {
container.getParent().positionChildAt(
hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
container, false /* includingParents */);
}
}
return TRANSACT_EFFECTS_LIFECYCLE;
}
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 int applyContainerTransaction(WindowContainerTransaction t, ITaskOrganizer organizer) {
enforceStackPermission("applyContainerTransaction()");
int syncId = -1;
if (t == null) {
throw new IllegalArgumentException(
"Null transaction passed to applyContainerTransaction");
}
long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
int effects = 0;
/**
* If organizer is non-null we are looking to synchronize this transaction
* by collecting all the results in to a SurfaceFlinger transaction and
* then delivering that to the given organizers transaction ready callback.
* See {@link BLASTSyncEngine} for the details of the operation. But at
* a high level we create a sync operation with a given ID and an associated
* organizer. Then we notify each WindowContainer in this WindowContainer
* transaction that it is participating in a sync operation with that
* ID. Once everything is notified we tell the BLASTSyncEngine
* "setSyncReady" which means that we have added everything
* to the set. At any point after this, all the WindowContainers
* will eventually finish applying their changes and notify the
* BLASTSyncEngine which will deliver the Transaction to the organizer.
*/
if (organizer != null) {
syncId = startSyncWithOrganizer(organizer);
}
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.fromBinder(entry.getKey());
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 (syncId >= 0) {
mBLASTSyncEngine.addToSyncSet(syncId, wc);
}
}
// Hierarchy changes
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
for (int i = 0, n = hops.size(); i < n; ++i) {
final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
}
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();
if (syncId >= 0) {
setSyncReady(syncId);
}
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return syncId;
}
@Override
public void transactionReady(int id, SurfaceControl.Transaction sc) {
final ITaskOrganizer organizer = mTaskOrganizersByPendingSyncId.get(id);
if (organizer == null) {
Slog.e(TAG, "Got transaction complete for unexpected ID");
}
try {
organizer.transactionReady(id, sc);
} catch (RemoteException e) {
}
mTaskOrganizersByPendingSyncId.remove(id);
}
int startSyncWithOrganizer(ITaskOrganizer organizer) {
int id = mBLASTSyncEngine.startSyncSet(this);
mTaskOrganizersByPendingSyncId.put(id, organizer);
return id;
}
void setSyncReady(int id) {
mBLASTSyncEngine.setReady(id);
}
}