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