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.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; 20 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK; 21 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; 22 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; 23 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; 24 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; 25 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; 26 27 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; 28 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; 29 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; 30 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; 31 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; 32 import static com.android.server.wm.WindowContainer.POSITION_TOP; 33 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.app.WindowConfiguration; 37 import android.content.pm.ActivityInfo; 38 import android.content.res.Configuration; 39 import android.graphics.Rect; 40 import android.os.Binder; 41 import android.os.Bundle; 42 import android.os.IBinder; 43 import android.os.Parcel; 44 import android.os.RemoteException; 45 import android.util.ArraySet; 46 import android.util.Slog; 47 import android.view.SurfaceControl; 48 import android.window.IDisplayAreaOrganizerController; 49 import android.window.ITaskOrganizerController; 50 import android.window.ITransitionPlayer; 51 import android.window.IWindowContainerTransactionCallback; 52 import android.window.IWindowOrganizerController; 53 import android.window.WindowContainerTransaction; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.internal.protolog.common.ProtoLog; 57 import com.android.internal.util.ArrayUtils; 58 import com.android.internal.util.function.pooled.PooledConsumer; 59 import com.android.internal.util.function.pooled.PooledLambda; 60 61 import java.util.ArrayList; 62 import java.util.HashMap; 63 import java.util.Iterator; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.function.Consumer; 67 68 /** 69 * Server side implementation for the interface for organizing windows 70 * @see android.window.WindowOrganizer 71 */ 72 class WindowOrganizerController extends IWindowOrganizerController.Stub 73 implements BLASTSyncEngine.TransactionReadyListener { 74 75 private static final String TAG = "WindowOrganizerController"; 76 77 /** Flag indicating that an applied transaction may have effected lifecycle */ 78 private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1; 79 private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1; 80 81 /** 82 * Masks specifying which configurations task-organizers can control. Incoming transactions 83 * will be filtered to only include these. 84 */ 85 static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION 86 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE; 87 static final int CONTROLLABLE_WINDOW_CONFIGS = WindowConfiguration.WINDOW_CONFIG_BOUNDS 88 | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; 89 90 private final ActivityTaskManagerService mService; 91 private final WindowManagerGlobalLock mGlobalLock; 92 93 private final HashMap<Integer, IWindowContainerTransactionCallback> 94 mTransactionCallbacksByPendingSyncId = new HashMap(); 95 96 final TaskOrganizerController mTaskOrganizerController; 97 final DisplayAreaOrganizerController mDisplayAreaOrganizerController; 98 99 final TransitionController mTransitionController; 100 WindowOrganizerController(ActivityTaskManagerService atm)101 WindowOrganizerController(ActivityTaskManagerService atm) { 102 mService = atm; 103 mGlobalLock = atm.mGlobalLock; 104 mTaskOrganizerController = new TaskOrganizerController(mService); 105 mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); 106 mTransitionController = new TransitionController(atm); 107 } 108 getTransitionController()109 TransitionController getTransitionController() { 110 return mTransitionController; 111 } 112 113 @Override onTransact(int code, Parcel data, Parcel reply, int flags)114 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 115 throws RemoteException { 116 try { 117 return super.onTransact(code, data, reply, flags); 118 } catch (RuntimeException e) { 119 throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(TAG, e); 120 } 121 } 122 123 @Override applyTransaction(WindowContainerTransaction t)124 public void applyTransaction(WindowContainerTransaction t) { 125 enforceTaskPermission("applyTransaction()"); 126 if (t == null) { 127 throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); 128 } 129 final long ident = Binder.clearCallingIdentity(); 130 try { 131 synchronized (mGlobalLock) { 132 applyTransaction(t, -1 /*syncId*/, null /*transition*/); 133 } 134 } finally { 135 Binder.restoreCallingIdentity(ident); 136 } 137 } 138 139 @Override applySyncTransaction(WindowContainerTransaction t, IWindowContainerTransactionCallback callback)140 public int applySyncTransaction(WindowContainerTransaction t, 141 IWindowContainerTransactionCallback callback) { 142 enforceTaskPermission("applySyncTransaction()"); 143 if (t == null) { 144 throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); 145 } 146 final long ident = Binder.clearCallingIdentity(); 147 try { 148 synchronized (mGlobalLock) { 149 /** 150 * If callback is non-null we are looking to synchronize this transaction by 151 * collecting all the results in to a SurfaceFlinger transaction and then delivering 152 * that to the given transaction ready callback. See {@link BLASTSyncEngine} for the 153 * details of the operation. But at a high level we create a sync operation with a 154 * given ID and an associated callback. Then we notify each WindowContainer in this 155 * WindowContainer transaction that it is participating in a sync operation with 156 * that ID. Once everything is notified we tell the BLASTSyncEngine "setSyncReady" 157 * which means that we have added everything to the set. At any point after this, 158 * all the WindowContainers will eventually finish applying their changes and notify 159 * the BLASTSyncEngine which will deliver the Transaction to the callback. 160 */ 161 int syncId = -1; 162 if (callback != null) { 163 syncId = startSyncWithOrganizer(callback); 164 } 165 applyTransaction(t, syncId, null /*transition*/); 166 if (syncId >= 0) { 167 setSyncReady(syncId); 168 } 169 return syncId; 170 } 171 } finally { 172 Binder.restoreCallingIdentity(ident); 173 } 174 } 175 176 @Override startTransition(int type, @Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t)177 public IBinder startTransition(int type, @Nullable IBinder transitionToken, 178 @Nullable WindowContainerTransaction t) { 179 enforceTaskPermission("startTransition()"); 180 final long ident = Binder.clearCallingIdentity(); 181 try { 182 synchronized (mGlobalLock) { 183 Transition transition = Transition.fromBinder(transitionToken); 184 // In cases where transition is already provided, the "readiness lifecycle" of the 185 // transition is determined outside of this transaction. However, if this is a 186 // direct call from shell, the entire transition lifecycle is contained in the 187 // provided transaction and thus we can setReady immediately after apply. 188 boolean needsSetReady = transition == null && t != null; 189 if (transition == null) { 190 if (type < 0) { 191 throw new IllegalArgumentException("Can't create transition with no type"); 192 } 193 if (mTransitionController.getTransitionPlayer() == null) { 194 Slog.w(TAG, "Using shell transitions API for legacy transitions."); 195 if (t == null) { 196 throw new IllegalArgumentException("Can't use legacy transitions in" 197 + " compatibility mode with no WCT."); 198 } 199 applyTransaction(t, -1 /* syncId */, null); 200 return null; 201 } 202 transition = mTransitionController.createTransition(type); 203 } 204 transition.start(); 205 if (t == null) { 206 t = new WindowContainerTransaction(); 207 } 208 applyTransaction(t, -1 /*syncId*/, transition); 209 if (needsSetReady) { 210 transition.setReady(); 211 } 212 return transition; 213 } 214 } finally { 215 Binder.restoreCallingIdentity(ident); 216 } 217 } 218 219 @Override finishTransition(@onNull IBinder transitionToken, @Nullable WindowContainerTransaction t, @Nullable IWindowContainerTransactionCallback callback)220 public int finishTransition(@NonNull IBinder transitionToken, 221 @Nullable WindowContainerTransaction t, 222 @Nullable IWindowContainerTransactionCallback callback) { 223 enforceTaskPermission("finishTransition()"); 224 final long ident = Binder.clearCallingIdentity(); 225 try { 226 synchronized (mGlobalLock) { 227 int syncId = -1; 228 if (t != null && callback != null) { 229 syncId = startSyncWithOrganizer(callback); 230 } 231 // apply the incoming transaction before finish in case it alters the visibility 232 // of the participants. 233 if (t != null) { 234 applyTransaction(t, syncId, null /*transition*/); 235 } 236 getTransitionController().finishTransition(transitionToken); 237 if (syncId >= 0) { 238 setSyncReady(syncId); 239 } 240 return syncId; 241 } 242 } finally { 243 Binder.restoreCallingIdentity(ident); 244 } 245 } 246 247 /** 248 * @param syncId If non-null, this will be a sync-transaction. 249 * @param transition A transition to collect changes into. 250 */ applyTransaction(@onNull WindowContainerTransaction t, int syncId, @Nullable Transition transition)251 private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId, 252 @Nullable Transition transition) { 253 int effects = 0; 254 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); 255 mService.deferWindowLayout(); 256 try { 257 if (transition != null) { 258 // First check if we have a display rotation transition and if so, update it. 259 final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition); 260 if (dc != null && transition.mChanges.get(dc).mRotation != dc.getRotation()) { 261 // Go through all tasks and collect them before the rotation 262 // TODO(shell-transitions): move collect() to onConfigurationChange once 263 // wallpaper handling is synchronized. 264 dc.forAllTasks(task -> { 265 if (task.isVisible()) transition.collect(task); 266 }); 267 dc.getInsetsStateController().addProvidersToTransition(); 268 dc.sendNewConfiguration(); 269 effects |= TRANSACT_EFFECTS_LIFECYCLE; 270 } 271 } 272 ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>(); 273 Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = 274 t.getChanges().entrySet().iterator(); 275 while (entries.hasNext()) { 276 final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); 277 final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); 278 if (wc == null || !wc.isAttached()) { 279 Slog.e(TAG, "Attempt to operate on detached container: " + wc); 280 continue; 281 } 282 // Make sure we add to the syncSet before performing 283 // operations so we don't end up splitting effects between the WM 284 // pending transaction and the BLASTSync transaction. 285 if (syncId >= 0) { 286 addToSyncSet(syncId, wc); 287 } 288 if (transition != null) transition.collect(wc); 289 290 int containerEffect = applyWindowContainerChange(wc, entry.getValue()); 291 effects |= containerEffect; 292 293 // Lifecycle changes will trigger ensureConfig for everything. 294 if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0 295 && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { 296 haveConfigChanges.add(wc); 297 } 298 } 299 // Hierarchy changes 300 final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); 301 final int hopSize = hops.size(); 302 if (hopSize > 0) { 303 final boolean isInLockTaskMode = mService.isInLockTaskMode(); 304 for (int i = 0; i < hopSize; ++i) { 305 effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition, 306 isInLockTaskMode); 307 } 308 } 309 // Queue-up bounds-change transactions for tasks which are now organized. Do 310 // this after hierarchy ops so we have the final organized state. 311 entries = t.getChanges().entrySet().iterator(); 312 while (entries.hasNext()) { 313 final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); 314 final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); 315 if (wc == null || !wc.isAttached()) { 316 Slog.e(TAG, "Attempt to operate on detached container: " + wc); 317 continue; 318 } 319 final Task task = wc.asTask(); 320 final Rect surfaceBounds = entry.getValue().getBoundsChangeSurfaceBounds(); 321 if (task == null || !task.isAttached() || surfaceBounds == null) { 322 continue; 323 } 324 if (!task.isOrganized()) { 325 final Task parent = task.getParent() != null ? task.getParent().asTask() : null; 326 // Also allow direct children of created-by-organizer tasks to be 327 // controlled. In the future, these will become organized anyways. 328 if (parent == null || !parent.mCreatedByOrganizer) { 329 throw new IllegalArgumentException( 330 "Can't manipulate non-organized task surface " + task); 331 } 332 } 333 final SurfaceControl.Transaction sft = new SurfaceControl.Transaction(); 334 final SurfaceControl sc = task.getSurfaceControl(); 335 sft.setPosition(sc, surfaceBounds.left, surfaceBounds.top); 336 if (surfaceBounds.isEmpty()) { 337 sft.setWindowCrop(sc, null); 338 } else { 339 sft.setWindowCrop(sc, surfaceBounds.width(), surfaceBounds.height()); 340 } 341 task.setMainWindowSizeChangeTransaction(sft); 342 } 343 if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) { 344 // Already calls ensureActivityConfig 345 mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); 346 mService.mRootWindowContainer.resumeFocusedTasksTopActivities(); 347 } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) { 348 final PooledConsumer f = PooledLambda.obtainConsumer( 349 ActivityRecord::ensureActivityConfiguration, 350 PooledLambda.__(ActivityRecord.class), 0, 351 true /* preserveWindow */); 352 try { 353 for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { 354 haveConfigChanges.valueAt(i).forAllActivities(f); 355 } 356 } finally { 357 f.recycle(); 358 } 359 } 360 361 if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) == 0) { 362 mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED); 363 } 364 } finally { 365 mService.continueWindowLayout(); 366 } 367 } 368 applyChanges(WindowContainer container, WindowContainerTransaction.Change change)369 private int applyChanges(WindowContainer container, WindowContainerTransaction.Change change) { 370 // The "client"-facing API should prevent bad changes; however, just in case, sanitize 371 // masks here. 372 final int configMask = change.getConfigSetMask() & CONTROLLABLE_CONFIGS; 373 final int windowMask = change.getWindowSetMask() & CONTROLLABLE_WINDOW_CONFIGS; 374 int effects = 0; 375 final int windowingMode = change.getWindowingMode(); 376 if (configMask != 0) { 377 if (windowingMode > -1 && windowingMode != container.getWindowingMode()) { 378 // Special handling for when we are setting a windowingMode in the same transaction. 379 // Setting the windowingMode is going to call onConfigurationChanged so we don't 380 // need it called right now. Additionally, some logic requires everything in the 381 // configuration to change at the same time (ie. surface-freezer requires bounds 382 // and mode to change at the same time). 383 final Configuration c = container.getRequestedOverrideConfiguration(); 384 c.setTo(change.getConfiguration(), configMask, windowMask); 385 } else { 386 final Configuration c = 387 new Configuration(container.getRequestedOverrideConfiguration()); 388 c.setTo(change.getConfiguration(), configMask, windowMask); 389 container.onRequestedOverrideConfigurationChanged(c); 390 } 391 effects |= TRANSACT_EFFECTS_CLIENT_CONFIG; 392 } 393 if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) { 394 if (container.setFocusable(change.getFocusable())) { 395 effects |= TRANSACT_EFFECTS_LIFECYCLE; 396 } 397 } 398 399 if (windowingMode > -1) { 400 if (mService.isInLockTaskMode() 401 && WindowConfiguration.inMultiWindowMode(windowingMode)) { 402 throw new UnsupportedOperationException("Not supported to set multi-window" 403 + " windowing mode during locked task mode."); 404 } 405 container.setWindowingMode(windowingMode); 406 } 407 return effects; 408 } 409 applyTaskChanges(Task tr, WindowContainerTransaction.Change c)410 private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) { 411 int effects = 0; 412 final SurfaceControl.Transaction t = c.getBoundsChangeTransaction(); 413 414 if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { 415 if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) { 416 effects = TRANSACT_EFFECTS_LIFECYCLE; 417 } 418 } 419 420 final int childWindowingMode = c.getActivityWindowingMode(); 421 if (childWindowingMode > -1) { 422 tr.setActivityWindowingMode(childWindowingMode); 423 } 424 425 if (t != null) { 426 tr.setMainWindowSizeChangeTransaction(t); 427 } 428 429 Rect enterPipBounds = c.getEnterPipBounds(); 430 if (enterPipBounds != null) { 431 tr.mDisplayContent.mPinnedTaskController.setEnterPipBounds(enterPipBounds); 432 } 433 434 return effects; 435 } 436 applyDisplayAreaChanges(DisplayArea displayArea, WindowContainerTransaction.Change c)437 private int applyDisplayAreaChanges(DisplayArea displayArea, 438 WindowContainerTransaction.Change c) { 439 final int[] effects = new int[1]; 440 441 if ((c.getChangeMask() 442 & WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) { 443 if (displayArea.setIgnoreOrientationRequest(c.getIgnoreOrientationRequest())) { 444 effects[0] |= TRANSACT_EFFECTS_LIFECYCLE; 445 } 446 } 447 448 displayArea.forAllTasks(task -> { 449 Task tr = (Task) task; 450 if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { 451 if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) { 452 effects[0] |= TRANSACT_EFFECTS_LIFECYCLE; 453 } 454 } 455 }); 456 457 return effects[0]; 458 } 459 applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects, int syncId, @Nullable Transition transition, boolean isInLockTaskMode)460 private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects, 461 int syncId, @Nullable Transition transition, boolean isInLockTaskMode) { 462 final int type = hop.getType(); 463 switch (type) { 464 case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: { 465 final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); 466 final Task task = wc != null ? wc.asTask() : null; 467 if (task != null) { 468 task.getDisplayArea().setLaunchRootTask(task, 469 hop.getWindowingModes(), hop.getActivityTypes()); 470 } else { 471 throw new IllegalArgumentException("Cannot set non-task as launch root: " + wc); 472 } 473 break; 474 } 475 case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: { 476 final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); 477 final Task task = wc != null ? wc.asTask() : null; 478 if (task == null) { 479 throw new IllegalArgumentException("Cannot set non-task as launch root: " + wc); 480 } else if (!task.mCreatedByOrganizer) { 481 throw new UnsupportedOperationException( 482 "Cannot set non-organized task as adjacent flag root: " + wc); 483 } else if (task.mAdjacentTask == null) { 484 throw new UnsupportedOperationException( 485 "Cannot set non-adjacent task as adjacent flag root: " + wc); 486 } 487 488 final boolean clearRoot = hop.getToTop(); 489 task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task); 490 break; 491 } 492 case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: 493 effects |= setAdjacentRootsHierarchyOp(hop); 494 break; 495 } 496 // The following operations may change task order so they are skipped while in lock task 497 // mode. The above operations are still allowed because they don't move tasks. And it may 498 // be necessary such as clearing launch root after entering lock task mode. 499 if (isInLockTaskMode) { 500 Slog.w(TAG, "Skip applying hierarchy operation " + hop + " while in lock task mode"); 501 return effects; 502 } 503 504 switch (type) { 505 case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: 506 effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId); 507 break; 508 case HIERARCHY_OP_TYPE_REORDER: 509 case HIERARCHY_OP_TYPE_REPARENT: 510 final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); 511 if (wc == null || !wc.isAttached()) { 512 Slog.e(TAG, "Attempt to operate on detached container: " + wc); 513 break; 514 } 515 if (syncId >= 0) { 516 addToSyncSet(syncId, wc); 517 } 518 if (transition != null) { 519 transition.collect(wc); 520 if (hop.isReparent()) { 521 if (wc.getParent() != null) { 522 // Collect the current parent. It's visibility may change as 523 // a result of this reparenting. 524 transition.collect(wc.getParent()); 525 } 526 if (hop.getNewParent() != null) { 527 final WindowContainer parentWc = 528 WindowContainer.fromBinder(hop.getNewParent()); 529 if (parentWc == null) { 530 Slog.e(TAG, "Can't resolve parent window from token"); 531 break; 532 } 533 transition.collect(parentWc); 534 } 535 } 536 } 537 effects |= sanitizeAndApplyHierarchyOp(wc, hop); 538 break; 539 case HIERARCHY_OP_TYPE_LAUNCH_TASK: 540 final Bundle launchOpts = hop.getLaunchOptions(); 541 final int taskId = launchOpts.getInt( 542 WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); 543 launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); 544 mService.startActivityFromRecents(taskId, launchOpts); 545 break; 546 } 547 return effects; 548 } 549 sanitizeAndApplyHierarchyOp(WindowContainer container, WindowContainerTransaction.HierarchyOp hop)550 private int sanitizeAndApplyHierarchyOp(WindowContainer container, 551 WindowContainerTransaction.HierarchyOp hop) { 552 final Task task = container.asTask(); 553 if (task == null) { 554 throw new IllegalArgumentException("Invalid container in hierarchy op"); 555 } 556 final DisplayContent dc = task.getDisplayContent(); 557 if (dc == null) { 558 Slog.w(TAG, "Container is no longer attached: " + task); 559 return 0; 560 } 561 final Task as = task; 562 563 if (hop.isReparent()) { 564 final boolean isNonOrganizedRootableTask = 565 task.isRootTask() || task.getParent().asTask().mCreatedByOrganizer; 566 if (isNonOrganizedRootableTask) { 567 WindowContainer newParent = hop.getNewParent() == null 568 ? dc.getDefaultTaskDisplayArea() 569 : WindowContainer.fromBinder(hop.getNewParent()); 570 if (newParent == null) { 571 Slog.e(TAG, "Can't resolve parent window from token"); 572 return 0; 573 } 574 if (task.getParent() != newParent) { 575 if (newParent.asTaskDisplayArea() != null) { 576 // For now, reparenting to displayarea is different from other reparents... 577 as.reparent(newParent.asTaskDisplayArea(), hop.getToTop()); 578 } else if (newParent.asTask() != null) { 579 if (newParent.inMultiWindowMode() && task.isLeafTask()) { 580 if (newParent.inPinnedWindowingMode()) { 581 Slog.w(TAG, "Can't support moving a task to another PIP window..." 582 + " newParent=" + newParent + " task=" + task); 583 return 0; 584 } 585 if (!task.supportsMultiWindowInDisplayArea( 586 newParent.asTask().getDisplayArea())) { 587 Slog.w(TAG, "Can't support task that doesn't support multi-window" 588 + " mode in multi-window mode... newParent=" + newParent 589 + " task=" + task); 590 return 0; 591 } 592 } 593 task.reparent((Task) newParent, 594 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, 595 false /*moveParents*/, "sanitizeAndApplyHierarchyOp"); 596 } else { 597 throw new RuntimeException("Can only reparent task to another task or" 598 + " taskDisplayArea, but not " + newParent); 599 } 600 } else { 601 final Task rootTask = (Task) ( 602 (newParent != null && !(newParent instanceof TaskDisplayArea)) 603 ? newParent : task.getRootTask()); 604 as.getDisplayArea().positionChildAt( 605 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, rootTask, 606 false /* includingParents */); 607 } 608 } else { 609 throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task); 610 } 611 } else { 612 task.getParent().positionChildAt( 613 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, 614 task, false /* includingParents */); 615 } 616 return TRANSACT_EFFECTS_LIFECYCLE; 617 } 618 reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop, @Nullable Transition transition, int syncId)619 private int reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop, 620 @Nullable Transition transition, int syncId) { 621 WindowContainer currentParent = hop.getContainer() != null 622 ? WindowContainer.fromBinder(hop.getContainer()) : null; 623 WindowContainer newParent = hop.getNewParent() != null 624 ? WindowContainer.fromBinder(hop.getNewParent()) : null; 625 if (currentParent == null && newParent == null) { 626 throw new IllegalArgumentException("reparentChildrenTasksHierarchyOp: " + hop); 627 } else if (currentParent == null) { 628 currentParent = newParent.asTask().getDisplayContent().getDefaultTaskDisplayArea(); 629 } else if (newParent == null) { 630 newParent = currentParent.asTask().getDisplayContent().getDefaultTaskDisplayArea(); 631 } 632 633 if (currentParent == newParent) { 634 Slog.e(TAG, "reparentChildrenTasksHierarchyOp parent not changing: " + hop); 635 return 0; 636 } 637 if (!currentParent.isAttached()) { 638 Slog.e(TAG, "reparentChildrenTasksHierarchyOp currentParent detached=" 639 + currentParent + " hop=" + hop); 640 return 0; 641 } 642 if (!newParent.isAttached()) { 643 Slog.e(TAG, "reparentChildrenTasksHierarchyOp newParent detached=" 644 + newParent + " hop=" + hop); 645 return 0; 646 } 647 if (newParent.inPinnedWindowingMode()) { 648 Slog.e(TAG, "reparentChildrenTasksHierarchyOp newParent in PIP=" 649 + newParent + " hop=" + hop); 650 return 0; 651 } 652 653 final boolean newParentInMultiWindow = newParent.inMultiWindowMode(); 654 final TaskDisplayArea newParentTda = newParent.asTask() != null 655 ? newParent.asTask().getDisplayArea() 656 : newParent.asTaskDisplayArea(); 657 final WindowContainer finalCurrentParent = currentParent; 658 Slog.i(TAG, "reparentChildrenTasksHierarchyOp" 659 + " currentParent=" + currentParent + " newParent=" + newParent + " hop=" + hop); 660 661 // We want to collect the tasks first before re-parenting to avoid array shifting on us. 662 final ArrayList<Task> tasksToReparent = new ArrayList<>(); 663 664 currentParent.forAllTasks((Consumer<Task>) (task) -> { 665 Slog.i(TAG, " Processing task=" + task); 666 if (task.mCreatedByOrganizer 667 || task.getParent() != finalCurrentParent) { 668 // We only care about non-organized task that are direct children of the thing we 669 // are reparenting from. 670 return; 671 } 672 if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) { 673 Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window," 674 + " task=" + task); 675 return; 676 } 677 if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return; 678 if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return; 679 680 tasksToReparent.add(task); 681 }, !hop.getToTop()); 682 683 final int count = tasksToReparent.size(); 684 for (int i = 0; i < count; ++i) { 685 final Task task = tasksToReparent.get(i); 686 if (syncId >= 0) { 687 addToSyncSet(syncId, task); 688 } 689 if (transition != null) transition.collect(task); 690 691 if (newParent instanceof TaskDisplayArea) { 692 // For now, reparenting to display area is different from other reparents... 693 task.reparent((TaskDisplayArea) newParent, hop.getToTop()); 694 } else { 695 task.reparent((Task) newParent, 696 hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, 697 false /*moveParents*/, "processChildrenTaskReparentHierarchyOp"); 698 } 699 } 700 701 if (transition != null) transition.collect(newParent); 702 703 return TRANSACT_EFFECTS_LIFECYCLE; 704 } 705 setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop)706 private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) { 707 final Task root1 = WindowContainer.fromBinder(hop.getContainer()).asTask(); 708 final Task root2 = WindowContainer.fromBinder(hop.getAdjacentRoot()).asTask(); 709 if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) { 710 throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" 711 + " organizer root1=" + root1 + " root2=" + root2); 712 } 713 root1.setAdjacentTask(root2); 714 return TRANSACT_EFFECTS_LIFECYCLE; 715 } 716 sanitizeWindowContainer(WindowContainer wc)717 private void sanitizeWindowContainer(WindowContainer wc) { 718 if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) { 719 throw new RuntimeException("Invalid token in task or displayArea transaction"); 720 } 721 } 722 applyWindowContainerChange(WindowContainer wc, WindowContainerTransaction.Change c)723 private int applyWindowContainerChange(WindowContainer wc, 724 WindowContainerTransaction.Change c) { 725 sanitizeWindowContainer(wc); 726 727 int effects = applyChanges(wc, c); 728 729 if (wc instanceof DisplayArea) { 730 effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c); 731 } else if (wc instanceof Task) { 732 effects |= applyTaskChanges(wc.asTask(), c); 733 } 734 735 return effects; 736 } 737 738 @Override getTaskOrganizerController()739 public ITaskOrganizerController getTaskOrganizerController() { 740 enforceTaskPermission("getTaskOrganizerController()"); 741 return mTaskOrganizerController; 742 } 743 744 @Override getDisplayAreaOrganizerController()745 public IDisplayAreaOrganizerController getDisplayAreaOrganizerController() { 746 enforceTaskPermission("getDisplayAreaOrganizerController()"); 747 return mDisplayAreaOrganizerController; 748 } 749 750 @VisibleForTesting startSyncWithOrganizer(IWindowContainerTransactionCallback callback)751 int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) { 752 int id = mService.mWindowManager.mSyncEngine.startSyncSet(this); 753 mTransactionCallbacksByPendingSyncId.put(id, callback); 754 return id; 755 } 756 757 @VisibleForTesting setSyncReady(int id)758 void setSyncReady(int id) { 759 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Set sync ready, syncId=%d", id); 760 mService.mWindowManager.mSyncEngine.setReady(id); 761 } 762 763 @VisibleForTesting addToSyncSet(int syncId, WindowContainer wc)764 void addToSyncSet(int syncId, WindowContainer wc) { 765 mService.mWindowManager.mSyncEngine.addToSyncSet(syncId, wc); 766 } 767 768 @Override onTransactionReady(int syncId, SurfaceControl.Transaction t)769 public void onTransactionReady(int syncId, SurfaceControl.Transaction t) { 770 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Transaction ready, syncId=%d", syncId); 771 final IWindowContainerTransactionCallback callback = 772 mTransactionCallbacksByPendingSyncId.get(syncId); 773 774 try { 775 callback.onTransactionReady(syncId, t); 776 } catch (RemoteException e) { 777 // If there's an exception when trying to send the mergedTransaction to the client, we 778 // should immediately apply it here so the transactions aren't lost. 779 t.apply(); 780 } 781 782 mTransactionCallbacksByPendingSyncId.remove(syncId); 783 } 784 785 @Override registerTransitionPlayer(ITransitionPlayer player)786 public void registerTransitionPlayer(ITransitionPlayer player) { 787 enforceTaskPermission("registerTransitionPlayer()"); 788 final long ident = Binder.clearCallingIdentity(); 789 try { 790 synchronized (mGlobalLock) { 791 mTransitionController.registerTransitionPlayer(player); 792 } 793 } finally { 794 Binder.restoreCallingIdentity(ident); 795 } 796 } 797 enforceTaskPermission(String func)798 private void enforceTaskPermission(String func) { 799 mService.enforceTaskPermission(func); 800 } 801 } 802