• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.systemui.car.window;
18 
19 import static android.view.WindowInsets.Type.navigationBars;
20 import static android.view.WindowInsets.Type.statusBars;
21 
22 import android.annotation.Nullable;
23 import android.util.Log;
24 import android.view.WindowInsets;
25 import android.view.WindowInsets.Side.InsetsSide;
26 import android.view.WindowInsets.Type.InsetsType;
27 import android.view.WindowInsetsController;
28 
29 import androidx.annotation.VisibleForTesting;
30 
31 import com.android.systemui.dagger.SysUISingleton;
32 
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38 import java.util.SortedMap;
39 import java.util.TreeMap;
40 
41 import javax.inject.Inject;
42 
43 /**
44  * This controller is responsible for the following:
45  * <p><ul>
46  * <li>Holds the global state for SystemUIOverlayWindow.
47  * <li>Allows {@link SystemUIOverlayWindowManager} to register {@link OverlayViewMediator}(s).
48  * <li>Enables {@link OverlayViewController)(s) to reveal/conceal themselves while respecting the
49  * global state of SystemUIOverlayWindow.
50  * </ul>
51  */
52 @SysUISingleton
53 public class OverlayViewGlobalStateController {
54     private static final boolean DEBUG = false;
55     private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
56     private static final int UNKNOWN_Z_ORDER = -1;
57     private final SystemUIOverlayWindowController mSystemUIOverlayWindowController;
58     private final WindowInsetsController mWindowInsetsController;
59     @VisibleForTesting
60     Map<OverlayViewController, Integer> mZOrderMap;
61     @VisibleForTesting
62     SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap;
63     @VisibleForTesting
64     Set<OverlayViewController> mViewsHiddenForOcclusion;
65     @VisibleForTesting
66     OverlayViewController mHighestZOrder;
67     private boolean mIsOccluded;
68 
69     @Inject
OverlayViewGlobalStateController( SystemUIOverlayWindowController systemUIOverlayWindowController)70     public OverlayViewGlobalStateController(
71             SystemUIOverlayWindowController systemUIOverlayWindowController) {
72         mSystemUIOverlayWindowController = systemUIOverlayWindowController;
73         mSystemUIOverlayWindowController.attach();
74         mWindowInsetsController =
75                 mSystemUIOverlayWindowController.getBaseLayout().getWindowInsetsController();
76         mZOrderMap = new HashMap<>();
77         mZOrderVisibleSortedMap = new TreeMap<>();
78         mViewsHiddenForOcclusion = new HashSet<>();
79     }
80 
81     /**
82      * Register {@link OverlayViewMediator} to use in SystemUIOverlayWindow.
83      */
registerMediator(OverlayViewMediator overlayViewMediator)84     public void registerMediator(OverlayViewMediator overlayViewMediator) {
85         Log.d(TAG, "Registering content mediator: " + overlayViewMediator.getClass().getName());
86 
87         overlayViewMediator.registerListeners();
88         overlayViewMediator.setupOverlayContentViewControllers();
89     }
90 
91     /**
92      * Show content in Overlay Window using {@link OverlayPanelViewController}.
93      *
94      * This calls {@link OverlayViewGlobalStateController#showView(OverlayViewController, Runnable)}
95      * where the runnable is nullified since the actual showing of the panel is handled by the
96      * controller itself.
97      */
showView(OverlayPanelViewController panelViewController)98     public void showView(OverlayPanelViewController panelViewController) {
99         showView(panelViewController, /* show= */ null);
100     }
101 
102     /**
103      * Show content in Overlay Window using {@link OverlayViewController}.
104      */
showView(OverlayViewController viewController, @Nullable Runnable show)105     public void showView(OverlayViewController viewController, @Nullable Runnable show) {
106         debugLog();
107         if (mIsOccluded && !viewController.shouldShowWhenOccluded()) {
108             mViewsHiddenForOcclusion.add(viewController);
109             return;
110         }
111         if (mZOrderVisibleSortedMap.isEmpty()) {
112             setWindowVisible(true);
113         }
114 
115         if (!(viewController instanceof OverlayPanelViewController)) {
116             inflateView(viewController);
117         }
118 
119         if (show != null) {
120             show.run();
121         }
122 
123         updateInternalsWhenShowingView(viewController);
124         refreshUseStableInsets();
125         refreshInsetsToFit();
126         refreshWindowFocus();
127         refreshSystemBarVisibility();
128         refreshStatusBarVisibility();
129         refreshRotaryFocusIfNeeded();
130 
131         Log.d(TAG, "Content shown: " + viewController.getClass().getName());
132         debugLog();
133     }
134 
updateInternalsWhenShowingView(OverlayViewController viewController)135     private void updateInternalsWhenShowingView(OverlayViewController viewController) {
136         int zOrder;
137         if (mZOrderMap.containsKey(viewController)) {
138             zOrder = mZOrderMap.get(viewController);
139         } else {
140             zOrder = mSystemUIOverlayWindowController.getBaseLayout().indexOfChild(
141                     viewController.getLayout());
142             mZOrderMap.put(viewController, zOrder);
143         }
144 
145         mZOrderVisibleSortedMap.put(zOrder, viewController);
146 
147         refreshHighestZOrderWhenShowingView(viewController);
148     }
149 
refreshHighestZOrderWhenShowingView(OverlayViewController viewController)150     private void refreshHighestZOrderWhenShowingView(OverlayViewController viewController) {
151         if (mZOrderMap.getOrDefault(mHighestZOrder, UNKNOWN_Z_ORDER) < mZOrderMap.get(
152                 viewController)) {
153             mHighestZOrder = viewController;
154         }
155     }
156 
157     /**
158      * Hide content in Overlay Window using {@link OverlayPanelViewController}.
159      *
160      * This calls {@link OverlayViewGlobalStateController#hideView(OverlayViewController, Runnable)}
161      * where the runnable is nullified since the actual hiding of the panel is handled by the
162      * controller itself.
163      */
hideView(OverlayPanelViewController panelViewController)164     public void hideView(OverlayPanelViewController panelViewController) {
165         hideView(panelViewController, /* hide= */ null);
166     }
167 
168     /**
169      * Hide content in Overlay Window using {@link OverlayViewController}.
170      */
hideView(OverlayViewController viewController, @Nullable Runnable hide)171     public void hideView(OverlayViewController viewController, @Nullable Runnable hide) {
172         debugLog();
173         if (mIsOccluded && mViewsHiddenForOcclusion.contains(viewController)) {
174             mViewsHiddenForOcclusion.remove(viewController);
175             return;
176         }
177         if (!viewController.isInflated()) {
178             Log.d(TAG, "Content cannot be hidden since it isn't inflated: "
179                     + viewController.getClass().getName());
180             return;
181         }
182         if (!mZOrderMap.containsKey(viewController)) {
183             Log.d(TAG, "Content cannot be hidden since it has never been shown: "
184                     + viewController.getClass().getName());
185             return;
186         }
187         if (!mZOrderVisibleSortedMap.containsKey(mZOrderMap.get(viewController))) {
188             Log.d(TAG, "Content cannot be hidden since it isn't currently shown: "
189                     + viewController.getClass().getName());
190             return;
191         }
192 
193         if (hide != null) {
194             hide.run();
195         }
196 
197         mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
198         refreshHighestZOrderWhenHidingView(viewController);
199         refreshUseStableInsets();
200         refreshInsetsToFit();
201         refreshWindowFocus();
202         refreshSystemBarVisibility();
203         refreshStatusBarVisibility();
204         refreshRotaryFocusIfNeeded();
205 
206         if (mZOrderVisibleSortedMap.isEmpty()) {
207             setWindowVisible(false);
208         }
209 
210         Log.d(TAG, "Content hidden: " + viewController.getClass().getName());
211         debugLog();
212     }
213 
refreshHighestZOrderWhenHidingView(OverlayViewController viewController)214     private void refreshHighestZOrderWhenHidingView(OverlayViewController viewController) {
215         if (mZOrderVisibleSortedMap.isEmpty()) {
216             mHighestZOrder = null;
217             return;
218         }
219         if (!mHighestZOrder.equals(viewController)) {
220             return;
221         }
222 
223         mHighestZOrder = mZOrderVisibleSortedMap.get(mZOrderVisibleSortedMap.lastKey());
224     }
225 
refreshSystemBarVisibility()226     private void refreshSystemBarVisibility() {
227         if (mZOrderVisibleSortedMap.isEmpty()) {
228             mWindowInsetsController.show(navigationBars());
229             return;
230         }
231 
232         // Do not hide navigation bar insets if the window is not focusable.
233         if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowNavigationBarInsets()) {
234             mWindowInsetsController.hide(navigationBars());
235         } else {
236             mWindowInsetsController.show(navigationBars());
237         }
238     }
239 
refreshStatusBarVisibility()240     private void refreshStatusBarVisibility() {
241         if (mZOrderVisibleSortedMap.isEmpty()) {
242             mWindowInsetsController.show(statusBars());
243             return;
244         }
245 
246         // Do not hide status bar insets if the window is not focusable.
247         if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowStatusBarInsets()) {
248             mWindowInsetsController.hide(statusBars());
249         } else {
250             mWindowInsetsController.show(statusBars());
251         }
252     }
253 
refreshWindowFocus()254     private void refreshWindowFocus() {
255         setWindowFocusable(mHighestZOrder == null ? false : mHighestZOrder.shouldFocusWindow());
256     }
257 
refreshUseStableInsets()258     private void refreshUseStableInsets() {
259         mSystemUIOverlayWindowController.setUsingStableInsets(
260                 mHighestZOrder == null ? false : mHighestZOrder.shouldUseStableInsets());
261     }
262 
263     /**
264      * Refreshes the insets to fit (or honor) either by {@link InsetsType} or {@link InsetsSide}.
265      *
266      * By default, the insets to fit are defined by the {@link InsetsType}. But if an
267      * {@link OverlayViewController} overrides {@link OverlayViewController#getInsetSidesToFit()} to
268      * return an {@link InsetsSide}, then that takes precedence over {@link InsetsType}.
269      */
refreshInsetsToFit()270     private void refreshInsetsToFit() {
271         if (mZOrderVisibleSortedMap.isEmpty()) {
272             setFitInsetsTypes(statusBars());
273         } else {
274             if (mHighestZOrder.getInsetSidesToFit() != OverlayViewController.INVALID_INSET_SIDE) {
275                 // First fit all system bar insets as setFitInsetsSide defines which sides of system
276                 // bar insets to actually honor.
277                 setFitInsetsTypes(WindowInsets.Type.systemBars());
278                 setFitInsetsSides(mHighestZOrder.getInsetSidesToFit());
279             } else {
280                 setFitInsetsTypes(mHighestZOrder.getInsetTypesToFit());
281             }
282         }
283     }
284 
refreshRotaryFocusIfNeeded()285     private void refreshRotaryFocusIfNeeded() {
286         for (OverlayViewController controller : mZOrderVisibleSortedMap.values()) {
287             boolean isTop = Objects.equals(controller, mHighestZOrder);
288             controller.setAllowRotaryFocus(isTop);
289         }
290 
291         if (!mZOrderVisibleSortedMap.isEmpty()) {
292             mHighestZOrder.refreshRotaryFocusIfNeeded();
293         }
294     }
295 
296     /** Returns {@code true} is the window is visible. */
isWindowVisible()297     public boolean isWindowVisible() {
298         return mSystemUIOverlayWindowController.isWindowVisible();
299     }
300 
setWindowVisible(boolean visible)301     private void setWindowVisible(boolean visible) {
302         mSystemUIOverlayWindowController.setWindowVisible(visible);
303     }
304 
305     /** Sets the insets to fit based on the {@link InsetsType} */
setFitInsetsTypes(@nsetsType int types)306     private void setFitInsetsTypes(@InsetsType int types) {
307         mSystemUIOverlayWindowController.setFitInsetsTypes(types);
308     }
309 
310     /** Sets the insets to fit based on the {@link InsetsSide} */
setFitInsetsSides(@nsetsSide int sides)311     private void setFitInsetsSides(@InsetsSide int sides) {
312         mSystemUIOverlayWindowController.setFitInsetsSides(sides);
313     }
314 
315     /**
316      * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
317      * sysui overlay window.
318      */
setWindowNeedsInput(boolean needsInput)319     public void setWindowNeedsInput(boolean needsInput) {
320         mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
321     }
322 
323     /** Returns {@code true} if the window is focusable. */
isWindowFocusable()324     public boolean isWindowFocusable() {
325         return mSystemUIOverlayWindowController.isWindowFocusable();
326     }
327 
328     /** Sets the focusable flag of the sysui overlawy window. */
setWindowFocusable(boolean focusable)329     public void setWindowFocusable(boolean focusable) {
330         mSystemUIOverlayWindowController.setWindowFocusable(focusable);
331     }
332 
333     /** Inflates the view controlled by the given view controller. */
inflateView(OverlayViewController viewController)334     public void inflateView(OverlayViewController viewController) {
335         if (!viewController.isInflated()) {
336             viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
337         }
338     }
339 
340     /**
341      * Return {@code true} if OverlayWindow is in a state where HUNs should be displayed above it.
342      */
shouldShowHUN()343     public boolean shouldShowHUN() {
344         return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN();
345     }
346 
347     /**
348      * Set the OverlayViewWindow to be in occluded or unoccluded state. When OverlayViewWindow is
349      * occluded, all views mounted to it that are not configured to be shown during occlusion will
350      * be hidden.
351      */
setOccluded(boolean occluded)352     public void setOccluded(boolean occluded) {
353         if (occluded) {
354             // Hide views before setting mIsOccluded to true so the regular hideView logic is used,
355             // not the one used during occlusion.
356             hideViewsForOcclusion();
357             mIsOccluded = true;
358         } else {
359             mIsOccluded = false;
360             // show views after setting mIsOccluded to false so the regular showView logic is used,
361             // not the one used during occlusion.
362             showViewsHiddenForOcclusion();
363         }
364     }
365 
hideViewsForOcclusion()366     private void hideViewsForOcclusion() {
367         HashSet<OverlayViewController> viewsCurrentlyShowing = new HashSet<>(
368                 mZOrderVisibleSortedMap.values());
369         viewsCurrentlyShowing.forEach(overlayController -> {
370             if (!overlayController.shouldShowWhenOccluded()) {
371                 hideView(overlayController, overlayController::hideInternal);
372                 mViewsHiddenForOcclusion.add(overlayController);
373             }
374         });
375     }
376 
showViewsHiddenForOcclusion()377     private void showViewsHiddenForOcclusion() {
378         mViewsHiddenForOcclusion.forEach(overlayViewController -> {
379             showView(overlayViewController, overlayViewController::showInternal);
380         });
381         mViewsHiddenForOcclusion.clear();
382     }
383 
debugLog()384     private void debugLog() {
385         if (!DEBUG) {
386             return;
387         }
388 
389         Log.d(TAG, "mHighestZOrder: " + mHighestZOrder);
390         Log.d(TAG, "mZOrderVisibleSortedMap.size(): " + mZOrderVisibleSortedMap.size());
391         Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap);
392         Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size());
393         Log.d(TAG, "mZOrderMap: " + mZOrderMap);
394         Log.d(TAG, "mIsOccluded: " + mIsOccluded);
395         Log.d(TAG, "mViewsHiddenForOcclusion: " + mViewsHiddenForOcclusion);
396         Log.d(TAG, "mViewsHiddenForOcclusion.size(): " + mViewsHiddenForOcclusion.size());
397     }
398 }
399