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