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         mSystemUIOverlayWindowController.registerOutsideTouchListener((v, event) -> {
75             if (mHighestZOrder != null) {
76                 mHighestZOrder.onTouchEvent(v, event);
77             }
78         });
79         mWindowInsetsController =
80                 mSystemUIOverlayWindowController.getBaseLayout().getWindowInsetsController();
81         mZOrderMap = new HashMap<>();
82         mZOrderVisibleSortedMap = new TreeMap<>();
83         mViewsHiddenForOcclusion = new HashSet<>();
84     }
85 
86     /**
87      * Register {@link OverlayViewMediator} to use in SystemUIOverlayWindow.
88      */
registerMediator(OverlayViewMediator overlayViewMediator)89     public void registerMediator(OverlayViewMediator overlayViewMediator) {
90         Log.d(TAG, "Registering content mediator: " + overlayViewMediator.getClass().getName());
91 
92         overlayViewMediator.registerListeners();
93         overlayViewMediator.setUpOverlayContentViewControllers();
94     }
95 
96     /**
97      * Show content in Overlay Window using {@link OverlayPanelViewController}.
98      *
99      * This calls {@link OverlayViewGlobalStateController#showView(OverlayViewController, Runnable)}
100      * where the runnable is nullified since the actual showing of the panel is handled by the
101      * controller itself.
102      */
showView(OverlayPanelViewController panelViewController)103     public void showView(OverlayPanelViewController panelViewController) {
104         showView(panelViewController, /* show= */ null);
105     }
106 
107     /**
108      * Show content in Overlay Window using {@link OverlayViewController}.
109      */
showView(OverlayViewController viewController, @Nullable Runnable show)110     public void showView(OverlayViewController viewController, @Nullable Runnable show) {
111         debugLog();
112         if (mIsOccluded && !viewController.shouldShowWhenOccluded()) {
113             mViewsHiddenForOcclusion.add(viewController);
114             return;
115         }
116         if (mZOrderVisibleSortedMap.isEmpty()) {
117             setWindowVisible(true);
118         }
119 
120         if (!(viewController instanceof OverlayPanelViewController)) {
121             inflateView(viewController);
122         }
123 
124         if (show != null) {
125             show.run();
126         }
127 
128         updateInternalsWhenShowingView(viewController);
129         refreshUseStableInsets();
130         refreshInsetsToFit();
131         refreshWindowFocus();
132         refreshWindowDefaultDimBehind();
133         refreshSystemBarVisibility();
134         refreshStatusBarVisibility();
135         refreshRotaryFocusIfNeeded();
136 
137         Log.d(TAG, "Content shown: " + viewController.getClass().getName());
138         debugLog();
139     }
140 
updateInternalsWhenShowingView(OverlayViewController viewController)141     private void updateInternalsWhenShowingView(OverlayViewController viewController) {
142         int zOrder;
143         if (mZOrderMap.containsKey(viewController)) {
144             zOrder = mZOrderMap.get(viewController);
145         } else {
146             zOrder = mSystemUIOverlayWindowController.getBaseLayout().indexOfChild(
147                     viewController.getLayout());
148             mZOrderMap.put(viewController, zOrder);
149         }
150 
151         mZOrderVisibleSortedMap.put(zOrder, viewController);
152 
153         refreshHighestZOrderWhenShowingView(viewController);
154     }
155 
refreshHighestZOrderWhenShowingView(OverlayViewController viewController)156     private void refreshHighestZOrderWhenShowingView(OverlayViewController viewController) {
157         if (mZOrderMap.getOrDefault(mHighestZOrder, UNKNOWN_Z_ORDER) < mZOrderMap.get(
158                 viewController)) {
159             mHighestZOrder = viewController;
160         }
161     }
162 
163     /**
164      * Hide content in Overlay Window using {@link OverlayPanelViewController}.
165      *
166      * This calls {@link OverlayViewGlobalStateController#hideView(OverlayViewController, Runnable)}
167      * where the runnable is nullified since the actual hiding of the panel is handled by the
168      * controller itself.
169      */
hideView(OverlayPanelViewController panelViewController)170     public void hideView(OverlayPanelViewController panelViewController) {
171         hideView(panelViewController, /* hide= */ null);
172     }
173 
174     /**
175      * Hide content in Overlay Window using {@link OverlayViewController}.
176      */
hideView(OverlayViewController viewController, @Nullable Runnable hide)177     public void hideView(OverlayViewController viewController, @Nullable Runnable hide) {
178         debugLog();
179         if (mIsOccluded && mViewsHiddenForOcclusion.contains(viewController)) {
180             mViewsHiddenForOcclusion.remove(viewController);
181             return;
182         }
183         if (!viewController.isInflated()) {
184             Log.d(TAG, "Content cannot be hidden since it isn't inflated: "
185                     + viewController.getClass().getName());
186             return;
187         }
188         if (!mZOrderMap.containsKey(viewController)) {
189             Log.d(TAG, "Content cannot be hidden since it has never been shown: "
190                     + viewController.getClass().getName());
191             return;
192         }
193         if (!mZOrderVisibleSortedMap.containsKey(mZOrderMap.get(viewController))) {
194             Log.d(TAG, "Content cannot be hidden since it isn't currently shown: "
195                     + viewController.getClass().getName());
196             return;
197         }
198 
199         if (hide != null) {
200             hide.run();
201         }
202 
203         mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
204         refreshHighestZOrderWhenHidingView(viewController);
205         refreshUseStableInsets();
206         refreshInsetsToFit();
207         refreshWindowFocus();
208         refreshWindowDefaultDimBehind();
209         refreshSystemBarVisibility();
210         refreshStatusBarVisibility();
211         refreshRotaryFocusIfNeeded();
212 
213         if (mZOrderVisibleSortedMap.isEmpty()) {
214             setWindowVisible(false);
215         }
216 
217         Log.d(TAG, "Content hidden: " + viewController.getClass().getName());
218         debugLog();
219     }
220 
221     /**
222      * After the default dim amount is set via {@link OverlayViewController#getDefaultDimAmount},
223      * this function can be called to make further updates to the dim amount when an overlay view
224      * is the top z-ordered window. Returns {@code true} if the dim amount of the window has been
225      * updated
226      */
updateWindowDimBehind(OverlayViewController viewController, float dimAmount)227     public boolean updateWindowDimBehind(OverlayViewController viewController, float dimAmount) {
228         if (mHighestZOrder == null || viewController != mHighestZOrder) {
229             return false;
230         }
231         mSystemUIOverlayWindowController.setDimBehind(dimAmount);
232         return true;
233     }
234 
refreshHighestZOrderWhenHidingView(OverlayViewController viewController)235     private void refreshHighestZOrderWhenHidingView(OverlayViewController viewController) {
236         if (mZOrderVisibleSortedMap.isEmpty()) {
237             mHighestZOrder = null;
238             return;
239         }
240         if (!mHighestZOrder.equals(viewController)) {
241             return;
242         }
243 
244         mHighestZOrder = mZOrderVisibleSortedMap.get(mZOrderVisibleSortedMap.lastKey());
245     }
246 
refreshSystemBarVisibility()247     private void refreshSystemBarVisibility() {
248         if (mZOrderVisibleSortedMap.isEmpty()) {
249             mWindowInsetsController.show(navigationBars());
250             return;
251         }
252 
253         // Do not hide navigation bar insets if the window is not focusable.
254         if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowNavigationBarInsets()) {
255             mWindowInsetsController.hide(navigationBars());
256         } else {
257             mWindowInsetsController.show(navigationBars());
258         }
259     }
260 
refreshStatusBarVisibility()261     private void refreshStatusBarVisibility() {
262         if (mZOrderVisibleSortedMap.isEmpty()) {
263             mWindowInsetsController.show(statusBars());
264             return;
265         }
266 
267         // Do not hide status bar insets if the window is not focusable.
268         if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowStatusBarInsets()) {
269             mWindowInsetsController.hide(statusBars());
270         } else {
271             mWindowInsetsController.show(statusBars());
272         }
273     }
274 
refreshWindowFocus()275     private void refreshWindowFocus() {
276         setWindowFocusable(mHighestZOrder == null ? false : mHighestZOrder.shouldFocusWindow());
277     }
278 
refreshWindowDefaultDimBehind()279     private void refreshWindowDefaultDimBehind() {
280         float dimAmount = mHighestZOrder == null ? 0f : mHighestZOrder.getDefaultDimAmount();
281         mSystemUIOverlayWindowController.setDimBehind(dimAmount);
282     }
283 
refreshUseStableInsets()284     private void refreshUseStableInsets() {
285         mSystemUIOverlayWindowController.setUsingStableInsets(
286                 mHighestZOrder == null ? false : mHighestZOrder.shouldUseStableInsets());
287     }
288 
289     /**
290      * Refreshes the insets to fit (or honor) either by {@link InsetsType} or {@link InsetsSide}.
291      *
292      * By default, the insets to fit are defined by the {@link InsetsType}. But if an
293      * {@link OverlayViewController} overrides {@link OverlayViewController#getInsetSidesToFit()} to
294      * return an {@link InsetsSide}, then that takes precedence over {@link InsetsType}.
295      */
refreshInsetsToFit()296     private void refreshInsetsToFit() {
297         if (mZOrderVisibleSortedMap.isEmpty()) {
298             setFitInsetsTypes(statusBars());
299         } else {
300             if (mHighestZOrder.getInsetSidesToFit() != OverlayViewController.INVALID_INSET_SIDE) {
301                 // First fit all system bar insets as setFitInsetsSide defines which sides of system
302                 // bar insets to actually honor.
303                 setFitInsetsTypes(WindowInsets.Type.systemBars());
304                 setFitInsetsSides(mHighestZOrder.getInsetSidesToFit());
305             } else {
306                 setFitInsetsTypes(mHighestZOrder.getInsetTypesToFit());
307             }
308         }
309     }
310 
refreshRotaryFocusIfNeeded()311     private void refreshRotaryFocusIfNeeded() {
312         for (OverlayViewController controller : mZOrderVisibleSortedMap.values()) {
313             boolean isTop = Objects.equals(controller, mHighestZOrder);
314             controller.setAllowRotaryFocus(isTop);
315         }
316 
317         if (!mZOrderVisibleSortedMap.isEmpty()) {
318             mHighestZOrder.refreshRotaryFocusIfNeeded();
319         }
320     }
321 
322     /** Returns {@code true} is the window is visible. */
isWindowVisible()323     public boolean isWindowVisible() {
324         return mSystemUIOverlayWindowController.isWindowVisible();
325     }
326 
setWindowVisible(boolean visible)327     private void setWindowVisible(boolean visible) {
328         mSystemUIOverlayWindowController.setWindowVisible(visible);
329     }
330 
331     /** Sets the insets to fit based on the {@link InsetsType} */
setFitInsetsTypes(@nsetsType int types)332     private void setFitInsetsTypes(@InsetsType int types) {
333         mSystemUIOverlayWindowController.setFitInsetsTypes(types);
334     }
335 
336     /** Sets the insets to fit based on the {@link InsetsSide} */
setFitInsetsSides(@nsetsSide int sides)337     private void setFitInsetsSides(@InsetsSide int sides) {
338         mSystemUIOverlayWindowController.setFitInsetsSides(sides);
339     }
340 
341     /**
342      * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
343      * sysui overlay window.
344      */
setWindowNeedsInput(boolean needsInput)345     public void setWindowNeedsInput(boolean needsInput) {
346         mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
347     }
348 
349     /** Returns {@code true} if the window is focusable. */
isWindowFocusable()350     public boolean isWindowFocusable() {
351         return mSystemUIOverlayWindowController.isWindowFocusable();
352     }
353 
354     /** Sets the focusable flag of the sysui overlawy window. */
setWindowFocusable(boolean focusable)355     public void setWindowFocusable(boolean focusable) {
356         mSystemUIOverlayWindowController.setWindowFocusable(focusable);
357     }
358 
359     /** Inflates the view controlled by the given view controller. */
inflateView(OverlayViewController viewController)360     public void inflateView(OverlayViewController viewController) {
361         if (!viewController.isInflated()) {
362             viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
363         }
364     }
365 
366     /**
367      * Return {@code true} if OverlayWindow is in a state where HUNs should be displayed above it.
368      */
shouldShowHUN()369     public boolean shouldShowHUN() {
370         return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN();
371     }
372 
373     /**
374      * Set the OverlayViewWindow to be in occluded or unoccluded state. When OverlayViewWindow is
375      * occluded, all views mounted to it that are not configured to be shown during occlusion will
376      * be hidden.
377      */
setOccluded(boolean occluded)378     public void setOccluded(boolean occluded) {
379         if (occluded) {
380             // Hide views before setting mIsOccluded to true so the regular hideView logic is used,
381             // not the one used during occlusion.
382             hideViewsForOcclusion();
383             mIsOccluded = true;
384         } else {
385             mIsOccluded = false;
386             // show views after setting mIsOccluded to false so the regular showView logic is used,
387             // not the one used during occlusion.
388             showViewsHiddenForOcclusion();
389         }
390     }
391 
hideViewsForOcclusion()392     private void hideViewsForOcclusion() {
393         HashSet<OverlayViewController> viewsCurrentlyShowing = new HashSet<>(
394                 mZOrderVisibleSortedMap.values());
395         viewsCurrentlyShowing.forEach(overlayController -> {
396             if (!overlayController.shouldShowWhenOccluded()) {
397                 hideView(overlayController, overlayController::hideInternal);
398                 mViewsHiddenForOcclusion.add(overlayController);
399             }
400         });
401     }
402 
showViewsHiddenForOcclusion()403     private void showViewsHiddenForOcclusion() {
404         mViewsHiddenForOcclusion.forEach(overlayViewController -> {
405             showView(overlayViewController, overlayViewController::showInternal);
406         });
407         mViewsHiddenForOcclusion.clear();
408     }
409 
debugLog()410     private void debugLog() {
411         if (!DEBUG) {
412             return;
413         }
414 
415         Log.d(TAG, "mHighestZOrder: " + mHighestZOrder);
416         Log.d(TAG, "mZOrderVisibleSortedMap.size(): " + mZOrderVisibleSortedMap.size());
417         Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap);
418         Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size());
419         Log.d(TAG, "mZOrderMap: " + mZOrderMap);
420         Log.d(TAG, "mIsOccluded: " + mIsOccluded);
421         Log.d(TAG, "mViewsHiddenForOcclusion: " + mViewsHiddenForOcclusion);
422         Log.d(TAG, "mViewsHiddenForOcclusion.size(): " + mViewsHiddenForOcclusion.size());
423     }
424 }
425