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