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