• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.wm.shell.common;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.Rect;
23 import android.graphics.RectF;
24 import android.hardware.display.DisplayManager;
25 import android.hardware.display.DisplayTopology;
26 import android.os.RemoteException;
27 import android.util.ArraySet;
28 import android.util.Size;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 import android.view.Display;
32 import android.view.IDisplayWindowListener;
33 import android.view.IWindowManager;
34 import android.view.InsetsState;
35 import android.window.WindowContainerTransaction;
36 
37 import androidx.annotation.BinderThread;
38 
39 import com.android.window.flags.Flags;
40 import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
41 import com.android.wm.shell.shared.annotations.ShellMainThread;
42 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
43 import com.android.wm.shell.sysui.ShellInit;
44 
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 
51 /**
52  * This module deals with display rotations coming from WM. When WM starts a rotation: after it has
53  * frozen the screen, it will call into this class. This will then call all registered local
54  * controllers and give them a chance to queue up task changes to be applied synchronously with that
55  * rotation.
56  */
57 public class DisplayController {
58     private static final String TAG = "DisplayController";
59 
60     private final ShellExecutor mMainExecutor;
61     private final Context mContext;
62     private final IWindowManager mWmService;
63     private final DisplayManager mDisplayManager;
64     private final DisplayChangeController mChangeController;
65     private final IDisplayWindowListener mDisplayContainerListener;
66 
67     private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
68     private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
69     private final Map<Integer, RectF> mUnpopulatedDisplayBounds = new HashMap<>();
70     private DisplayTopology mDisplayTopology;
71 
DisplayController(Context context, IWindowManager wmService, ShellInit shellInit, ShellExecutor mainExecutor, DisplayManager displayManager)72     public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
73             ShellExecutor mainExecutor, DisplayManager displayManager) {
74         mMainExecutor = mainExecutor;
75         mContext = context;
76         mWmService = wmService;
77         mDisplayManager = displayManager;
78         // TODO: Inject this instead
79         mChangeController = new DisplayChangeController(mWmService, shellInit, mainExecutor);
80         mDisplayContainerListener = new DisplayWindowListenerImpl();
81         // Note, add this after DisplaceChangeController is constructed to ensure that is
82         // initialized first
83         shellInit.addInitCallback(this::onInit, this);
84     }
85 
86     /**
87      * Initializes the window listener and the topology listener.
88      */
onInit()89     public void onInit() {
90         try {
91             int[] displayIds = mWmService.registerDisplayWindowListener(mDisplayContainerListener);
92             for (int i = 0; i < displayIds.length; i++) {
93                 onDisplayAdded(displayIds[i]);
94             }
95 
96             if (Flags.enableConnectedDisplaysWindowDrag()
97                     && DesktopModeStatus.canEnterDesktopMode(mContext)) {
98                 mDisplayManager.registerTopologyListener(mMainExecutor,
99                         this::onDisplayTopologyChanged);
100                 onDisplayTopologyChanged(mDisplayManager.getDisplayTopology());
101             }
102         } catch (RemoteException e) {
103             throw new RuntimeException("Unable to register display controller");
104         }
105     }
106 
107     /**
108      * Gets a display by id from DisplayManager.
109      */
getDisplay(int displayId)110     public Display getDisplay(int displayId) {
111         return mDisplayManager.getDisplay(displayId);
112     }
113 
114     /**
115      * Gets the DisplayLayout associated with a display.
116      */
getDisplayLayout(int displayId)117     public @Nullable DisplayLayout getDisplayLayout(int displayId) {
118         final DisplayRecord r = mDisplays.get(displayId);
119         return r != null ? r.mDisplayLayout : null;
120     }
121 
122     /**
123      * Gets a display-specific context for a display.
124      */
getDisplayContext(int displayId)125     public @Nullable Context getDisplayContext(int displayId) {
126         final DisplayRecord r = mDisplays.get(displayId);
127         return r != null ? r.mContext : null;
128     }
129 
130     /**
131      *  Get the InsetsState of a display.
132      */
getInsetsState(int displayId)133     public InsetsState getInsetsState(int displayId) {
134         final DisplayRecord r = mDisplays.get(displayId);
135         return r != null ? r.mInsetsState : null;
136     }
137 
138     /**
139      * Updates the insets for a given display.
140      */
updateDisplayInsets(int displayId, InsetsState state)141     public void updateDisplayInsets(int displayId, InsetsState state) {
142         final DisplayRecord r = mDisplays.get(displayId);
143         if (r != null) {
144             r.setInsets(state);
145         }
146     }
147 
148     /**
149      * Add a display window-container listener. It will get notified whenever a display's
150      * configuration changes or when displays are added/removed from the WM hierarchy.
151      */
addDisplayWindowListener(OnDisplaysChangedListener listener)152     public void addDisplayWindowListener(OnDisplaysChangedListener listener) {
153         synchronized (mDisplays) {
154             if (mDisplayChangedListeners.contains(listener)) {
155                 return;
156             }
157             mDisplayChangedListeners.add(listener);
158             for (int i = 0; i < mDisplays.size(); ++i) {
159                 listener.onDisplayAdded(mDisplays.keyAt(i));
160             }
161             listener.onTopologyChanged(mDisplayTopology);
162         }
163     }
164 
165     /**
166      * Remove a display window-container listener.
167      */
removeDisplayWindowListener(OnDisplaysChangedListener listener)168     public void removeDisplayWindowListener(OnDisplaysChangedListener listener) {
169         synchronized (mDisplays) {
170             mDisplayChangedListeners.remove(listener);
171         }
172     }
173 
174     /**
175      * Adds a display rotation controller.
176      */
addDisplayChangingController(OnDisplayChangingListener controller)177     public void addDisplayChangingController(OnDisplayChangingListener controller) {
178         mChangeController.addDisplayChangeListener(controller);
179     }
180 
181     /**
182      * Removes a display rotation controller.
183      */
removeDisplayChangingController(OnDisplayChangingListener controller)184     public void removeDisplayChangingController(OnDisplayChangingListener controller) {
185         mChangeController.removeDisplayChangeListener(controller);
186     }
187 
onDisplayAdded(int displayId)188     private void onDisplayAdded(int displayId) {
189         synchronized (mDisplays) {
190             if (mDisplays.get(displayId) != null) {
191                 return;
192             }
193             final Display display = getDisplay(displayId);
194             if (display == null) {
195                 // It's likely that the display is private to some app and thus not
196                 // accessible by system-ui.
197                 return;
198             }
199 
200             final Context context = (displayId == Display.DEFAULT_DISPLAY)
201                     ? mContext
202                     : mContext.createDisplayContext(display);
203             final DisplayRecord record = new DisplayRecord(displayId);
204             DisplayLayout displayLayout = new DisplayLayout(context, display);
205             if (Flags.enableConnectedDisplaysWindowDrag()
206                     && mUnpopulatedDisplayBounds.containsKey(displayId)) {
207                 displayLayout.setGlobalBoundsDp(mUnpopulatedDisplayBounds.get(displayId));
208             }
209             record.setDisplayLayout(context, displayLayout);
210             mDisplays.put(displayId, record);
211             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
212                 mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
213             }
214         }
215     }
216 
217 
218     /** Called when a display rotate requested. */
onDisplayChangeRequested(WindowContainerTransaction wct, int displayId, Rect startAbsBounds, Rect endAbsBounds, int fromRotation, int toRotation)219     public void onDisplayChangeRequested(WindowContainerTransaction wct, int displayId,
220             Rect startAbsBounds, Rect endAbsBounds, int fromRotation, int toRotation) {
221         synchronized (mDisplays) {
222             final DisplayRecord dr = mDisplays.get(displayId);
223             if (dr == null) {
224                 Slog.w(TAG, "Skipping Display rotate on non-added display.");
225                 return;
226             }
227 
228             if (dr.mDisplayLayout != null) {
229                 if (endAbsBounds != null) {
230                     // If there is a change in the display dimensions update the layout as well;
231                     // note that endAbsBounds should ignore any potential rotation changes, so
232                     // we still need to rotate the layout after if needed.
233                     dr.mDisplayLayout.resizeTo(dr.mContext.getResources(),
234                             new Size(endAbsBounds.width(), endAbsBounds.height()));
235                 }
236                 if (fromRotation != toRotation) {
237                     dr.mDisplayLayout.rotateTo(dr.mContext.getResources(), toRotation);
238                 }
239             }
240 
241             mChangeController.dispatchOnDisplayChange(
242                     wct, displayId, fromRotation, toRotation, null /* newDisplayAreaInfo */);
243         }
244     }
245 
onDisplayTopologyChanged(DisplayTopology topology)246     private void onDisplayTopologyChanged(DisplayTopology topology) {
247         if (topology == null) {
248             return;
249         }
250         mDisplayTopology = topology;
251         SparseArray<RectF> absoluteBounds = topology.getAbsoluteBounds();
252         mUnpopulatedDisplayBounds.clear();
253         for (int i = 0; i < absoluteBounds.size(); ++i) {
254             int displayId = absoluteBounds.keyAt(i);
255             DisplayLayout displayLayout = getDisplayLayout(displayId);
256             if (displayLayout == null) {
257                 // onDisplayTopologyChanged can arrive before onDisplayAdded.
258                 // Store the bounds to be applied later in onDisplayAdded.
259                 Slog.d(TAG, "Storing bounds for onDisplayTopologyChanged on unknown"
260                         + " display, displayId=" + displayId);
261                 mUnpopulatedDisplayBounds.put(displayId, absoluteBounds.valueAt(i));
262             } else {
263                 displayLayout.setGlobalBoundsDp(absoluteBounds.valueAt(i));
264             }
265         }
266 
267         for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
268             mDisplayChangedListeners.get(i).onTopologyChanged(topology);
269         }
270     }
271 
onDisplayConfigurationChanged(int displayId, Configuration newConfig)272     private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
273         synchronized (mDisplays) {
274             final DisplayRecord dr = mDisplays.get(displayId);
275             if (dr == null) {
276                 Slog.w(TAG, "Skipping Display Configuration change on non-added"
277                         + " display.");
278                 return;
279             }
280             final Display display = getDisplay(displayId);
281             if (display == null) {
282                 Slog.w(TAG, "Skipping Display Configuration change on invalid"
283                         + " display. It may have been removed.");
284                 return;
285             }
286             final Context perDisplayContext = (displayId == Display.DEFAULT_DISPLAY)
287                     ? mContext
288                     : mContext.createDisplayContext(display);
289             final Context context = perDisplayContext.createConfigurationContext(newConfig);
290             final DisplayLayout displayLayout = new DisplayLayout(context, display);
291             if (mDisplayTopology != null) {
292                 displayLayout.setGlobalBoundsDp(
293                         mDisplayTopology.getAbsoluteBounds().get(
294                                 displayId, displayLayout.globalBoundsDp()));
295             }
296             dr.setDisplayLayout(context, displayLayout);
297             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
298                 mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
299                         displayId, newConfig);
300             }
301         }
302     }
303 
onDisplayRemoved(int displayId)304     private void onDisplayRemoved(int displayId) {
305         synchronized (mDisplays) {
306             if (mDisplays.get(displayId) == null) {
307                 return;
308             }
309             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
310                 mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
311             }
312             mDisplays.remove(displayId);
313         }
314     }
315 
onFixedRotationStarted(int displayId, int newRotation)316     private void onFixedRotationStarted(int displayId, int newRotation) {
317         synchronized (mDisplays) {
318             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
319                 Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
320                         + " display, displayId=" + displayId);
321                 return;
322             }
323             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
324                 mDisplayChangedListeners.get(i).onFixedRotationStarted(
325                         displayId, newRotation);
326             }
327         }
328     }
329 
onFixedRotationFinished(int displayId)330     private void onFixedRotationFinished(int displayId) {
331         synchronized (mDisplays) {
332             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
333                 Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
334                         + " display, displayId=" + displayId);
335                 return;
336             }
337             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
338                 mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
339             }
340         }
341     }
342 
onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted)343     private void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
344             Set<Rect> unrestricted) {
345         synchronized (mDisplays) {
346             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
347                 Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
348                         + " display, displayId=" + displayId);
349                 return;
350             }
351             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
352                 mDisplayChangedListeners.get(i)
353                     .onKeepClearAreasChanged(displayId, restricted, unrestricted);
354             }
355         }
356     }
357 
onDesktopModeEligibleChanged(int displayId)358     private void onDesktopModeEligibleChanged(int displayId) {
359         synchronized (mDisplays) {
360             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
361                 Slog.w(TAG, "Skipping onDesktopModeEligibleChanged on unknown"
362                         + " display, displayId=" + displayId);
363                 return;
364             }
365             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
366                 mDisplayChangedListeners.get(i).onDesktopModeEligibleChanged(displayId);
367             }
368         }
369     }
370 
371     private static class DisplayRecord {
372         private int mDisplayId;
373         private Context mContext;
374         private DisplayLayout mDisplayLayout;
375         private InsetsState mInsetsState = new InsetsState();
376 
DisplayRecord(int displayId)377         private DisplayRecord(int displayId) {
378             mDisplayId = displayId;
379         }
380 
setDisplayLayout(Context context, DisplayLayout displayLayout)381         private void setDisplayLayout(Context context, DisplayLayout displayLayout) {
382             mContext = context;
383             mDisplayLayout = displayLayout;
384             mDisplayLayout.setInsets(mContext.getResources(), mInsetsState);
385         }
386 
setInsets(InsetsState state)387         private void setInsets(InsetsState state) {
388             mInsetsState = state;
389             mDisplayLayout.setInsets(mContext.getResources(), state);
390         }
391     }
392 
393     @BinderThread
394     private class DisplayWindowListenerImpl extends IDisplayWindowListener.Stub {
395         @Override
onDisplayAdded(int displayId)396         public void onDisplayAdded(int displayId) {
397             mMainExecutor.execute(() -> {
398                 DisplayController.this.onDisplayAdded(displayId);
399             });
400         }
401 
402         @Override
onDisplayConfigurationChanged(int displayId, Configuration newConfig)403         public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
404             mMainExecutor.execute(() -> {
405                 DisplayController.this.onDisplayConfigurationChanged(displayId, newConfig);
406             });
407         }
408 
409         @Override
onDisplayRemoved(int displayId)410         public void onDisplayRemoved(int displayId) {
411             mMainExecutor.execute(() -> {
412                 DisplayController.this.onDisplayRemoved(displayId);
413             });
414         }
415 
416         @Override
onFixedRotationStarted(int displayId, int newRotation)417         public void onFixedRotationStarted(int displayId, int newRotation) {
418             mMainExecutor.execute(() -> {
419                 DisplayController.this.onFixedRotationStarted(displayId, newRotation);
420             });
421         }
422 
423         @Override
onFixedRotationFinished(int displayId)424         public void onFixedRotationFinished(int displayId) {
425             mMainExecutor.execute(() -> {
426                 DisplayController.this.onFixedRotationFinished(displayId);
427             });
428         }
429 
430         @Override
onKeepClearAreasChanged(int displayId, List<Rect> restricted, List<Rect> unrestricted)431         public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
432                 List<Rect> unrestricted) {
433             mMainExecutor.execute(() -> {
434                 DisplayController.this.onKeepClearAreasChanged(displayId,
435                         new ArraySet<>(restricted), new ArraySet<>(unrestricted));
436             });
437         }
438 
439         @Override
onDesktopModeEligibleChanged(int displayId)440         public void onDesktopModeEligibleChanged(int displayId) {
441             mMainExecutor.execute(() -> {
442                 DisplayController.this.onDesktopModeEligibleChanged(displayId);
443             });
444         }
445     }
446 
447     /**
448      * Gets notified when a display is added/removed to the WM hierarchy and when a display's
449      * window-configuration changes.
450      *
451      * @see IDisplayWindowListener
452      */
453     @ShellMainThread
454     public interface OnDisplaysChangedListener {
455         /**
456          * Called when a display has been added to the WM hierarchy.
457          */
onDisplayAdded(int displayId)458         default void onDisplayAdded(int displayId) {}
459 
460         /**
461          * Called when a display's window-container configuration changes.
462          */
onDisplayConfigurationChanged(int displayId, Configuration newConfig)463         default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {}
464 
465         /**
466          * Called when a display is removed.
467          */
onDisplayRemoved(int displayId)468         default void onDisplayRemoved(int displayId) {}
469 
470         /**
471          * Called when fixed rotation on a display is started.
472          */
onFixedRotationStarted(int displayId, int newRotation)473         default void onFixedRotationStarted(int displayId, int newRotation) {}
474 
475         /**
476          * Called when fixed rotation on a display is finished.
477          */
onFixedRotationFinished(int displayId)478         default void onFixedRotationFinished(int displayId) {}
479 
480         /**
481          * Called when keep-clear areas on a display have changed.
482          */
onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted)483         default void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
484                 Set<Rect> unrestricted) {}
485 
486         /**
487          * Called when the display topology has changed.
488          */
onTopologyChanged(DisplayTopology topology)489         default void onTopologyChanged(DisplayTopology topology) {}
490 
491         /**
492          * Called when the eligibility of the desktop mode for a display have changed.
493          */
onDesktopModeEligibleChanged(int displayId)494         default void onDesktopModeEligibleChanged(int displayId) {}
495     }
496 }
497