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