1 /* 2 * Copyright (C) 2021 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.compatui; 18 19 import android.annotation.Nullable; 20 import android.app.TaskInfo; 21 import android.app.TaskInfo.CameraCompatControlState; 22 import android.content.Context; 23 import android.content.res.Configuration; 24 import android.hardware.display.DisplayManager; 25 import android.util.ArraySet; 26 import android.util.Log; 27 import android.util.Pair; 28 import android.util.SparseArray; 29 import android.view.Display; 30 import android.view.InsetsSourceControl; 31 import android.view.InsetsState; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.wm.shell.ShellTaskOrganizer; 35 import com.android.wm.shell.common.DisplayController; 36 import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener; 37 import com.android.wm.shell.common.DisplayImeController; 38 import com.android.wm.shell.common.DisplayInsetsController; 39 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; 40 import com.android.wm.shell.common.DisplayLayout; 41 import com.android.wm.shell.common.DockStateReader; 42 import com.android.wm.shell.common.ShellExecutor; 43 import com.android.wm.shell.common.SyncTransactionQueue; 44 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; 45 import com.android.wm.shell.sysui.KeyguardChangeListener; 46 import com.android.wm.shell.sysui.ShellController; 47 import com.android.wm.shell.sysui.ShellInit; 48 import com.android.wm.shell.transition.Transitions; 49 50 import java.lang.ref.WeakReference; 51 import java.util.ArrayList; 52 import java.util.HashSet; 53 import java.util.List; 54 import java.util.Set; 55 import java.util.function.Consumer; 56 import java.util.function.Predicate; 57 58 import dagger.Lazy; 59 60 /** 61 * Controller to show/update compat UI components on Tasks based on whether the foreground 62 * activities are in compatibility mode. 63 */ 64 public class CompatUIController implements OnDisplaysChangedListener, 65 DisplayImeController.ImePositionProcessor, KeyguardChangeListener { 66 67 /** Callback for compat UI interaction. */ 68 public interface CompatUICallback { 69 /** Called when the size compat restart button appears. */ onSizeCompatRestartButtonAppeared(int taskId)70 void onSizeCompatRestartButtonAppeared(int taskId); 71 /** Called when the size compat restart button is clicked. */ onSizeCompatRestartButtonClicked(int taskId)72 void onSizeCompatRestartButtonClicked(int taskId); 73 /** Called when the camera compat control state is updated. */ onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state)74 void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state); 75 } 76 77 private static final String TAG = "CompatUIController"; 78 79 /** Whether the IME is shown on display id. */ 80 private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1); 81 82 /** {@link PerDisplayOnInsetsChangedListener} by display id. */ 83 private final SparseArray<PerDisplayOnInsetsChangedListener> mOnInsetsChangedListeners = 84 new SparseArray<>(0); 85 86 /** 87 * The active Compat Control UI layouts by task id. 88 * 89 * <p>An active layout is a layout that is eligible to be shown for the associated task but 90 * isn't necessarily shown at a given time. 91 */ 92 private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0); 93 94 /** 95 * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are 96 * currently visible 97 */ 98 private final SparseArray<RestartDialogWindowManager> mTaskIdToRestartDialogWindowManagerMap = 99 new SparseArray<>(0); 100 101 /** 102 * {@link Set} of task ids for which we need to display a restart confirmation dialog 103 */ 104 private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>(); 105 106 /** 107 * The active Letterbox Education layout if there is one (there can be at most one active). 108 * 109 * <p>An active layout is a layout that is eligible to be shown for the associated task but 110 * isn't necessarily shown at a given time. 111 */ 112 @Nullable 113 private LetterboxEduWindowManager mActiveLetterboxEduLayout; 114 115 /** 116 * The active Reachability UI layout. 117 */ 118 @Nullable 119 private ReachabilityEduWindowManager mActiveReachabilityEduLayout; 120 121 /** Avoid creating display context frequently for non-default display. */ 122 private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); 123 124 private final Context mContext; 125 private final ShellController mShellController; 126 private final DisplayController mDisplayController; 127 private final DisplayInsetsController mDisplayInsetsController; 128 private final DisplayImeController mImeController; 129 private final SyncTransactionQueue mSyncQueue; 130 private final ShellExecutor mMainExecutor; 131 private final Lazy<Transitions> mTransitionsLazy; 132 private final DockStateReader mDockStateReader; 133 private final CompatUIConfiguration mCompatUIConfiguration; 134 // Only show each hint once automatically in the process life. 135 private final CompatUIHintsState mCompatUIHintsState; 136 private final CompatUIShellCommandHandler mCompatUIShellCommandHandler; 137 138 private CompatUICallback mCallback; 139 140 // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't 141 // be shown. 142 private boolean mKeyguardShowing; 143 CompatUIController(Context context, ShellInit shellInit, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue, ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration, CompatUIShellCommandHandler compatUIShellCommandHandler)144 public CompatUIController(Context context, 145 ShellInit shellInit, 146 ShellController shellController, 147 DisplayController displayController, 148 DisplayInsetsController displayInsetsController, 149 DisplayImeController imeController, 150 SyncTransactionQueue syncQueue, 151 ShellExecutor mainExecutor, 152 Lazy<Transitions> transitionsLazy, 153 DockStateReader dockStateReader, 154 CompatUIConfiguration compatUIConfiguration, 155 CompatUIShellCommandHandler compatUIShellCommandHandler) { 156 mContext = context; 157 mShellController = shellController; 158 mDisplayController = displayController; 159 mDisplayInsetsController = displayInsetsController; 160 mImeController = imeController; 161 mSyncQueue = syncQueue; 162 mMainExecutor = mainExecutor; 163 mTransitionsLazy = transitionsLazy; 164 mCompatUIHintsState = new CompatUIHintsState(); 165 mDockStateReader = dockStateReader; 166 mCompatUIConfiguration = compatUIConfiguration; 167 mCompatUIShellCommandHandler = compatUIShellCommandHandler; 168 shellInit.addInitCallback(this::onInit, this); 169 } 170 onInit()171 private void onInit() { 172 mShellController.addKeyguardChangeListener(this); 173 mDisplayController.addDisplayWindowListener(this); 174 mImeController.addPositionProcessor(this); 175 mCompatUIShellCommandHandler.onInit(); 176 } 177 178 /** Sets the callback for UI interactions. */ setCompatUICallback(CompatUICallback callback)179 public void setCompatUICallback(CompatUICallback callback) { 180 mCallback = callback; 181 } 182 183 /** 184 * Called when the Task info changed. Creates and updates the compat UI if there is an 185 * activity in size compat, or removes the UI if there is no size compat activity. 186 * 187 * @param taskInfo {@link TaskInfo} task the activity is in. 188 * @param taskListener listener to handle the Task Surface placement. 189 */ onCompatInfoChanged(TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener)190 public void onCompatInfoChanged(TaskInfo taskInfo, 191 @Nullable ShellTaskOrganizer.TaskListener taskListener) { 192 if (taskInfo != null && !taskInfo.topActivityInSizeCompat) { 193 mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); 194 } 195 if (taskInfo.configuration == null || taskListener == null) { 196 // Null token means the current foreground activity is not in compatibility mode. 197 removeLayouts(taskInfo.taskId); 198 return; 199 } 200 201 createOrUpdateCompatLayout(taskInfo, taskListener); 202 createOrUpdateLetterboxEduLayout(taskInfo, taskListener); 203 createOrUpdateRestartDialogLayout(taskInfo, taskListener); 204 if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { 205 createOrUpdateReachabilityEduLayout(taskInfo, taskListener); 206 } 207 } 208 209 @Override onDisplayAdded(int displayId)210 public void onDisplayAdded(int displayId) { 211 addOnInsetsChangedListener(displayId); 212 } 213 214 @Override onDisplayRemoved(int displayId)215 public void onDisplayRemoved(int displayId) { 216 mDisplayContextCache.remove(displayId); 217 removeOnInsetsChangedListener(displayId); 218 219 // Remove all compat UIs on the removed display. 220 final List<Integer> toRemoveTaskIds = new ArrayList<>(); 221 forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId())); 222 for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) { 223 removeLayouts(toRemoveTaskIds.get(i)); 224 } 225 } 226 addOnInsetsChangedListener(int displayId)227 private void addOnInsetsChangedListener(int displayId) { 228 PerDisplayOnInsetsChangedListener listener = new PerDisplayOnInsetsChangedListener( 229 displayId); 230 listener.register(); 231 mOnInsetsChangedListeners.put(displayId, listener); 232 } 233 removeOnInsetsChangedListener(int displayId)234 private void removeOnInsetsChangedListener(int displayId) { 235 PerDisplayOnInsetsChangedListener listener = mOnInsetsChangedListeners.get(displayId); 236 if (listener == null) { 237 return; 238 } 239 listener.unregister(); 240 mOnInsetsChangedListeners.remove(displayId); 241 } 242 243 244 @Override onDisplayConfigurationChanged(int displayId, Configuration newConfig)245 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { 246 updateDisplayLayout(displayId); 247 } 248 updateDisplayLayout(int displayId)249 private void updateDisplayLayout(int displayId) { 250 final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); 251 forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout)); 252 } 253 254 @Override onImeVisibilityChanged(int displayId, boolean isShowing)255 public void onImeVisibilityChanged(int displayId, boolean isShowing) { 256 if (isShowing) { 257 mDisplaysWithIme.add(displayId); 258 } else { 259 mDisplaysWithIme.remove(displayId); 260 } 261 262 // Hide the compat UIs when input method is showing. 263 forAllLayoutsOnDisplay(displayId, 264 layout -> layout.updateVisibility(showOnDisplay(displayId))); 265 } 266 267 @Override onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss)268 public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, 269 boolean animatingDismiss) { 270 mKeyguardShowing = visible; 271 // Hide the compat UIs when keyguard is showing. 272 forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId()))); 273 } 274 showOnDisplay(int displayId)275 private boolean showOnDisplay(int displayId) { 276 return !mKeyguardShowing && !isImeShowingOnDisplay(displayId); 277 } 278 isImeShowingOnDisplay(int displayId)279 private boolean isImeShowingOnDisplay(int displayId) { 280 return mDisplaysWithIme.contains(displayId); 281 } 282 createOrUpdateCompatLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)283 private void createOrUpdateCompatLayout(TaskInfo taskInfo, 284 ShellTaskOrganizer.TaskListener taskListener) { 285 CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId); 286 if (layout != null) { 287 // UI already exists, update the UI layout. 288 if (!layout.updateCompatInfo(taskInfo, taskListener, 289 showOnDisplay(layout.getDisplayId()))) { 290 // The layout is no longer eligible to be shown, remove from active layouts. 291 mActiveCompatLayouts.remove(taskInfo.taskId); 292 } 293 return; 294 } 295 296 // Create a new UI layout. 297 final Context context = getOrCreateDisplayContext(taskInfo.displayId); 298 if (context == null) { 299 return; 300 } 301 layout = createCompatUiWindowManager(context, taskInfo, taskListener); 302 if (layout.createLayout(showOnDisplay(taskInfo.displayId))) { 303 // The new layout is eligible to be shown, add it the active layouts. 304 mActiveCompatLayouts.put(taskInfo.taskId, layout); 305 } 306 } 307 308 @VisibleForTesting createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)309 CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, 310 ShellTaskOrganizer.TaskListener taskListener) { 311 return new CompatUIWindowManager(context, 312 taskInfo, mSyncQueue, mCallback, taskListener, 313 mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, 314 mCompatUIConfiguration, this::onRestartButtonClicked); 315 } 316 onRestartButtonClicked( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState)317 private void onRestartButtonClicked( 318 Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) { 319 if (mCompatUIConfiguration.isRestartDialogEnabled() 320 && mCompatUIConfiguration.shouldShowRestartDialogAgain( 321 taskInfoState.first)) { 322 // We need to show the dialog 323 mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId); 324 onCompatInfoChanged(taskInfoState.first, taskInfoState.second); 325 } else { 326 mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); 327 } 328 } 329 createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)330 private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, 331 ShellTaskOrganizer.TaskListener taskListener) { 332 if (mActiveLetterboxEduLayout != null 333 && mActiveLetterboxEduLayout.getTaskId() == taskInfo.taskId) { 334 // UI already exists, update the UI layout. 335 if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener, 336 showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) { 337 // The layout is no longer eligible to be shown, clear active layout. 338 mActiveLetterboxEduLayout = null; 339 } 340 return; 341 } 342 343 // Create a new UI layout. 344 final Context context = getOrCreateDisplayContext(taskInfo.displayId); 345 if (context == null) { 346 return; 347 } 348 LetterboxEduWindowManager newLayout = createLetterboxEduWindowManager(context, taskInfo, 349 taskListener); 350 if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) { 351 // The new layout is eligible to be shown, make it the active layout. 352 if (mActiveLetterboxEduLayout != null) { 353 // Release the previous layout since at most one can be active. 354 // Since letterbox education is only shown once to the user, releasing the previous 355 // layout is only a precaution. 356 mActiveLetterboxEduLayout.release(); 357 } 358 mActiveLetterboxEduLayout = newLayout; 359 } 360 } 361 362 @VisibleForTesting createLetterboxEduWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)363 LetterboxEduWindowManager createLetterboxEduWindowManager(Context context, TaskInfo taskInfo, 364 ShellTaskOrganizer.TaskListener taskListener) { 365 return new LetterboxEduWindowManager(context, taskInfo, 366 mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), 367 mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader, 368 mCompatUIConfiguration); 369 } 370 onLetterboxEduDismissed( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo)371 private void onLetterboxEduDismissed( 372 Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { 373 mActiveLetterboxEduLayout = null; 374 // We need to update the UI 375 createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second); 376 } 377 createOrUpdateRestartDialogLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)378 private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo, 379 ShellTaskOrganizer.TaskListener taskListener) { 380 RestartDialogWindowManager layout = 381 mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); 382 if (layout != null) { 383 if (layout.needsToBeRecreated(taskInfo, taskListener)) { 384 mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId); 385 layout.release(); 386 } else { 387 layout.setRequestRestartDialog( 388 mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId)); 389 // UI already exists, update the UI layout. 390 if (!layout.updateCompatInfo(taskInfo, taskListener, 391 showOnDisplay(layout.getDisplayId()))) { 392 // The layout is no longer eligible to be shown, remove from active layouts. 393 mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId); 394 } 395 return; 396 } 397 } 398 // Create a new UI layout. 399 final Context context = getOrCreateDisplayContext(taskInfo.displayId); 400 if (context == null) { 401 return; 402 } 403 layout = createRestartDialogWindowManager(context, taskInfo, taskListener); 404 layout.setRequestRestartDialog( 405 mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId)); 406 if (layout.createLayout(showOnDisplay(taskInfo.displayId))) { 407 // The new layout is eligible to be shown, add it the active layouts. 408 mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout); 409 } 410 } 411 412 @VisibleForTesting createRestartDialogWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)413 RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo, 414 ShellTaskOrganizer.TaskListener taskListener) { 415 return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener, 416 mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(), 417 this::onRestartDialogCallback, this::onRestartDialogDismissCallback, 418 mCompatUIConfiguration); 419 } 420 onRestartDialogCallback( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo)421 private void onRestartDialogCallback( 422 Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { 423 mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId); 424 mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); 425 } 426 onRestartDialogDismissCallback( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo)427 private void onRestartDialogDismissCallback( 428 Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { 429 mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId); 430 onCompatInfoChanged(stateInfo.first, stateInfo.second); 431 } 432 createOrUpdateReachabilityEduLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)433 private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo, 434 ShellTaskOrganizer.TaskListener taskListener) { 435 if (mActiveReachabilityEduLayout != null) { 436 // UI already exists, update the UI layout. 437 if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener, 438 showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) { 439 // The layout is no longer eligible to be shown, remove from active layouts. 440 mActiveReachabilityEduLayout = null; 441 } 442 return; 443 } 444 // Create a new UI layout. 445 final Context context = getOrCreateDisplayContext(taskInfo.displayId); 446 if (context == null) { 447 return; 448 } 449 ReachabilityEduWindowManager newLayout = createReachabilityEduWindowManager(context, 450 taskInfo, taskListener); 451 if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) { 452 // The new layout is eligible to be shown, make it the active layout. 453 if (mActiveReachabilityEduLayout != null) { 454 // Release the previous layout since at most one can be active. 455 // Since letterbox reachability education is only shown once to the user, 456 // releasing the previous layout is only a precaution. 457 mActiveReachabilityEduLayout.release(); 458 } 459 mActiveReachabilityEduLayout = newLayout; 460 } 461 } 462 463 @VisibleForTesting createReachabilityEduWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)464 ReachabilityEduWindowManager createReachabilityEduWindowManager(Context context, 465 TaskInfo taskInfo, 466 ShellTaskOrganizer.TaskListener taskListener) { 467 return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue, 468 taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), 469 mCompatUIConfiguration, mMainExecutor); 470 } 471 472 removeLayouts(int taskId)473 private void removeLayouts(int taskId) { 474 final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId); 475 if (layout != null) { 476 layout.release(); 477 mActiveCompatLayouts.remove(taskId); 478 } 479 480 if (mActiveLetterboxEduLayout != null && mActiveLetterboxEduLayout.getTaskId() == taskId) { 481 mActiveLetterboxEduLayout.release(); 482 mActiveLetterboxEduLayout = null; 483 } 484 485 final RestartDialogWindowManager restartLayout = 486 mTaskIdToRestartDialogWindowManagerMap.get(taskId); 487 if (restartLayout != null) { 488 restartLayout.release(); 489 mTaskIdToRestartDialogWindowManagerMap.remove(taskId); 490 mSetOfTaskIdsShowingRestartDialog.remove(taskId); 491 } 492 if (mActiveReachabilityEduLayout != null 493 && mActiveReachabilityEduLayout.getTaskId() == taskId) { 494 mActiveReachabilityEduLayout.release(); 495 mActiveReachabilityEduLayout = null; 496 } 497 } 498 getOrCreateDisplayContext(int displayId)499 private Context getOrCreateDisplayContext(int displayId) { 500 if (displayId == Display.DEFAULT_DISPLAY) { 501 return mContext; 502 } 503 Context context = null; 504 final WeakReference<Context> ref = mDisplayContextCache.get(displayId); 505 if (ref != null) { 506 context = ref.get(); 507 } 508 if (context == null) { 509 Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId); 510 if (display != null) { 511 context = mContext.createDisplayContext(display); 512 mDisplayContextCache.put(displayId, new WeakReference<>(context)); 513 } else { 514 Log.e(TAG, "Cannot get context for display " + displayId); 515 } 516 } 517 return context; 518 } 519 forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManagerAbstract> callback)520 private void forAllLayoutsOnDisplay(int displayId, 521 Consumer<CompatUIWindowManagerAbstract> callback) { 522 forAllLayouts(layout -> layout.getDisplayId() == displayId, callback); 523 } 524 forAllLayouts(Consumer<CompatUIWindowManagerAbstract> callback)525 private void forAllLayouts(Consumer<CompatUIWindowManagerAbstract> callback) { 526 forAllLayouts(layout -> true, callback); 527 } 528 forAllLayouts(Predicate<CompatUIWindowManagerAbstract> condition, Consumer<CompatUIWindowManagerAbstract> callback)529 private void forAllLayouts(Predicate<CompatUIWindowManagerAbstract> condition, 530 Consumer<CompatUIWindowManagerAbstract> callback) { 531 for (int i = 0; i < mActiveCompatLayouts.size(); i++) { 532 final int taskId = mActiveCompatLayouts.keyAt(i); 533 final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId); 534 if (layout != null && condition.test(layout)) { 535 callback.accept(layout); 536 } 537 } 538 if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) { 539 callback.accept(mActiveLetterboxEduLayout); 540 } 541 for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) { 542 final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i); 543 final RestartDialogWindowManager layout = 544 mTaskIdToRestartDialogWindowManagerMap.get(taskId); 545 if (layout != null && condition.test(layout)) { 546 callback.accept(layout); 547 } 548 } 549 if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) { 550 callback.accept(mActiveReachabilityEduLayout); 551 } 552 } 553 554 /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ 555 private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener { 556 final int mDisplayId; 557 final InsetsState mInsetsState = new InsetsState(); 558 PerDisplayOnInsetsChangedListener(int displayId)559 PerDisplayOnInsetsChangedListener(int displayId) { 560 mDisplayId = displayId; 561 } 562 register()563 void register() { 564 mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this); 565 } 566 unregister()567 void unregister() { 568 mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this); 569 } 570 571 @Override insetsChanged(InsetsState insetsState)572 public void insetsChanged(InsetsState insetsState) { 573 if (mInsetsState.equals(insetsState)) { 574 return; 575 } 576 mInsetsState.set(insetsState); 577 updateDisplayLayout(mDisplayId); 578 } 579 580 @Override insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)581 public void insetsControlChanged(InsetsState insetsState, 582 InsetsSourceControl[] activeControls) { 583 insetsChanged(insetsState); 584 } 585 } 586 } 587