• 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.statusBars;
20 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
21 
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.view.ViewStub;
25 import android.view.WindowInsets;
26 
27 import androidx.annotation.IdRes;
28 
29 import com.android.car.ui.FocusArea;
30 
31 /**
32  * Owns a {@link View} that is present in SystemUIOverlayWindow.
33  */
34 public class OverlayViewController {
35     protected static final int INVALID_INSET_SIDE = -1;
36     protected static final int NO_INSET_SIDE = 0;
37 
38     private final int mStubId;
39     private final OverlayViewGlobalStateController mOverlayViewGlobalStateController;
40 
41     private View mLayout;
42 
OverlayViewController(int stubId, OverlayViewGlobalStateController overlayViewGlobalStateController)43     public OverlayViewController(int stubId,
44             OverlayViewGlobalStateController overlayViewGlobalStateController) {
45         mLayout = null;
46         mStubId = stubId;
47         mOverlayViewGlobalStateController = overlayViewGlobalStateController;
48     }
49 
50     /**
51      * Shows content of {@link OverlayViewController}.
52      *
53      * Should be used to show view externally and in particular by {@link OverlayViewMediator}.
54      */
start()55     public final void start() {
56         mOverlayViewGlobalStateController.showView(/* viewController= */ this, this::show);
57     }
58 
59     /**
60      * Hides content of {@link OverlayViewController}.
61      *
62      * Should be used to hide view externally and in particular by {@link OverlayViewMediator}.
63      */
stop()64     public final void stop() {
65         mOverlayViewGlobalStateController.hideView(/* viewController= */ this, this::hide);
66     }
67 
68     /**
69      * Inflate layout owned by controller.
70      */
inflate(ViewGroup baseLayout)71     public final void inflate(ViewGroup baseLayout) {
72         ViewStub viewStub = baseLayout.findViewById(mStubId);
73         mLayout = viewStub.inflate();
74         onFinishInflate();
75     }
76 
77     /**
78      * Called once inflate finishes.
79      */
onFinishInflate()80     protected void onFinishInflate() {
81         // no-op
82     }
83 
84     /**
85      * Returns {@code true} if layout owned by controller has been inflated.
86      */
isInflated()87     public final boolean isInflated() {
88         return mLayout != null;
89     }
90 
show()91     private void show() {
92         if (mLayout == null) {
93             // layout must be inflated before show() is called.
94             return;
95         }
96         showInternal();
97     }
98 
99     /**
100      * Subclasses should override this method to implement reveal animations and implement logic
101      * specific to when the layout owned by the controller is shown.
102      *
103      * Should only be overridden by Superclass but not called by any {@link OverlayViewMediator}.
104      */
showInternal()105     protected void showInternal() {
106         mLayout.setVisibility(View.VISIBLE);
107     }
108 
hide()109     private void hide() {
110         if (mLayout == null) {
111             // layout must be inflated before hide() is called.
112             return;
113         }
114         hideInternal();
115     }
116 
117     /**
118      * Subclasses should override this method to implement conceal animations and implement logic
119      * specific to when the layout owned by the controller is hidden.
120      *
121      * Should only be overridden by Superclass but not called by any {@link OverlayViewMediator}.
122      */
hideInternal()123     protected void hideInternal() {
124         mLayout.setVisibility(View.GONE);
125     }
126 
127     /**
128      * Provides access to layout owned by controller.
129      */
getLayout()130     protected final View getLayout() {
131         return mLayout;
132     }
133 
134     /** Returns the {@link OverlayViewGlobalStateController}. */
getOverlayViewGlobalStateController()135     protected final OverlayViewGlobalStateController getOverlayViewGlobalStateController() {
136         return mOverlayViewGlobalStateController;
137     }
138 
139     /** Returns whether the view controlled by this controller is visible. */
isVisible()140     public final boolean isVisible() {
141         return mLayout.getVisibility() == View.VISIBLE;
142     }
143 
144     /**
145      * Returns the ID of the focus area that should receive focus when this view is the
146      * topmost view or {@link View#NO_ID} if there is no focus area.
147      */
148     @IdRes
getFocusAreaViewId()149     protected int getFocusAreaViewId() {
150         return View.NO_ID;
151     }
152 
153     /** Returns whether the view controlled by this controller has rotary focus. */
hasRotaryFocus()154     protected final boolean hasRotaryFocus() {
155         return !mLayout.isInTouchMode() && mLayout.hasFocus();
156     }
157 
158     /**
159      * Sets whether this view allows rotary focus. This should be set to {@code true} for the
160      * topmost layer in the overlay window and {@code false} for the others.
161      */
setAllowRotaryFocus(boolean allowRotaryFocus)162     public void setAllowRotaryFocus(boolean allowRotaryFocus) {
163         if (!isInflated()) {
164             return;
165         }
166 
167         if (!(mLayout instanceof ViewGroup)) {
168             return;
169         }
170 
171         ViewGroup viewGroup = (ViewGroup) mLayout;
172         viewGroup.setDescendantFocusability(allowRotaryFocus
173                 ? ViewGroup.FOCUS_BEFORE_DESCENDANTS
174                 : ViewGroup.FOCUS_BLOCK_DESCENDANTS);
175     }
176 
177     /**
178      * Refreshes the rotary focus in this view if we are in rotary mode. If the view already has
179      * rotary focus, it leaves the focus alone. Returns {@code true} if a new view was focused.
180      */
refreshRotaryFocusIfNeeded()181     public boolean refreshRotaryFocusIfNeeded() {
182         if (mLayout.isInTouchMode()) {
183             return false;
184         }
185 
186         if (hasRotaryFocus()) {
187             return false;
188         }
189 
190         View view = mLayout.findViewById(getFocusAreaViewId());
191         if (view == null || !(view instanceof FocusArea)) {
192             return mLayout.requestFocus();
193         }
194 
195         FocusArea focusArea = (FocusArea) view;
196         return focusArea.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
197     }
198 
199     /**
200      * Returns {@code true} if heads up notifications should be displayed over this view.
201      */
shouldShowHUN()202     protected boolean shouldShowHUN() {
203         return true;
204     }
205 
206     /**
207      * Returns {@code true} if navigation bar insets should be displayed over this view. Has no
208      * effect if {@link #shouldFocusWindow} returns {@code false}.
209      */
shouldShowNavigationBarInsets()210     protected boolean shouldShowNavigationBarInsets() {
211         return false;
212     }
213 
214     /**
215      * Returns {@code true} if status bar insets should be displayed over this view. Has no
216      * effect if {@link #shouldFocusWindow} returns {@code false}.
217      */
shouldShowStatusBarInsets()218     protected boolean shouldShowStatusBarInsets() {
219         return false;
220     }
221 
222     /**
223      * Returns {@code true} if this view should be hidden during the occluded state.
224      */
shouldShowWhenOccluded()225     protected boolean shouldShowWhenOccluded() {
226         return false;
227     }
228 
229     /**
230      * Returns {@code true} if the window should be focued when this view is visible. Note that
231      * returning {@code false} here means that {@link #shouldShowStatusBarInsets} and
232      * {@link #shouldShowNavigationBarInsets} will have no effect.
233      */
shouldFocusWindow()234     protected boolean shouldFocusWindow() {
235         return true;
236     }
237 
238     /**
239      * Returns {@code true} if the window should use stable insets. Using stable insets means that
240      * even when system bars are temporarily not visible, inset from the system bars will still be
241      * applied.
242      *
243      * NOTE: When system bars are hidden in transient mode, insets from them will not be applied
244      * even when the system bars become visible. Setting the return value to {@true} here can
245      * prevent the OverlayView from overlapping with the system bars when that happens.
246      */
shouldUseStableInsets()247     protected boolean shouldUseStableInsets() {
248         return false;
249     }
250 
251     /**
252      * Returns the insets types to fit to the sysui overlay window when this
253      * {@link OverlayViewController} is in the foreground.
254      */
255     @WindowInsets.Type.InsetsType
getInsetTypesToFit()256     protected int getInsetTypesToFit() {
257         return statusBars();
258     }
259 
260     /**
261      * Optionally returns the sides of enabled system bar insets to fit to the sysui overlay window
262      * when this {@link OverlayViewController} is in the foreground.
263      *
264      * For example, if the bottom and left system bars are enabled and this method returns
265      * WindowInsets.Side.LEFT, then the inset from the bottom system bar will be ignored.
266      *
267      * NOTE: By default, this method returns {@link #INVALID_INSET_SIDE}, so insets to fit are
268      * defined by {@link #getInsetTypesToFit()}, and not by this method, unless it is overridden
269      * by subclasses.
270      *
271      * NOTE: {@link #NO_INSET_SIDE} signifies no insets from any system bars will be honored. Each
272      * {@link OverlayViewController} can first take this value and add sides of the system bar
273      * insets to honor to it.
274      *
275      * NOTE: If getInsetSidesToFit is overridden to return {@link WindowInsets.Side}, it always
276      * takes precedence over {@link #getInsetTypesToFit()}. That is, the return value of {@link
277      * #getInsetTypesToFit()} will be ignored.
278      */
279     @WindowInsets.Side.InsetsSide
getInsetSidesToFit()280     protected int getInsetSidesToFit() {
281         return INVALID_INSET_SIDE;
282     }
283 }
284