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