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.hardware.display.DisplayManager;
24 import android.os.RemoteException;
25 import android.util.ArraySet;
26 import android.util.Slog;
27 import android.util.SparseArray;
28 import android.view.Display;
29 import android.view.IDisplayWindowListener;
30 import android.view.IWindowManager;
31 import android.view.InsetsState;
32 import android.window.WindowContainerTransaction;
33 
34 import androidx.annotation.BinderThread;
35 
36 import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
37 import com.android.wm.shell.shared.annotations.ShellMainThread;
38 import com.android.wm.shell.sysui.ShellInit;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Set;
43 
44 /**
45  * This module deals with display rotations coming from WM. When WM starts a rotation: after it has
46  * frozen the screen, it will call into this class. This will then call all registered local
47  * controllers and give them a chance to queue up task changes to be applied synchronously with that
48  * rotation.
49  */
50 public class DisplayController {
51     private static final String TAG = "DisplayController";
52 
53     private final ShellExecutor mMainExecutor;
54     private final Context mContext;
55     private final IWindowManager mWmService;
56     private final DisplayChangeController mChangeController;
57     private final IDisplayWindowListener mDisplayContainerListener;
58 
59     private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
60     private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
61 
DisplayController(Context context, IWindowManager wmService, ShellInit shellInit, ShellExecutor mainExecutor)62     public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
63             ShellExecutor mainExecutor) {
64         mMainExecutor = mainExecutor;
65         mContext = context;
66         mWmService = wmService;
67         // TODO: Inject this instead
68         mChangeController = new DisplayChangeController(mWmService, shellInit, mainExecutor);
69         mDisplayContainerListener = new DisplayWindowListenerImpl();
70         // Note, add this after DisplaceChangeController is constructed to ensure that is
71         // initialized first
72         shellInit.addInitCallback(this::onInit, this);
73     }
74 
75     /**
76      * Initializes the window listener.
77      */
onInit()78     public void onInit() {
79         try {
80             int[] displayIds = mWmService.registerDisplayWindowListener(mDisplayContainerListener);
81             for (int i = 0; i < displayIds.length; i++) {
82                 onDisplayAdded(displayIds[i]);
83             }
84         } catch (RemoteException e) {
85             throw new RuntimeException("Unable to register display controller");
86         }
87     }
88 
89     /**
90      * Gets a display by id from DisplayManager.
91      */
getDisplay(int displayId)92     public Display getDisplay(int displayId) {
93         final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
94         return displayManager.getDisplay(displayId);
95     }
96 
97     /**
98      * Gets the DisplayLayout associated with a display.
99      */
getDisplayLayout(int displayId)100     public @Nullable DisplayLayout getDisplayLayout(int displayId) {
101         final DisplayRecord r = mDisplays.get(displayId);
102         return r != null ? r.mDisplayLayout : null;
103     }
104 
105     /**
106      * Gets a display-specific context for a display.
107      */
getDisplayContext(int displayId)108     public @Nullable Context getDisplayContext(int displayId) {
109         final DisplayRecord r = mDisplays.get(displayId);
110         return r != null ? r.mContext : null;
111     }
112 
113     /**
114      *  Get the InsetsState of a display.
115      */
getInsetsState(int displayId)116     public InsetsState getInsetsState(int displayId) {
117         final DisplayRecord r = mDisplays.get(displayId);
118         return r != null ? r.mInsetsState : null;
119     }
120 
121     /**
122      * Updates the insets for a given display.
123      */
updateDisplayInsets(int displayId, InsetsState state)124     public void updateDisplayInsets(int displayId, InsetsState state) {
125         final DisplayRecord r = mDisplays.get(displayId);
126         if (r != null) {
127             r.setInsets(state);
128         }
129     }
130 
131     /**
132      * Add a display window-container listener. It will get notified whenever a display's
133      * configuration changes or when displays are added/removed from the WM hierarchy.
134      */
addDisplayWindowListener(OnDisplaysChangedListener listener)135     public void addDisplayWindowListener(OnDisplaysChangedListener listener) {
136         synchronized (mDisplays) {
137             if (mDisplayChangedListeners.contains(listener)) {
138                 return;
139             }
140             mDisplayChangedListeners.add(listener);
141             for (int i = 0; i < mDisplays.size(); ++i) {
142                 listener.onDisplayAdded(mDisplays.keyAt(i));
143             }
144         }
145     }
146 
147     /**
148      * Remove a display window-container listener.
149      */
removeDisplayWindowListener(OnDisplaysChangedListener listener)150     public void removeDisplayWindowListener(OnDisplaysChangedListener listener) {
151         synchronized (mDisplays) {
152             mDisplayChangedListeners.remove(listener);
153         }
154     }
155 
156     /**
157      * Adds a display rotation controller.
158      */
addDisplayChangingController(OnDisplayChangingListener controller)159     public void addDisplayChangingController(OnDisplayChangingListener controller) {
160         mChangeController.addDisplayChangeListener(controller);
161     }
162 
163     /**
164      * Removes a display rotation controller.
165      */
removeDisplayChangingController(OnDisplayChangingListener controller)166     public void removeDisplayChangingController(OnDisplayChangingListener controller) {
167         mChangeController.removeDisplayChangeListener(controller);
168     }
169 
onDisplayAdded(int displayId)170     private void onDisplayAdded(int displayId) {
171         synchronized (mDisplays) {
172             if (mDisplays.get(displayId) != null) {
173                 return;
174             }
175             final Display display = getDisplay(displayId);
176             if (display == null) {
177                 // It's likely that the display is private to some app and thus not
178                 // accessible by system-ui.
179                 return;
180             }
181 
182             final Context context = (displayId == Display.DEFAULT_DISPLAY)
183                     ? mContext
184                     : mContext.createDisplayContext(display);
185             final DisplayRecord record = new DisplayRecord(displayId);
186             record.setDisplayLayout(context, new DisplayLayout(context, display));
187             mDisplays.put(displayId, record);
188             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
189                 mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
190             }
191         }
192     }
193 
194 
195     /** Called when a display rotate requested. */
onDisplayRotateRequested(WindowContainerTransaction wct, int displayId, int fromRotation, int toRotation)196     public void onDisplayRotateRequested(WindowContainerTransaction wct, int displayId,
197             int fromRotation, int toRotation) {
198         synchronized (mDisplays) {
199             final DisplayRecord dr = mDisplays.get(displayId);
200             if (dr == null) {
201                 Slog.w(TAG, "Skipping Display rotate on non-added display.");
202                 return;
203             }
204 
205             if (dr.mDisplayLayout != null) {
206                 dr.mDisplayLayout.rotateTo(dr.mContext.getResources(), toRotation);
207             }
208 
209             mChangeController.dispatchOnDisplayChange(
210                     wct, displayId, fromRotation, toRotation, null /* newDisplayAreaInfo */);
211         }
212     }
213 
onDisplayConfigurationChanged(int displayId, Configuration newConfig)214     private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
215         synchronized (mDisplays) {
216             final DisplayRecord dr = mDisplays.get(displayId);
217             if (dr == null) {
218                 Slog.w(TAG, "Skipping Display Configuration change on non-added"
219                         + " display.");
220                 return;
221             }
222             final Display display = getDisplay(displayId);
223             if (display == null) {
224                 Slog.w(TAG, "Skipping Display Configuration change on invalid"
225                         + " display. It may have been removed.");
226                 return;
227             }
228             final Context perDisplayContext = (displayId == Display.DEFAULT_DISPLAY)
229                     ? mContext
230                     : mContext.createDisplayContext(display);
231             final Context context = perDisplayContext.createConfigurationContext(newConfig);
232             dr.setDisplayLayout(context, new DisplayLayout(context, display));
233             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
234                 mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
235                         displayId, newConfig);
236             }
237         }
238     }
239 
onDisplayRemoved(int displayId)240     private void onDisplayRemoved(int displayId) {
241         synchronized (mDisplays) {
242             if (mDisplays.get(displayId) == null) {
243                 return;
244             }
245             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
246                 mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
247             }
248             mDisplays.remove(displayId);
249         }
250     }
251 
onFixedRotationStarted(int displayId, int newRotation)252     private void onFixedRotationStarted(int displayId, int newRotation) {
253         synchronized (mDisplays) {
254             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
255                 Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
256                         + " display, displayId=" + displayId);
257                 return;
258             }
259             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
260                 mDisplayChangedListeners.get(i).onFixedRotationStarted(
261                         displayId, newRotation);
262             }
263         }
264     }
265 
onFixedRotationFinished(int displayId)266     private void onFixedRotationFinished(int displayId) {
267         synchronized (mDisplays) {
268             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
269                 Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
270                         + " display, displayId=" + displayId);
271                 return;
272             }
273             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
274                 mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
275             }
276         }
277     }
278 
onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted)279     private void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
280             Set<Rect> unrestricted) {
281         synchronized (mDisplays) {
282             if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
283                 Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
284                         + " display, displayId=" + displayId);
285                 return;
286             }
287             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
288                 mDisplayChangedListeners.get(i)
289                     .onKeepClearAreasChanged(displayId, restricted, unrestricted);
290             }
291         }
292     }
293 
294     private static class DisplayRecord {
295         private int mDisplayId;
296         private Context mContext;
297         private DisplayLayout mDisplayLayout;
298         private InsetsState mInsetsState = new InsetsState();
299 
DisplayRecord(int displayId)300         private DisplayRecord(int displayId) {
301             mDisplayId = displayId;
302         }
303 
setDisplayLayout(Context context, DisplayLayout displayLayout)304         private void setDisplayLayout(Context context, DisplayLayout displayLayout) {
305             mContext = context;
306             mDisplayLayout = displayLayout;
307             mDisplayLayout.setInsets(mContext.getResources(), mInsetsState);
308         }
309 
setInsets(InsetsState state)310         private void setInsets(InsetsState state) {
311             mInsetsState = state;
312             mDisplayLayout.setInsets(mContext.getResources(), state);
313         }
314     }
315 
316     @BinderThread
317     private class DisplayWindowListenerImpl extends IDisplayWindowListener.Stub {
318         @Override
onDisplayAdded(int displayId)319         public void onDisplayAdded(int displayId) {
320             mMainExecutor.execute(() -> {
321                 DisplayController.this.onDisplayAdded(displayId);
322             });
323         }
324 
325         @Override
onDisplayConfigurationChanged(int displayId, Configuration newConfig)326         public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
327             mMainExecutor.execute(() -> {
328                 DisplayController.this.onDisplayConfigurationChanged(displayId, newConfig);
329             });
330         }
331 
332         @Override
onDisplayRemoved(int displayId)333         public void onDisplayRemoved(int displayId) {
334             mMainExecutor.execute(() -> {
335                 DisplayController.this.onDisplayRemoved(displayId);
336             });
337         }
338 
339         @Override
onFixedRotationStarted(int displayId, int newRotation)340         public void onFixedRotationStarted(int displayId, int newRotation) {
341             mMainExecutor.execute(() -> {
342                 DisplayController.this.onFixedRotationStarted(displayId, newRotation);
343             });
344         }
345 
346         @Override
onFixedRotationFinished(int displayId)347         public void onFixedRotationFinished(int displayId) {
348             mMainExecutor.execute(() -> {
349                 DisplayController.this.onFixedRotationFinished(displayId);
350             });
351         }
352 
353         @Override
onKeepClearAreasChanged(int displayId, List<Rect> restricted, List<Rect> unrestricted)354         public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
355                 List<Rect> unrestricted) {
356             mMainExecutor.execute(() -> {
357                 DisplayController.this.onKeepClearAreasChanged(displayId,
358                         new ArraySet<>(restricted), new ArraySet<>(unrestricted));
359             });
360         }
361     }
362 
363     /**
364      * Gets notified when a display is added/removed to the WM hierarchy and when a display's
365      * window-configuration changes.
366      *
367      * @see IDisplayWindowListener
368      */
369     @ShellMainThread
370     public interface OnDisplaysChangedListener {
371         /**
372          * Called when a display has been added to the WM hierarchy.
373          */
onDisplayAdded(int displayId)374         default void onDisplayAdded(int displayId) {}
375 
376         /**
377          * Called when a display's window-container configuration changes.
378          */
onDisplayConfigurationChanged(int displayId, Configuration newConfig)379         default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {}
380 
381         /**
382          * Called when a display is removed.
383          */
onDisplayRemoved(int displayId)384         default void onDisplayRemoved(int displayId) {}
385 
386         /**
387          * Called when fixed rotation on a display is started.
388          */
onFixedRotationStarted(int displayId, int newRotation)389         default void onFixedRotationStarted(int displayId, int newRotation) {}
390 
391         /**
392          * Called when fixed rotation on a display is finished.
393          */
onFixedRotationFinished(int displayId)394         default void onFixedRotationFinished(int displayId) {}
395 
396         /**
397          * Called when keep-clear areas on a display have changed.
398          */
onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted)399         default void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
400                 Set<Rect> unrestricted) {}
401     }
402 }
403