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