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