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.HashMap; 34 import java.util.HashSet; 35 import java.util.Map; 36 import java.util.Objects; 37 import java.util.Set; 38 import java.util.SortedMap; 39 import java.util.TreeMap; 40 41 import javax.inject.Inject; 42 43 /** 44 * This controller is responsible for the following: 45 * <p><ul> 46 * <li>Holds the global state for SystemUIOverlayWindow. 47 * <li>Allows {@link SystemUIOverlayWindowManager} to register {@link OverlayViewMediator}(s). 48 * <li>Enables {@link OverlayViewController)(s) to reveal/conceal themselves while respecting the 49 * global state of SystemUIOverlayWindow. 50 * </ul> 51 */ 52 @SysUISingleton 53 public class OverlayViewGlobalStateController { 54 private static final boolean DEBUG = false; 55 private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName(); 56 private static final int UNKNOWN_Z_ORDER = -1; 57 private final SystemUIOverlayWindowController mSystemUIOverlayWindowController; 58 private final WindowInsetsController mWindowInsetsController; 59 @VisibleForTesting 60 Map<OverlayViewController, Integer> mZOrderMap; 61 @VisibleForTesting 62 SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap; 63 @VisibleForTesting 64 Set<OverlayViewController> mViewsHiddenForOcclusion; 65 @VisibleForTesting 66 OverlayViewController mHighestZOrder; 67 private boolean mIsOccluded; 68 69 @Inject OverlayViewGlobalStateController( SystemUIOverlayWindowController systemUIOverlayWindowController)70 public OverlayViewGlobalStateController( 71 SystemUIOverlayWindowController systemUIOverlayWindowController) { 72 mSystemUIOverlayWindowController = systemUIOverlayWindowController; 73 mSystemUIOverlayWindowController.attach(); 74 mSystemUIOverlayWindowController.registerOutsideTouchListener((v, event) -> { 75 if (mHighestZOrder != null) { 76 mHighestZOrder.onTouchEvent(v, event); 77 } 78 }); 79 mWindowInsetsController = 80 mSystemUIOverlayWindowController.getBaseLayout().getWindowInsetsController(); 81 mZOrderMap = new HashMap<>(); 82 mZOrderVisibleSortedMap = new TreeMap<>(); 83 mViewsHiddenForOcclusion = new HashSet<>(); 84 } 85 86 /** 87 * Register {@link OverlayViewMediator} to use in SystemUIOverlayWindow. 88 */ registerMediator(OverlayViewMediator overlayViewMediator)89 public void registerMediator(OverlayViewMediator overlayViewMediator) { 90 Log.d(TAG, "Registering content mediator: " + overlayViewMediator.getClass().getName()); 91 92 overlayViewMediator.registerListeners(); 93 overlayViewMediator.setUpOverlayContentViewControllers(); 94 } 95 96 /** 97 * Show content in Overlay Window using {@link OverlayPanelViewController}. 98 * 99 * This calls {@link OverlayViewGlobalStateController#showView(OverlayViewController, Runnable)} 100 * where the runnable is nullified since the actual showing of the panel is handled by the 101 * controller itself. 102 */ showView(OverlayPanelViewController panelViewController)103 public void showView(OverlayPanelViewController panelViewController) { 104 showView(panelViewController, /* show= */ null); 105 } 106 107 /** 108 * Show content in Overlay Window using {@link OverlayViewController}. 109 */ showView(OverlayViewController viewController, @Nullable Runnable show)110 public void showView(OverlayViewController viewController, @Nullable Runnable show) { 111 debugLog(); 112 if (mIsOccluded && !viewController.shouldShowWhenOccluded()) { 113 mViewsHiddenForOcclusion.add(viewController); 114 return; 115 } 116 if (mZOrderVisibleSortedMap.isEmpty()) { 117 setWindowVisible(true); 118 } 119 120 if (!(viewController instanceof OverlayPanelViewController)) { 121 inflateView(viewController); 122 } 123 124 if (show != null) { 125 show.run(); 126 } 127 128 updateInternalsWhenShowingView(viewController); 129 refreshUseStableInsets(); 130 refreshInsetsToFit(); 131 refreshWindowFocus(); 132 refreshWindowDefaultDimBehind(); 133 refreshSystemBarVisibility(); 134 refreshStatusBarVisibility(); 135 refreshRotaryFocusIfNeeded(); 136 137 Log.d(TAG, "Content shown: " + viewController.getClass().getName()); 138 debugLog(); 139 } 140 updateInternalsWhenShowingView(OverlayViewController viewController)141 private void updateInternalsWhenShowingView(OverlayViewController viewController) { 142 int zOrder; 143 if (mZOrderMap.containsKey(viewController)) { 144 zOrder = mZOrderMap.get(viewController); 145 } else { 146 zOrder = mSystemUIOverlayWindowController.getBaseLayout().indexOfChild( 147 viewController.getLayout()); 148 mZOrderMap.put(viewController, zOrder); 149 } 150 151 mZOrderVisibleSortedMap.put(zOrder, viewController); 152 153 refreshHighestZOrderWhenShowingView(viewController); 154 } 155 refreshHighestZOrderWhenShowingView(OverlayViewController viewController)156 private void refreshHighestZOrderWhenShowingView(OverlayViewController viewController) { 157 if (mZOrderMap.getOrDefault(mHighestZOrder, UNKNOWN_Z_ORDER) < mZOrderMap.get( 158 viewController)) { 159 mHighestZOrder = viewController; 160 } 161 } 162 163 /** 164 * Hide content in Overlay Window using {@link OverlayPanelViewController}. 165 * 166 * This calls {@link OverlayViewGlobalStateController#hideView(OverlayViewController, Runnable)} 167 * where the runnable is nullified since the actual hiding of the panel is handled by the 168 * controller itself. 169 */ hideView(OverlayPanelViewController panelViewController)170 public void hideView(OverlayPanelViewController panelViewController) { 171 hideView(panelViewController, /* hide= */ null); 172 } 173 174 /** 175 * Hide content in Overlay Window using {@link OverlayViewController}. 176 */ hideView(OverlayViewController viewController, @Nullable Runnable hide)177 public void hideView(OverlayViewController viewController, @Nullable Runnable hide) { 178 debugLog(); 179 if (mIsOccluded && mViewsHiddenForOcclusion.contains(viewController)) { 180 mViewsHiddenForOcclusion.remove(viewController); 181 return; 182 } 183 if (!viewController.isInflated()) { 184 Log.d(TAG, "Content cannot be hidden since it isn't inflated: " 185 + viewController.getClass().getName()); 186 return; 187 } 188 if (!mZOrderMap.containsKey(viewController)) { 189 Log.d(TAG, "Content cannot be hidden since it has never been shown: " 190 + viewController.getClass().getName()); 191 return; 192 } 193 if (!mZOrderVisibleSortedMap.containsKey(mZOrderMap.get(viewController))) { 194 Log.d(TAG, "Content cannot be hidden since it isn't currently shown: " 195 + viewController.getClass().getName()); 196 return; 197 } 198 199 if (hide != null) { 200 hide.run(); 201 } 202 203 mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController)); 204 refreshHighestZOrderWhenHidingView(viewController); 205 refreshUseStableInsets(); 206 refreshInsetsToFit(); 207 refreshWindowFocus(); 208 refreshWindowDefaultDimBehind(); 209 refreshSystemBarVisibility(); 210 refreshStatusBarVisibility(); 211 refreshRotaryFocusIfNeeded(); 212 213 if (mZOrderVisibleSortedMap.isEmpty()) { 214 setWindowVisible(false); 215 } 216 217 Log.d(TAG, "Content hidden: " + viewController.getClass().getName()); 218 debugLog(); 219 } 220 221 /** 222 * After the default dim amount is set via {@link OverlayViewController#getDefaultDimAmount}, 223 * this function can be called to make further updates to the dim amount when an overlay view 224 * is the top z-ordered window. Returns {@code true} if the dim amount of the window has been 225 * updated 226 */ updateWindowDimBehind(OverlayViewController viewController, float dimAmount)227 public boolean updateWindowDimBehind(OverlayViewController viewController, float dimAmount) { 228 if (mHighestZOrder == null || viewController != mHighestZOrder) { 229 return false; 230 } 231 mSystemUIOverlayWindowController.setDimBehind(dimAmount); 232 return true; 233 } 234 refreshHighestZOrderWhenHidingView(OverlayViewController viewController)235 private void refreshHighestZOrderWhenHidingView(OverlayViewController viewController) { 236 if (mZOrderVisibleSortedMap.isEmpty()) { 237 mHighestZOrder = null; 238 return; 239 } 240 if (!mHighestZOrder.equals(viewController)) { 241 return; 242 } 243 244 mHighestZOrder = mZOrderVisibleSortedMap.get(mZOrderVisibleSortedMap.lastKey()); 245 } 246 refreshSystemBarVisibility()247 private void refreshSystemBarVisibility() { 248 if (mZOrderVisibleSortedMap.isEmpty()) { 249 mWindowInsetsController.show(navigationBars()); 250 return; 251 } 252 253 // Do not hide navigation bar insets if the window is not focusable. 254 if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowNavigationBarInsets()) { 255 mWindowInsetsController.hide(navigationBars()); 256 } else { 257 mWindowInsetsController.show(navigationBars()); 258 } 259 } 260 refreshStatusBarVisibility()261 private void refreshStatusBarVisibility() { 262 if (mZOrderVisibleSortedMap.isEmpty()) { 263 mWindowInsetsController.show(statusBars()); 264 return; 265 } 266 267 // Do not hide status bar insets if the window is not focusable. 268 if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowStatusBarInsets()) { 269 mWindowInsetsController.hide(statusBars()); 270 } else { 271 mWindowInsetsController.show(statusBars()); 272 } 273 } 274 refreshWindowFocus()275 private void refreshWindowFocus() { 276 setWindowFocusable(mHighestZOrder == null ? false : mHighestZOrder.shouldFocusWindow()); 277 } 278 refreshWindowDefaultDimBehind()279 private void refreshWindowDefaultDimBehind() { 280 float dimAmount = mHighestZOrder == null ? 0f : mHighestZOrder.getDefaultDimAmount(); 281 mSystemUIOverlayWindowController.setDimBehind(dimAmount); 282 } 283 refreshUseStableInsets()284 private void refreshUseStableInsets() { 285 mSystemUIOverlayWindowController.setUsingStableInsets( 286 mHighestZOrder == null ? false : mHighestZOrder.shouldUseStableInsets()); 287 } 288 289 /** 290 * Refreshes the insets to fit (or honor) either by {@link InsetsType} or {@link InsetsSide}. 291 * 292 * By default, the insets to fit are defined by the {@link InsetsType}. But if an 293 * {@link OverlayViewController} overrides {@link OverlayViewController#getInsetSidesToFit()} to 294 * return an {@link InsetsSide}, then that takes precedence over {@link InsetsType}. 295 */ refreshInsetsToFit()296 private void refreshInsetsToFit() { 297 if (mZOrderVisibleSortedMap.isEmpty()) { 298 setFitInsetsTypes(statusBars()); 299 } else { 300 if (mHighestZOrder.getInsetSidesToFit() != OverlayViewController.INVALID_INSET_SIDE) { 301 // First fit all system bar insets as setFitInsetsSide defines which sides of system 302 // bar insets to actually honor. 303 setFitInsetsTypes(WindowInsets.Type.systemBars()); 304 setFitInsetsSides(mHighestZOrder.getInsetSidesToFit()); 305 } else { 306 setFitInsetsTypes(mHighestZOrder.getInsetTypesToFit()); 307 } 308 } 309 } 310 refreshRotaryFocusIfNeeded()311 private void refreshRotaryFocusIfNeeded() { 312 for (OverlayViewController controller : mZOrderVisibleSortedMap.values()) { 313 boolean isTop = Objects.equals(controller, mHighestZOrder); 314 controller.setAllowRotaryFocus(isTop); 315 } 316 317 if (!mZOrderVisibleSortedMap.isEmpty()) { 318 mHighestZOrder.refreshRotaryFocusIfNeeded(); 319 } 320 } 321 322 /** Returns {@code true} is the window is visible. */ isWindowVisible()323 public boolean isWindowVisible() { 324 return mSystemUIOverlayWindowController.isWindowVisible(); 325 } 326 setWindowVisible(boolean visible)327 private void setWindowVisible(boolean visible) { 328 mSystemUIOverlayWindowController.setWindowVisible(visible); 329 } 330 331 /** Sets the insets to fit based on the {@link InsetsType} */ setFitInsetsTypes(@nsetsType int types)332 private void setFitInsetsTypes(@InsetsType int types) { 333 mSystemUIOverlayWindowController.setFitInsetsTypes(types); 334 } 335 336 /** Sets the insets to fit based on the {@link InsetsSide} */ setFitInsetsSides(@nsetsSide int sides)337 private void setFitInsetsSides(@InsetsSide int sides) { 338 mSystemUIOverlayWindowController.setFitInsetsSides(sides); 339 } 340 341 /** 342 * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the 343 * sysui overlay window. 344 */ setWindowNeedsInput(boolean needsInput)345 public void setWindowNeedsInput(boolean needsInput) { 346 mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput); 347 } 348 349 /** Returns {@code true} if the window is focusable. */ isWindowFocusable()350 public boolean isWindowFocusable() { 351 return mSystemUIOverlayWindowController.isWindowFocusable(); 352 } 353 354 /** Sets the focusable flag of the sysui overlawy window. */ setWindowFocusable(boolean focusable)355 public void setWindowFocusable(boolean focusable) { 356 mSystemUIOverlayWindowController.setWindowFocusable(focusable); 357 } 358 359 /** Inflates the view controlled by the given view controller. */ inflateView(OverlayViewController viewController)360 public void inflateView(OverlayViewController viewController) { 361 if (!viewController.isInflated()) { 362 viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout()); 363 } 364 } 365 366 /** 367 * Return {@code true} if OverlayWindow is in a state where HUNs should be displayed above it. 368 */ shouldShowHUN()369 public boolean shouldShowHUN() { 370 return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN(); 371 } 372 373 /** 374 * Set the OverlayViewWindow to be in occluded or unoccluded state. When OverlayViewWindow is 375 * occluded, all views mounted to it that are not configured to be shown during occlusion will 376 * be hidden. 377 */ setOccluded(boolean occluded)378 public void setOccluded(boolean occluded) { 379 if (occluded) { 380 // Hide views before setting mIsOccluded to true so the regular hideView logic is used, 381 // not the one used during occlusion. 382 hideViewsForOcclusion(); 383 mIsOccluded = true; 384 } else { 385 mIsOccluded = false; 386 // show views after setting mIsOccluded to false so the regular showView logic is used, 387 // not the one used during occlusion. 388 showViewsHiddenForOcclusion(); 389 } 390 } 391 hideViewsForOcclusion()392 private void hideViewsForOcclusion() { 393 HashSet<OverlayViewController> viewsCurrentlyShowing = new HashSet<>( 394 mZOrderVisibleSortedMap.values()); 395 viewsCurrentlyShowing.forEach(overlayController -> { 396 if (!overlayController.shouldShowWhenOccluded()) { 397 hideView(overlayController, overlayController::hideInternal); 398 mViewsHiddenForOcclusion.add(overlayController); 399 } 400 }); 401 } 402 showViewsHiddenForOcclusion()403 private void showViewsHiddenForOcclusion() { 404 mViewsHiddenForOcclusion.forEach(overlayViewController -> { 405 showView(overlayViewController, overlayViewController::showInternal); 406 }); 407 mViewsHiddenForOcclusion.clear(); 408 } 409 debugLog()410 private void debugLog() { 411 if (!DEBUG) { 412 return; 413 } 414 415 Log.d(TAG, "mHighestZOrder: " + mHighestZOrder); 416 Log.d(TAG, "mZOrderVisibleSortedMap.size(): " + mZOrderVisibleSortedMap.size()); 417 Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap); 418 Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size()); 419 Log.d(TAG, "mZOrderMap: " + mZOrderMap); 420 Log.d(TAG, "mIsOccluded: " + mIsOccluded); 421 Log.d(TAG, "mViewsHiddenForOcclusion: " + mViewsHiddenForOcclusion); 422 Log.d(TAG, "mViewsHiddenForOcclusion.size(): " + mViewsHiddenForOcclusion.size()); 423 } 424 } 425