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