1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; 20 import static android.Manifest.permission.READ_FRAME_BUFFER; 21 22 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; 23 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; 24 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; 25 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; 26 import static com.android.server.wm.WindowContainer.POSITION_TOP; 27 28 import android.app.WindowConfiguration; 29 import android.content.pm.ActivityInfo; 30 import android.content.res.Configuration; 31 import android.graphics.PixelFormat; 32 import android.graphics.Rect; 33 import android.os.Binder; 34 import android.os.IBinder; 35 import android.os.RemoteException; 36 import android.util.ArraySet; 37 import android.util.Slog; 38 import android.view.Surface; 39 import android.view.SurfaceControl; 40 import android.window.IDisplayAreaOrganizerController; 41 import android.window.ITaskOrganizerController; 42 import android.window.IWindowContainerTransactionCallback; 43 import android.window.IWindowOrganizerController; 44 import android.window.WindowContainerToken; 45 import android.window.WindowContainerTransaction; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.util.function.pooled.PooledConsumer; 49 import com.android.internal.util.function.pooled.PooledLambda; 50 51 import java.util.HashMap; 52 import java.util.Iterator; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.Set; 56 57 /** 58 * Server side implementation for the interface for organizing windows 59 * @see android.window.WindowOrganizer 60 */ 61 class WindowOrganizerController extends IWindowOrganizerController.Stub 62 implements BLASTSyncEngine.TransactionReadyListener { 63 64 private static final String TAG = "WindowOrganizerController"; 65 66 /** Flag indicating that an applied transaction may have effected lifecycle */ 67 private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; 68 private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; 69 70 /** 71 * Masks specifying which configurations task-organizers can control. Incoming transactions 72 * will be filtered to only include these. 73 */ 74 static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION 75 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE; 76 static final int CONTROLLABLE_WINDOW_CONFIGS = WindowConfiguration.WINDOW_CONFIG_BOUNDS 77 | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; 78 79 private final ActivityTaskManagerService mService; 80 private final WindowManagerGlobalLock mGlobalLock; 81 82 private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); 83 private final HashMap<Integer, IWindowContainerTransactionCallback> 84 mTransactionCallbacksByPendingSyncId = new HashMap(); 85 86 final TaskOrganizerController mTaskOrganizerController; 87 final DisplayAreaOrganizerController mDisplayAreaOrganizerController; 88 WindowOrganizerController(ActivityTaskManagerService atm)89 WindowOrganizerController(ActivityTaskManagerService atm) { 90 mService = atm; 91 mGlobalLock = atm.mGlobalLock; 92 mTaskOrganizerController = new TaskOrganizerController(mService); 93 mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); 94 } 95 96 @Override applyTransaction(WindowContainerTransaction t)97 public void applyTransaction(WindowContainerTransaction t) { 98 applySyncTransaction(t, null /*callback*/); 99 } 100 101 @Override applySyncTransaction(WindowContainerTransaction t, IWindowContainerTransactionCallback callback)102 public int applySyncTransaction(WindowContainerTransaction t, 103 IWindowContainerTransactionCallback callback) { 104 enforceStackPermission("applySyncTransaction()"); 105 int syncId = -1; 106 if (t == null) { 107 throw new IllegalArgumentException( 108 "Null transaction passed to applySyncTransaction"); 109 } 110 long ident = Binder.clearCallingIdentity(); 111 try { 112 synchronized (mGlobalLock) { 113 int effects = 0; 114 115 /** 116 * If callback is non-null we are looking to synchronize this transaction by 117 * collecting all the results in to a SurfaceFlinger transaction and then delivering 118 * that to the given transaction ready callback. See {@link BLASTSyncEngine} for the 119 * details of the operation. But at a high level we create a sync operation with a 120 * given ID and an associated callback. Then we notify each WindowContainer in this 121 * WindowContainer transaction that it is participating in a sync operation with 122 * that ID. Once everything is notified we tell the BLASTSyncEngine "setSyncReady" 123 * which means that we have added everything to the set. At any point after this, 124 * all the WindowContainers will eventually finish applying their changes and notify 125 * the BLASTSyncEngine which will deliver the Transaction to the callback. 126 */ 127 if (callback != null) { 128 syncId = startSyncWithOrganizer(callback); 129 } 130 mService.deferWindowLayout(); 131 try { 132 ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); 133 Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = 134 t.getChanges().entrySet().iterator(); 135 while (entries.hasNext()) { 136 final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = 137 entries.next(); 138 final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); 139 if (!wc.isAttached()) { 140 Slog.e(TAG, "Attempt to operate on detached container: " + wc); 141 continue; 142 } 143 // Make sure we add to the syncSet before performing 144 // operations so we don't end up splitting effects between the WM 145 // pending transaction and the BLASTSync transaction. 146 if (syncId >= 0) { 147 addToSyncSet(syncId, wc); 148 } 149 150 int containerEffect = applyWindowContainerChange(wc, entry.getValue()); 151 effects |= containerEffect; 152 153 // Lifecycle changes will trigger ensureConfig for everything. 154 if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 155 && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { 156 haveConfigChanges.add(wc); 157 } 158 } 159 // Hierarchy changes 160 final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); 161 for (int i = 0, n = hops.size(); i < n; ++i) { 162 final WindowContainerTransaction.HierarchyOp hop = hops.get(i); 163 final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); 164 if (!wc.isAttached()) { 165 Slog.e(TAG, "Attempt to operate on detached container: " + wc); 166 continue; 167 } 168 if (syncId >= 0) { 169 addToSyncSet(syncId, wc); 170 } 171 effects |= sanitizeAndApplyHierarchyOp(wc, hop); 172 } 173 // Queue-up bounds-change transactions for tasks which are now organized. Do 174 // this after hierarchy ops so we have the final organized state. 175 entries = t.getChanges().entrySet().iterator(); 176 while (entries.hasNext()) { 177 final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = 178 entries.next(); 179 final Task task = WindowContainer.fromBinder(entry.getKey()).asTask(); 180 final Rect surfaceBounds = entry.getValue().getBoundsChangeSurfaceBounds(); 181 if (task == null || !task.isAttached() || surfaceBounds == null) { 182 continue; 183 } 184 if (!task.isOrganized()) { 185 final Task parent = 186 task.getParent() != null ? task.getParent().asTask() : null; 187 // Also allow direct children of created-by-organizer tasks to be 188 // controlled. In the future, these will become organized anyways. 189 if (parent == null || !parent.mCreatedByOrganizer) { 190 throw new IllegalArgumentException( 191 "Can't manipulate non-organized task surface " + task); 192 } 193 } 194 final SurfaceControl.Transaction sft = new SurfaceControl.Transaction(); 195 final SurfaceControl sc = task.getSurfaceControl(); 196 sft.setPosition(sc, surfaceBounds.left, surfaceBounds.top); 197 if (surfaceBounds.isEmpty()) { 198 sft.setWindowCrop(sc, null); 199 } else { 200 sft.setWindowCrop(sc, surfaceBounds.width(), surfaceBounds.height()); 201 } 202 task.setMainWindowSizeChangeTransaction(sft); 203 } 204 if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { 205 // Already calls ensureActivityConfig 206 mService.mRootWindowContainer.ensureActivitiesVisible( 207 null, 0, PRESERVE_WINDOWS); 208 } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { 209 final PooledConsumer f = PooledLambda.obtainConsumer( 210 ActivityRecord::ensureActivityConfiguration, 211 PooledLambda.__(ActivityRecord.class), 0, 212 true /* preserveWindow */); 213 try { 214 for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { 215 haveConfigChanges.valueAt(i).forAllActivities(f); 216 } 217 } finally { 218 f.recycle(); 219 } 220 } 221 222 if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) == 0) { 223 mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED); 224 } 225 } finally { 226 mService.continueWindowLayout(); 227 if (syncId >= 0) { 228 setSyncReady(syncId); 229 } 230 } 231 } 232 } finally { 233 Binder.restoreCallingIdentity(ident); 234 } 235 return syncId; 236 } 237 applyChanges(WindowContainer container, WindowContainerTransaction.Change change)238 private int applyChanges(WindowContainer container, WindowContainerTransaction.Change change) { 239 // The "client"-facing API should prevent bad changes; however, just in case, sanitize 240 // masks here. 241 final int configMask = change.getConfigSetMask() & CONTROLLABLE_CONFIGS; 242 final int windowMask = change.getWindowSetMask() & CONTROLLABLE_WINDOW_CONFIGS; 243 int effects = 0; 244 if (configMask != 0) { 245 Configuration c = new Configuration(container.getRequestedOverrideConfiguration()); 246 c.setTo(change.getConfiguration(), configMask, windowMask); 247 container.onRequestedOverrideConfigurationChanged(c); 248 // TODO(b/145675353): remove the following once we could apply new bounds to the 249 // pinned stack together with its children. 250 resizePinnedStackIfNeeded(container, configMask, windowMask, c); 251 effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; 252 } 253 if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { 254 if (container.setFocusable(change.getFocusable())) { 255 effects |= TRANSACT_EFFECTS_LIFECYCLE; 256 } 257 } 258 259 final int windowingMode = change.getWindowingMode(); 260 if (windowingMode > -1) { 261 container.setWindowingMode(windowingMode); 262 } 263 return effects; 264 } 265 applyTaskChanges(Task tr, WindowContainerTransaction.Change c)266 private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) { 267 int effects = 0; 268 final SurfaceControl.Transaction t = c.getBoundsChangeTransaction(); 269 270 if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { 271 if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) { 272 effects = TRANSACT_EFFECTS_LIFECYCLE; 273 } 274 } 275 276 final int childWindowingMode = c.getActivityWindowingMode(); 277 if (childWindowingMode > -1) { 278 tr.setActivityWindowingMode(childWindowingMode); 279 } 280 281 if (t != null) { 282 tr.setMainWindowSizeChangeTransaction(t); 283 } 284 285 Rect enterPipBounds = c.getEnterPipBounds(); 286 if (enterPipBounds != null) { 287 mService.mStackSupervisor.updatePictureInPictureMode(tr, enterPipBounds, true); 288 } 289 290 return effects; 291 } 292 sanitizeAndApplyHierarchyOp(WindowContainer container, WindowContainerTransaction.HierarchyOp hop)293 private int sanitizeAndApplyHierarchyOp(WindowContainer container, 294 WindowContainerTransaction.HierarchyOp hop) { 295 final Task task = container.asTask(); 296 if (task == null) { 297 throw new IllegalArgumentException("Invalid container in hierarchy op"); 298 } 299 final DisplayContent dc = task.getDisplayContent(); 300 if (dc == null) { 301 Slog.w(TAG, "Container is no longer attached: " + task); 302 return 0; 303 } 304 final ActivityStack as = (ActivityStack) task; 305 306 if (hop.isReparent()) { 307 final boolean isNonOrganizedRootableTask = 308 (task.isRootTask() && !task.mCreatedByOrganizer) 309 || task.getParent().asTask().mCreatedByOrganizer; 310 if (isNonOrganizedRootableTask) { 311 Task newParent = hop.getNewParent() == null ? null 312 : WindowContainer.fromBinder(hop.getNewParent()).asTask(); 313 if (task.getParent() != newParent) { 314 if (newParent == null) { 315 // Re-parent task to display as a root task. 316 as.reparent(dc.getDefaultTaskDisplayArea(), hop.getToTop()); 317 } else if (newParent.inMultiWindowMode() && !task.isResizeable() 318 && task.isLeafTask()) { 319 Slog.w(TAG, "Can't support task that doesn't support multi-window mode in" 320 + " multi-window mode... newParent=" + newParent + " task=" + task); 321 return 0; 322 } else { 323 task.reparent((ActivityStack) newParent, 324 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, 325 false /*moveParents*/, "sanitizeAndApplyHierarchyOp"); 326 } 327 } else { 328 final ActivityStack rootTask = 329 (ActivityStack) (newParent != null ? newParent : task.getRootTask()); 330 if (hop.getToTop()) { 331 as.getDisplayArea().positionStackAtTop(rootTask, 332 false /* includingParents */); 333 } else { 334 as.getDisplayArea().positionStackAtBottom(rootTask); 335 } 336 } 337 } else { 338 throw new RuntimeException("Reparenting leaf Tasks is not supported now."); 339 } 340 } else { 341 // Ugh, of course ActivityStack has its own special reorder logic... 342 if (task.isRootTask()) { 343 if (hop.getToTop()) { 344 as.getDisplayArea().positionStackAtTop(as, false /* includingParents */); 345 } else { 346 as.getDisplayArea().positionStackAtBottom(as); 347 } 348 } else { 349 task.getParent().positionChildAt( 350 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, 351 task, false /* includingParents */); 352 } 353 } 354 return TRANSACT_EFFECTS_LIFECYCLE; 355 } 356 sanitizeWindowContainer(WindowContainer wc)357 private void sanitizeWindowContainer(WindowContainer wc) { 358 if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) { 359 throw new RuntimeException("Invalid token in task or displayArea transaction"); 360 } 361 } 362 applyWindowContainerChange(WindowContainer wc, WindowContainerTransaction.Change c)363 private int applyWindowContainerChange(WindowContainer wc, 364 WindowContainerTransaction.Change c) { 365 sanitizeWindowContainer(wc); 366 367 int effects = applyChanges(wc, c); 368 369 if (wc instanceof Task) { 370 effects |= applyTaskChanges(wc.asTask(), c); 371 } 372 373 return effects; 374 } 375 resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, int windowMask, Configuration config)376 private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask, 377 int windowMask, Configuration config) { 378 if ((container instanceof ActivityStack) 379 && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) 380 && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) { 381 final ActivityStack stack = (ActivityStack) container; 382 if (stack.inPinnedWindowingMode()) { 383 stack.resize(config.windowConfiguration.getBounds(), 384 PRESERVE_WINDOWS, true /* deferResume */); 385 } 386 } 387 } 388 389 @Override getTaskOrganizerController()390 public ITaskOrganizerController getTaskOrganizerController() { 391 enforceStackPermission("getTaskOrganizerController()"); 392 return mTaskOrganizerController; 393 } 394 395 @Override getDisplayAreaOrganizerController()396 public IDisplayAreaOrganizerController getDisplayAreaOrganizerController() { 397 enforceStackPermission("getDisplayAreaOrganizerController()"); 398 return mDisplayAreaOrganizerController; 399 } 400 401 @VisibleForTesting startSyncWithOrganizer(IWindowContainerTransactionCallback callback)402 int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) { 403 int id = mBLASTSyncEngine.startSyncSet(this); 404 mTransactionCallbacksByPendingSyncId.put(id, callback); 405 return id; 406 } 407 408 @VisibleForTesting setSyncReady(int id)409 void setSyncReady(int id) { 410 mBLASTSyncEngine.setReady(id); 411 } 412 413 @VisibleForTesting addToSyncSet(int syncId, WindowContainer wc)414 void addToSyncSet(int syncId, WindowContainer wc) { 415 mBLASTSyncEngine.addToSyncSet(syncId, wc); 416 } 417 418 @Override onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady)419 public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) { 420 final IWindowContainerTransactionCallback callback = 421 mTransactionCallbacksByPendingSyncId.get(mSyncId); 422 423 SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); 424 for (WindowContainer container : windowContainersReady) { 425 container.mergeBlastSyncTransaction(mergedTransaction); 426 } 427 428 try { 429 callback.onTransactionReady(mSyncId, mergedTransaction); 430 } catch (RemoteException e) { 431 // If there's an exception when trying to send the mergedTransaction to the client, we 432 // should immediately apply it here so the transactions aren't lost. 433 mergedTransaction.apply(); 434 } 435 436 mTransactionCallbacksByPendingSyncId.remove(mSyncId); 437 } 438 439 @Override takeScreenshot(WindowContainerToken token, SurfaceControl outSurfaceControl)440 public boolean takeScreenshot(WindowContainerToken token, SurfaceControl outSurfaceControl) { 441 mService.mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "takeScreenshot()"); 442 final WindowContainer wc = WindowContainer.fromBinder(token.asBinder()); 443 if (wc == null) { 444 throw new RuntimeException("Invalid token in screenshot transaction"); 445 } 446 447 final Rect bounds = new Rect(); 448 wc.getBounds(bounds); 449 bounds.offsetTo(0, 0); 450 SurfaceControl.ScreenshotGraphicBuffer buffer = SurfaceControl.captureLayers( 451 wc.getSurfaceControl(), bounds, 1); 452 453 if (buffer == null || buffer.getGraphicBuffer() == null) { 454 return false; 455 } 456 457 SurfaceControl screenshot = mService.mWindowManager.mSurfaceControlFactory.apply(null) 458 .setName(wc.getName() + " - Organizer Screenshot") 459 .setBufferSize(bounds.width(), bounds.height()) 460 .setFormat(PixelFormat.TRANSLUCENT) 461 .setParent(wc.getParentSurfaceControl()) 462 .setCallsite("WindowOrganizerController.takeScreenshot") 463 .build(); 464 465 Surface surface = new Surface(); 466 surface.copyFrom(screenshot); 467 surface.attachAndQueueBufferWithColorSpace(buffer.getGraphicBuffer(), null); 468 surface.release(); 469 470 outSurfaceControl.copyFrom(screenshot, "WindowOrganizerController.takeScreenshot"); 471 return true; 472 } 473 enforceStackPermission(String func)474 private void enforceStackPermission(String func) { 475 mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); 476 } 477 } 478