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