• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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