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