• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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