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