1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui; 16 17 import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; 18 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; 19 import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH; 20 import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; 21 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; 22 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 23 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 24 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 25 26 import static com.android.systemui.util.DumpUtilsKt.asIndenting; 27 28 import android.annotation.IdRes; 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.content.Context; 32 import android.content.pm.ActivityInfo; 33 import android.content.res.Configuration; 34 import android.content.res.Resources; 35 import android.graphics.Color; 36 import android.graphics.Paint; 37 import android.graphics.Path; 38 import android.graphics.PixelFormat; 39 import android.graphics.Point; 40 import android.graphics.Rect; 41 import android.graphics.drawable.Drawable; 42 import android.hardware.graphics.common.AlphaInterpretation; 43 import android.hardware.graphics.common.DisplayDecorationSupport; 44 import android.os.Handler; 45 import android.os.HandlerExecutor; 46 import android.os.SystemProperties; 47 import android.os.Trace; 48 import android.provider.Settings.Secure; 49 import android.util.DisplayUtils; 50 import android.util.IndentingPrintWriter; 51 import android.util.Log; 52 import android.util.Size; 53 import android.view.Display; 54 import android.view.DisplayCutout; 55 import android.view.DisplayCutout.BoundsPosition; 56 import android.view.DisplayInfo; 57 import android.view.Gravity; 58 import android.view.LayoutInflater; 59 import android.view.View; 60 import android.view.View.OnLayoutChangeListener; 61 import android.view.ViewGroup; 62 import android.view.ViewGroup.LayoutParams; 63 import android.view.ViewTreeObserver; 64 import android.view.WindowManager; 65 import android.widget.FrameLayout; 66 67 import androidx.annotation.VisibleForTesting; 68 69 import com.android.internal.util.Preconditions; 70 import com.android.settingslib.Utils; 71 import com.android.systemui.biometrics.AuthController; 72 import com.android.systemui.dagger.SysUISingleton; 73 import com.android.systemui.decor.CutoutDecorProviderFactory; 74 import com.android.systemui.decor.DebugRoundedCornerDelegate; 75 import com.android.systemui.decor.DebugRoundedCornerModel; 76 import com.android.systemui.decor.DecorProvider; 77 import com.android.systemui.decor.DecorProviderFactory; 78 import com.android.systemui.decor.DecorProviderKt; 79 import com.android.systemui.decor.FaceScanningProviderFactory; 80 import com.android.systemui.decor.OverlayWindow; 81 import com.android.systemui.decor.PrivacyDotDecorProviderFactory; 82 import com.android.systemui.decor.RoundedCornerDecorProviderFactory; 83 import com.android.systemui.decor.RoundedCornerResDelegateImpl; 84 import com.android.systemui.decor.ScreenDecorCommand; 85 import com.android.systemui.log.ScreenDecorationsLogger; 86 import com.android.systemui.qs.SettingObserver; 87 import com.android.systemui.settings.DisplayTracker; 88 import com.android.systemui.settings.UserTracker; 89 import com.android.systemui.statusbar.commandline.CommandRegistry; 90 import com.android.systemui.statusbar.events.PrivacyDotViewController; 91 import com.android.systemui.util.concurrency.DelayableExecutor; 92 import com.android.systemui.util.concurrency.ThreadFactory; 93 import com.android.systemui.util.settings.SecureSettings; 94 95 import kotlin.Pair; 96 97 import java.io.PrintWriter; 98 import java.util.ArrayList; 99 import java.util.List; 100 import java.util.Objects; 101 102 import javax.inject.Inject; 103 104 /** 105 * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) 106 * for antialiasing and emulation purposes. 107 */ 108 @SysUISingleton 109 public class ScreenDecorations implements CoreStartable, Dumpable { 110 private static final boolean DEBUG_LOGGING = false; 111 private static final String TAG = "ScreenDecorations"; 112 113 // Provide a way for factory to disable ScreenDecorations to run the Display tests. 114 private static final boolean DEBUG_DISABLE_SCREEN_DECORATIONS = 115 SystemProperties.getBoolean("debug.disable_screen_decorations", false); 116 private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS = 117 SystemProperties.getBoolean("debug.screenshot_rounded_corners", false); 118 private boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS; 119 private int mDebugColor = Color.RED; 120 121 private static final int[] DISPLAY_CUTOUT_IDS = { 122 R.id.display_cutout, 123 R.id.display_cutout_left, 124 R.id.display_cutout_right, 125 R.id.display_cutout_bottom 126 }; 127 private final ScreenDecorationsLogger mLogger; 128 129 private final AuthController mAuthController; 130 131 private DisplayTracker mDisplayTracker; 132 @VisibleForTesting 133 protected boolean mIsRegistered; 134 private final Context mContext; 135 private final CommandRegistry mCommandRegistry; 136 private final SecureSettings mSecureSettings; 137 @VisibleForTesting 138 DisplayTracker.Callback mDisplayListener; 139 private CameraAvailabilityListener mCameraListener; 140 private final UserTracker mUserTracker; 141 private final PrivacyDotViewController mDotViewController; 142 private final ThreadFactory mThreadFactory; 143 private final DecorProviderFactory mDotFactory; 144 private final FaceScanningProviderFactory mFaceScanningFactory; 145 public final int mFaceScanningViewId; 146 147 @VisibleForTesting 148 protected RoundedCornerResDelegateImpl mRoundedCornerResDelegate; 149 @VisibleForTesting 150 protected DecorProviderFactory mRoundedCornerFactory; 151 @VisibleForTesting 152 protected DebugRoundedCornerDelegate mDebugRoundedCornerDelegate = 153 new DebugRoundedCornerDelegate(); 154 protected DecorProviderFactory mDebugRoundedCornerFactory; 155 private CutoutDecorProviderFactory mCutoutFactory; 156 private int mProviderRefreshToken = 0; 157 @VisibleForTesting 158 protected OverlayWindow[] mOverlays = null; 159 @VisibleForTesting 160 ViewGroup mScreenDecorHwcWindow; 161 @VisibleForTesting 162 ScreenDecorHwcLayer mScreenDecorHwcLayer; 163 private WindowManager mWindowManager; 164 private int mRotation; 165 private SettingObserver mColorInversionSetting; 166 @Nullable 167 private DelayableExecutor mExecutor; 168 private Handler mHandler; 169 boolean mPendingConfigChange; 170 @VisibleForTesting 171 String mDisplayUniqueId; 172 private int mTintColor = Color.BLACK; 173 @VisibleForTesting 174 protected DisplayDecorationSupport mHwcScreenDecorationSupport; 175 private final Point mDisplaySize = new Point(); 176 @VisibleForTesting 177 protected DisplayInfo mDisplayInfo = new DisplayInfo(); 178 private DisplayCutout mDisplayCutout; 179 180 @VisibleForTesting showCameraProtection(@onNull Path protectionPath, @NonNull Rect bounds)181 protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { 182 if (mFaceScanningFactory.shouldShowFaceScanningAnim()) { 183 DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView( 184 mFaceScanningViewId); 185 if (overlay != null) { 186 mLogger.cameraProtectionBoundsForScanningOverlay(bounds); 187 overlay.setProtection(protectionPath, bounds); 188 overlay.enableShowProtection(true); 189 updateOverlayWindowVisibilityIfViewExists( 190 overlay.findViewById(mFaceScanningViewId)); 191 // immediately return, bc FaceScanningOverlay also renders the camera 192 // protection, so we don't need to show the camera protection in 193 // mScreenDecorHwcLayer or mCutoutViews 194 return; 195 } 196 } 197 198 if (mScreenDecorHwcLayer != null) { 199 mLogger.hwcLayerCameraProtectionBounds(bounds); 200 mScreenDecorHwcLayer.setProtection(protectionPath, bounds); 201 mScreenDecorHwcLayer.enableShowProtection(true); 202 return; 203 } 204 205 int setProtectionCnt = 0; 206 for (int id: DISPLAY_CUTOUT_IDS) { 207 final View view = getOverlayView(id); 208 if (!(view instanceof DisplayCutoutView)) { 209 continue; 210 } 211 ++setProtectionCnt; 212 final DisplayCutoutView dcv = (DisplayCutoutView) view; 213 mLogger.dcvCameraBounds(id, bounds); 214 dcv.setProtection(protectionPath, bounds); 215 dcv.enableShowProtection(true); 216 } 217 if (setProtectionCnt == 0) { 218 mLogger.cutoutViewNotInitialized(); 219 } 220 } 221 222 @VisibleForTesting hideCameraProtection()223 protected void hideCameraProtection() { 224 FaceScanningOverlay faceScanningOverlay = 225 (FaceScanningOverlay) getOverlayView(mFaceScanningViewId); 226 if (faceScanningOverlay != null) { 227 faceScanningOverlay.setHideOverlayRunnable(() -> { 228 Trace.beginSection("ScreenDecorations#hideOverlayRunnable"); 229 updateOverlayWindowVisibilityIfViewExists( 230 faceScanningOverlay.findViewById(mFaceScanningViewId)); 231 Trace.endSection(); 232 }); 233 faceScanningOverlay.enableShowProtection(false); 234 } 235 236 if (mScreenDecorHwcLayer != null) { 237 mScreenDecorHwcLayer.enableShowProtection(false); 238 return; 239 } 240 241 int setProtectionCnt = 0; 242 for (int id: DISPLAY_CUTOUT_IDS) { 243 final View view = getOverlayView(id); 244 if (!(view instanceof DisplayCutoutView)) { 245 continue; 246 } 247 ++setProtectionCnt; 248 ((DisplayCutoutView) view).enableShowProtection(false); 249 } 250 if (setProtectionCnt == 0) { 251 Log.e(TAG, "CutoutView not initialized hideCameraProtection"); 252 } 253 } 254 255 private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback = 256 new CameraAvailabilityListener.CameraTransitionCallback() { 257 @Override 258 public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { 259 mLogger.cameraProtectionEvent("onApplyCameraProtection"); 260 showCameraProtection(protectionPath, bounds); 261 } 262 263 @Override 264 public void onHideCameraProtection() { 265 mLogger.cameraProtectionEvent("onHideCameraProtection"); 266 hideCameraProtection(); 267 } 268 }; 269 270 @VisibleForTesting 271 PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener = 272 new PrivacyDotViewController.ShowingListener() { 273 @Override 274 public void onPrivacyDotShown(@Nullable View v) { 275 updateOverlayWindowVisibilityIfViewExists(v); 276 } 277 278 @Override 279 public void onPrivacyDotHidden(@Nullable View v) { 280 updateOverlayWindowVisibilityIfViewExists(v); 281 } 282 }; 283 284 @VisibleForTesting updateOverlayWindowVisibilityIfViewExists(@ullable View view)285 protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) { 286 if (view == null) { 287 return; 288 } 289 mExecutor.execute(() -> { 290 // We don't need to control the window visibility if rounded corners or cutout is drawn 291 // on sw layer since the overlay windows are always visible in this case. 292 if (mOverlays == null || !shouldOptimizeVisibility()) { 293 return; 294 } 295 Trace.beginSection("ScreenDecorations#updateOverlayWindowVisibilityIfViewExists"); 296 for (final OverlayWindow overlay : mOverlays) { 297 if (overlay == null) { 298 continue; 299 } 300 if (overlay.getView(view.getId()) != null) { 301 overlay.getRootView().setVisibility(getWindowVisibility(overlay, true)); 302 Trace.endSection(); 303 return; 304 } 305 } 306 Trace.endSection(); 307 }); 308 } 309 eq(DisplayDecorationSupport a, DisplayDecorationSupport b)310 private static boolean eq(DisplayDecorationSupport a, DisplayDecorationSupport b) { 311 if (a == null) return (b == null); 312 if (b == null) return false; 313 return a.format == b.format && a.alphaInterpretation == b.alphaInterpretation; 314 } 315 316 @Inject ScreenDecorations(Context context, SecureSettings secureSettings, CommandRegistry commandRegistry, UserTracker userTracker, DisplayTracker displayTracker, PrivacyDotViewController dotViewController, ThreadFactory threadFactory, PrivacyDotDecorProviderFactory dotFactory, FaceScanningProviderFactory faceScanningFactory, ScreenDecorationsLogger logger, AuthController authController)317 public ScreenDecorations(Context context, 318 SecureSettings secureSettings, 319 CommandRegistry commandRegistry, 320 UserTracker userTracker, 321 DisplayTracker displayTracker, 322 PrivacyDotViewController dotViewController, 323 ThreadFactory threadFactory, 324 PrivacyDotDecorProviderFactory dotFactory, 325 FaceScanningProviderFactory faceScanningFactory, 326 ScreenDecorationsLogger logger, 327 AuthController authController) { 328 mContext = context; 329 mSecureSettings = secureSettings; 330 mCommandRegistry = commandRegistry; 331 mUserTracker = userTracker; 332 mDisplayTracker = displayTracker; 333 mDotViewController = dotViewController; 334 mThreadFactory = threadFactory; 335 mDotFactory = dotFactory; 336 mFaceScanningFactory = faceScanningFactory; 337 mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim; 338 mLogger = logger; 339 mAuthController = authController; 340 } 341 342 343 private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { 344 @Override 345 public void onFaceSensorLocationChanged() { 346 mLogger.onSensorLocationChanged(); 347 if (mExecutor != null) { 348 mExecutor.execute( 349 () -> updateOverlayProviderViews( 350 new Integer[]{mFaceScanningViewId})); 351 } 352 } 353 }; 354 355 private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> { 356 // If we are exiting debug mode, we can set it (false) and bail, otherwise we will 357 // ensure that debug mode is set 358 if (cmd.getDebug() != null && !cmd.getDebug()) { 359 setDebug(false); 360 return; 361 } else { 362 // setDebug is idempotent 363 setDebug(true); 364 } 365 366 if (cmd.getColor() != null) { 367 mDebugColor = cmd.getColor(); 368 mExecutor.execute(() -> { 369 if (mScreenDecorHwcLayer != null) { 370 mScreenDecorHwcLayer.setDebugColor(cmd.getColor()); 371 } 372 updateColorInversionDefault(); 373 }); 374 } 375 376 DebugRoundedCornerModel roundedTop = null; 377 DebugRoundedCornerModel roundedBottom = null; 378 if (cmd.getRoundedTop() != null) { 379 roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel(); 380 } 381 if (cmd.getRoundedBottom() != null) { 382 roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel(); 383 } 384 if (roundedTop != null || roundedBottom != null) { 385 mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom); 386 mExecutor.execute(() -> { 387 removeAllOverlays(); 388 removeHwcOverlay(); 389 setupDecorations(); 390 }); 391 } 392 }; 393 394 @Override start()395 public void start() { 396 if (DEBUG_DISABLE_SCREEN_DECORATIONS) { 397 Log.i(TAG, "ScreenDecorations is disabled"); 398 return; 399 } 400 mHandler = mThreadFactory.buildHandlerOnNewThread("ScreenDecorations"); 401 mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler); 402 mExecutor.execute(this::startOnScreenDecorationsThread); 403 mDotViewController.setUiExecutor(mExecutor); 404 mAuthController.addCallback(mAuthControllerCallback); 405 mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME, 406 () -> new ScreenDecorCommand(mScreenDecorCommandCallback)); 407 } 408 409 /** 410 * Change the value of {@link ScreenDecorations#mDebug}. This operation is heavyweight, since 411 * it requires essentially re-init-ing this screen decorations process with the debug 412 * information taken into account. 413 */ 414 @VisibleForTesting setDebug(boolean debug)415 protected void setDebug(boolean debug) { 416 if (mDebug == debug) { 417 return; 418 } 419 420 mDebug = debug; 421 if (!mDebug) { 422 mDebugRoundedCornerDelegate.removeDebugState(); 423 } 424 425 mExecutor.execute(() -> { 426 // Re-trigger all of the screen decorations setup here so that the debug values 427 // can be picked up 428 removeAllOverlays(); 429 removeHwcOverlay(); 430 startOnScreenDecorationsThread(); 431 updateColorInversionDefault(); 432 }); 433 } 434 isPrivacyDotEnabled()435 private boolean isPrivacyDotEnabled() { 436 return mDotFactory.getHasProviders(); 437 } 438 439 @NonNull 440 @VisibleForTesting getProviders(boolean hasHwLayer)441 protected List<DecorProvider> getProviders(boolean hasHwLayer) { 442 List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders()); 443 decorProviders.addAll(mFaceScanningFactory.getProviders()); 444 if (!hasHwLayer) { 445 if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) { 446 decorProviders.addAll(mDebugRoundedCornerFactory.getProviders()); 447 } else { 448 decorProviders.addAll(mRoundedCornerFactory.getProviders()); 449 } 450 decorProviders.addAll(mCutoutFactory.getProviders()); 451 } 452 return decorProviders; 453 } 454 455 /** 456 * Check that newProviders is the same list with decorProviders inside mOverlay. 457 * @param newProviders expected comparing DecorProviders 458 * @return true if same provider list 459 */ 460 @VisibleForTesting hasSameProviders(@onNull List<DecorProvider> newProviders)461 boolean hasSameProviders(@NonNull List<DecorProvider> newProviders) { 462 final ArrayList<Integer> overlayViewIds = new ArrayList<>(); 463 if (mOverlays != null) { 464 for (OverlayWindow overlay : mOverlays) { 465 if (overlay == null) { 466 continue; 467 } 468 overlayViewIds.addAll(overlay.getViewIds()); 469 } 470 } 471 if (overlayViewIds.size() != newProviders.size()) { 472 return false; 473 } 474 475 for (DecorProvider provider: newProviders) { 476 if (!overlayViewIds.contains(provider.getViewId())) { 477 return false; 478 } 479 } 480 return true; 481 } 482 startOnScreenDecorationsThread()483 private void startOnScreenDecorationsThread() { 484 Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread"); 485 mWindowManager = mContext.getSystemService(WindowManager.class); 486 mContext.getDisplay().getDisplayInfo(mDisplayInfo); 487 mRotation = mDisplayInfo.rotation; 488 mDisplaySize.x = mDisplayInfo.getNaturalWidth(); 489 mDisplaySize.y = mDisplayInfo.getNaturalHeight(); 490 mDisplayUniqueId = mDisplayInfo.uniqueId; 491 mDisplayCutout = mDisplayInfo.displayCutout; 492 mRoundedCornerResDelegate = 493 new RoundedCornerResDelegateImpl(mContext.getResources(), mDisplayUniqueId); 494 mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( 495 getPhysicalPixelDisplaySizeRatio()); 496 mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); 497 mDebugRoundedCornerFactory = 498 new RoundedCornerDecorProviderFactory(mDebugRoundedCornerDelegate); 499 mCutoutFactory = getCutoutFactory(); 500 mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); 501 updateHwLayerRoundedCornerDrawable(); 502 setupDecorations(); 503 setupCameraListener(); 504 505 mDisplayListener = new DisplayTracker.Callback() { 506 @Override 507 public void onDisplayChanged(int displayId) { 508 mContext.getDisplay().getDisplayInfo(mDisplayInfo); 509 final int newRotation = mDisplayInfo.rotation; 510 if ((mOverlays != null || mScreenDecorHwcWindow != null) 511 && (mRotation != newRotation 512 || displaySizeChanged(mDisplaySize, mDisplayInfo))) { 513 final Point newSize = new Point(); 514 newSize.x = mDisplayInfo.getNaturalWidth(); 515 newSize.y = mDisplayInfo.getNaturalHeight(); 516 // We cannot immediately update the orientation. Otherwise 517 // WindowManager is still deferring layout until it has finished dispatching 518 // the config changes, which may cause divergence between what we draw 519 // (new orientation), and where we are placed on the screen (old orientation). 520 // Instead we wait until either: 521 // - we are trying to redraw. This because WM resized our window and told us to. 522 // - the config change has been dispatched, so WM is no longer deferring layout. 523 mPendingConfigChange = true; 524 if (mRotation != newRotation) { 525 mLogger.logRotationChangeDeferred(mRotation, newRotation); 526 } 527 if (!mDisplaySize.equals(newSize)) { 528 mLogger.logDisplaySizeChanged(mDisplaySize, newSize); 529 } 530 531 if (mOverlays != null) { 532 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { 533 if (mOverlays[i] != null) { 534 final ViewGroup overlayView = mOverlays[i].getRootView(); 535 overlayView.getViewTreeObserver().addOnPreDrawListener( 536 new RestartingPreDrawListener( 537 overlayView, i, newRotation, newSize)); 538 } 539 } 540 } 541 542 if (mScreenDecorHwcWindow != null) { 543 mScreenDecorHwcWindow.getViewTreeObserver().addOnPreDrawListener( 544 new RestartingPreDrawListener( 545 mScreenDecorHwcWindow, 546 -1, // Pass -1 for views with no specific position. 547 newRotation, newSize)); 548 } 549 if (mScreenDecorHwcLayer != null) { 550 mScreenDecorHwcLayer.pendingConfigChange = true; 551 } 552 } 553 554 final String newUniqueId = mDisplayInfo.uniqueId; 555 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { 556 mDisplayUniqueId = newUniqueId; 557 final DisplayDecorationSupport newScreenDecorationSupport = 558 mContext.getDisplay().getDisplayDecorationSupport(); 559 560 mRoundedCornerResDelegate.updateDisplayUniqueId(newUniqueId, null); 561 562 // When providers or the value of mSupportHwcScreenDecoration is changed, 563 // re-setup the whole screen decoration. 564 if (!hasSameProviders(getProviders(newScreenDecorationSupport != null)) 565 || !eq(newScreenDecorationSupport, mHwcScreenDecorationSupport)) { 566 mHwcScreenDecorationSupport = newScreenDecorationSupport; 567 removeAllOverlays(); 568 setupDecorations(); 569 return; 570 } 571 } 572 } 573 }; 574 mDisplayTracker.addDisplayChangeCallback(mDisplayListener, new HandlerExecutor(mHandler)); 575 updateConfiguration(); 576 Trace.endSection(); 577 } 578 579 @VisibleForTesting 580 @Nullable getOverlayView(@dRes int id)581 View getOverlayView(@IdRes int id) { 582 if (mOverlays == null) { 583 return null; 584 } 585 586 for (final OverlayWindow overlay : mOverlays) { 587 if (overlay == null) { 588 continue; 589 } 590 final View view = overlay.getView(id); 591 if (view != null) { 592 return view; 593 } 594 } 595 return null; 596 } 597 removeRedundantOverlayViews(@onNull List<DecorProvider> decorProviders)598 private void removeRedundantOverlayViews(@NonNull List<DecorProvider> decorProviders) { 599 if (mOverlays == null) { 600 return; 601 } 602 int[] viewIds = decorProviders.stream().mapToInt(DecorProvider::getViewId).toArray(); 603 for (final OverlayWindow overlay : mOverlays) { 604 if (overlay == null) { 605 continue; 606 } 607 overlay.removeRedundantViews(viewIds); 608 } 609 } 610 removeOverlayView(@dRes int id)611 private void removeOverlayView(@IdRes int id) { 612 if (mOverlays == null) { 613 return; 614 } 615 616 for (final OverlayWindow overlay : mOverlays) { 617 if (overlay == null) { 618 continue; 619 } 620 621 overlay.removeView(id); 622 } 623 } 624 setupDecorations()625 private void setupDecorations() { 626 Trace.beginSection("ScreenDecorations#setupDecorations"); 627 setupDecorationsInner(); 628 Trace.endSection(); 629 } 630 setupDecorationsInner()631 private void setupDecorationsInner() { 632 if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled() 633 || mFaceScanningFactory.getHasProviders()) { 634 635 List<DecorProvider> decorProviders = getProviders(mHwcScreenDecorationSupport != null); 636 removeRedundantOverlayViews(decorProviders); 637 638 if (mHwcScreenDecorationSupport != null) { 639 createHwcOverlay(); 640 } else { 641 removeHwcOverlay(); 642 } 643 644 boolean[] hasCreatedOverlay = new boolean[BOUNDS_POSITION_LENGTH]; 645 final boolean shouldOptimizeVisibility = shouldOptimizeVisibility(); 646 Integer bound; 647 while ((bound = DecorProviderKt.getProperBound(decorProviders)) != null) { 648 hasCreatedOverlay[bound] = true; 649 Pair<List<DecorProvider>, List<DecorProvider>> pair = 650 DecorProviderKt.partitionAlignedBound(decorProviders, bound); 651 decorProviders = pair.getSecond(); 652 createOverlay(bound, pair.getFirst(), shouldOptimizeVisibility); 653 } 654 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { 655 if (!hasCreatedOverlay[i]) { 656 removeOverlay(i); 657 } 658 } 659 660 if (shouldOptimizeVisibility) { 661 mDotViewController.setShowingListener(mPrivacyDotShowingListener); 662 } else { 663 mDotViewController.setShowingListener(null); 664 } 665 final View tl, tr, bl, br; 666 if ((tl = getOverlayView(R.id.privacy_dot_top_left_container)) != null 667 && (tr = getOverlayView(R.id.privacy_dot_top_right_container)) != null 668 && (bl = getOverlayView(R.id.privacy_dot_bottom_left_container)) != null 669 && (br = getOverlayView(R.id.privacy_dot_bottom_right_container)) != null) { 670 // Overlays have been created, send the dots to the controller 671 //TODO: need a better way to do this 672 mDotViewController.initialize(tl, tr, bl, br); 673 } 674 } else { 675 removeAllOverlays(); 676 removeHwcOverlay(); 677 } 678 679 if (hasOverlays() || hasHwcOverlay()) { 680 if (mIsRegistered) { 681 return; 682 } 683 684 // Watch color inversion and invert the overlay as needed. 685 if (mColorInversionSetting == null) { 686 mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler, 687 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 688 mUserTracker.getUserId()) { 689 @Override 690 protected void handleValueChanged(int value, boolean observedChange) { 691 updateColorInversion(value); 692 } 693 }; 694 } 695 mColorInversionSetting.setListening(true); 696 mColorInversionSetting.onChange(false); 697 updateColorInversion(mColorInversionSetting.getValue()); 698 699 mUserTracker.addCallback(mUserChangedCallback, mExecutor); 700 mIsRegistered = true; 701 } else { 702 if (mColorInversionSetting != null) { 703 mColorInversionSetting.setListening(false); 704 } 705 706 mUserTracker.removeCallback(mUserChangedCallback); 707 mIsRegistered = false; 708 } 709 } 710 711 // For unit test to override getCutoutFactory()712 protected CutoutDecorProviderFactory getCutoutFactory() { 713 return new CutoutDecorProviderFactory(mContext.getResources(), 714 mContext.getDisplay()); 715 } 716 717 @VisibleForTesting hasOverlays()718 boolean hasOverlays() { 719 if (mOverlays == null) { 720 return false; 721 } 722 723 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { 724 if (mOverlays[i] != null) { 725 return true; 726 } 727 } 728 mOverlays = null; 729 return false; 730 } 731 removeAllOverlays()732 private void removeAllOverlays() { 733 if (mOverlays == null) { 734 return; 735 } 736 737 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { 738 if (mOverlays[i] != null) { 739 removeOverlay(i); 740 } 741 } 742 mOverlays = null; 743 } 744 removeOverlay(@oundsPosition int pos)745 private void removeOverlay(@BoundsPosition int pos) { 746 if (mOverlays == null || mOverlays[pos] == null) { 747 return; 748 } 749 mWindowManager.removeViewImmediate(mOverlays[pos].getRootView()); 750 mOverlays[pos] = null; 751 } 752 753 @View.Visibility getWindowVisibility(@onNull OverlayWindow overlay, boolean shouldOptimizeVisibility)754 private int getWindowVisibility(@NonNull OverlayWindow overlay, 755 boolean shouldOptimizeVisibility) { 756 if (!shouldOptimizeVisibility) { 757 // All overlays have visible views so there's no need to optimize visibility. 758 // For example, the rounded corners could exist in each overlay and since the rounded 759 // corners are always visible, there's no need to optimize visibility. 760 return View.VISIBLE; 761 } 762 763 // Optimize if it's just the privacy dot & face scanning animation, since the privacy 764 // dot and face scanning overlay aren't always visible. 765 int[] ids = { 766 R.id.privacy_dot_top_left_container, 767 R.id.privacy_dot_top_right_container, 768 R.id.privacy_dot_bottom_left_container, 769 R.id.privacy_dot_bottom_right_container, 770 mFaceScanningViewId 771 }; 772 for (int id: ids) { 773 final View notAlwaysVisibleViews = overlay.getView(id); 774 if (notAlwaysVisibleViews != null 775 && notAlwaysVisibleViews.getVisibility() == View.VISIBLE) { 776 // Overlay is VISIBLE if one the views inside this overlay is VISIBLE 777 return View.VISIBLE; 778 } 779 } 780 781 // Only non-visible views in this overlay, so set overlay to INVISIBLE 782 return View.INVISIBLE; 783 } 784 createOverlay( @oundsPosition int pos, @NonNull List<DecorProvider> decorProviders, boolean shouldOptimizeVisibility)785 private void createOverlay( 786 @BoundsPosition int pos, 787 @NonNull List<DecorProvider> decorProviders, 788 boolean shouldOptimizeVisibility) { 789 if (mOverlays == null) { 790 mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH]; 791 } 792 793 if (mOverlays[pos] != null) { 794 initOverlay(mOverlays[pos], decorProviders, shouldOptimizeVisibility); 795 return; 796 } 797 mOverlays[pos] = new OverlayWindow(mContext); 798 initOverlay(mOverlays[pos], decorProviders, shouldOptimizeVisibility); 799 final ViewGroup overlayView = mOverlays[pos].getRootView(); 800 overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 801 overlayView.setAlpha(0); 802 overlayView.setForceDarkAllowed(false); 803 804 mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); 805 806 overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() { 807 @Override 808 public void onLayoutChange(View v, int left, int top, int right, int bottom, 809 int oldLeft, int oldTop, int oldRight, int oldBottom) { 810 overlayView.removeOnLayoutChangeListener(this); 811 overlayView.animate() 812 .alpha(1) 813 .setDuration(1000) 814 .start(); 815 } 816 }); 817 818 overlayView.getRootView().getViewTreeObserver().addOnPreDrawListener( 819 new ValidatingPreDrawListener(overlayView.getRootView())); 820 } 821 hasHwcOverlay()822 private boolean hasHwcOverlay() { 823 return mScreenDecorHwcWindow != null; 824 } 825 removeHwcOverlay()826 private void removeHwcOverlay() { 827 if (mScreenDecorHwcWindow == null) { 828 return; 829 } 830 mWindowManager.removeViewImmediate(mScreenDecorHwcWindow); 831 mScreenDecorHwcWindow = null; 832 mScreenDecorHwcLayer = null; 833 } 834 createHwcOverlay()835 private void createHwcOverlay() { 836 if (mScreenDecorHwcWindow != null) { 837 return; 838 } 839 mScreenDecorHwcWindow = (ViewGroup) LayoutInflater.from(mContext).inflate( 840 R.layout.screen_decor_hwc_layer, null); 841 mScreenDecorHwcLayer = 842 new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport, mDebug); 843 mScreenDecorHwcWindow.addView(mScreenDecorHwcLayer, new FrameLayout.LayoutParams( 844 MATCH_PARENT, MATCH_PARENT, Gravity.TOP | Gravity.START)); 845 mWindowManager.addView(mScreenDecorHwcWindow, getHwcWindowLayoutParams()); 846 updateHwLayerRoundedCornerExistAndSize(); 847 updateHwLayerRoundedCornerDrawable(); 848 mScreenDecorHwcWindow.getViewTreeObserver().addOnPreDrawListener( 849 new ValidatingPreDrawListener(mScreenDecorHwcWindow)); 850 } 851 852 /** 853 * Init OverlayWindow with decorProviders 854 */ initOverlay( @onNull OverlayWindow overlay, @NonNull List<DecorProvider> decorProviders, boolean shouldOptimizeVisibility)855 private void initOverlay( 856 @NonNull OverlayWindow overlay, 857 @NonNull List<DecorProvider> decorProviders, 858 boolean shouldOptimizeVisibility) { 859 if (!overlay.hasSameProviders(decorProviders)) { 860 decorProviders.forEach(provider -> { 861 if (overlay.getView(provider.getViewId()) != null) { 862 return; 863 } 864 removeOverlayView(provider.getViewId()); 865 overlay.addDecorProvider(provider, mRotation, mTintColor); 866 }); 867 } 868 // Use visibility of privacy dot views & face scanning view to determine the overlay's 869 // visibility if the screen decoration SW layer overlay isn't persistently showing 870 // (ie: rounded corners always showing in SW layer) 871 overlay.getRootView().setVisibility(getWindowVisibility(overlay, shouldOptimizeVisibility)); 872 } 873 874 @VisibleForTesting getWindowLayoutParams(@oundsPosition int pos)875 WindowManager.LayoutParams getWindowLayoutParams(@BoundsPosition int pos) { 876 final WindowManager.LayoutParams lp = getWindowLayoutBaseParams(); 877 lp.width = getWidthLayoutParamByPos(pos); 878 lp.height = getHeightLayoutParamByPos(pos); 879 lp.setTitle(getWindowTitleByPos(pos)); 880 lp.gravity = getOverlayWindowGravity(pos); 881 return lp; 882 } 883 getHwcWindowLayoutParams()884 private WindowManager.LayoutParams getHwcWindowLayoutParams() { 885 final WindowManager.LayoutParams lp = getWindowLayoutBaseParams(); 886 lp.width = MATCH_PARENT; 887 lp.height = MATCH_PARENT; 888 lp.setTitle("ScreenDecorHwcOverlay"); 889 lp.gravity = Gravity.TOP | Gravity.START; 890 if (!mDebug) { 891 lp.setColorMode(ActivityInfo.COLOR_MODE_A8); 892 } 893 return lp; 894 } 895 getWindowLayoutBaseParams()896 private WindowManager.LayoutParams getWindowLayoutBaseParams() { 897 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 898 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 899 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 900 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 901 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 902 | WindowManager.LayoutParams.FLAG_SLIPPERY 903 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 904 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, 905 PixelFormat.TRANSLUCENT); 906 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS 907 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 908 909 // FLAG_SLIPPERY can only be set by trusted overlays 910 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 911 912 if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) { 913 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; 914 } 915 916 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 917 lp.setFitInsetsTypes(0 /* types */); 918 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; 919 return lp; 920 } 921 getWidthLayoutParamByPos(@oundsPosition int pos)922 private int getWidthLayoutParamByPos(@BoundsPosition int pos) { 923 final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); 924 return rotatedPos == BOUNDS_POSITION_TOP || rotatedPos == BOUNDS_POSITION_BOTTOM 925 ? MATCH_PARENT : WRAP_CONTENT; 926 } 927 getHeightLayoutParamByPos(@oundsPosition int pos)928 private int getHeightLayoutParamByPos(@BoundsPosition int pos) { 929 final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); 930 return rotatedPos == BOUNDS_POSITION_TOP || rotatedPos == BOUNDS_POSITION_BOTTOM 931 ? WRAP_CONTENT : MATCH_PARENT; 932 } 933 getWindowTitleByPos(@oundsPosition int pos)934 private static String getWindowTitleByPos(@BoundsPosition int pos) { 935 switch (pos) { 936 case BOUNDS_POSITION_LEFT: 937 return "ScreenDecorOverlayLeft"; 938 case BOUNDS_POSITION_TOP: 939 return "ScreenDecorOverlay"; 940 case BOUNDS_POSITION_RIGHT: 941 return "ScreenDecorOverlayRight"; 942 case BOUNDS_POSITION_BOTTOM: 943 return "ScreenDecorOverlayBottom"; 944 default: 945 throw new IllegalArgumentException("unknown bound position: " + pos); 946 } 947 } 948 displaySizeChanged(Point size, DisplayInfo info)949 private static boolean displaySizeChanged(Point size, DisplayInfo info) { 950 return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight(); 951 } 952 getOverlayWindowGravity(@oundsPosition int pos)953 private int getOverlayWindowGravity(@BoundsPosition int pos) { 954 final int rotated = getBoundPositionFromRotation(pos, mRotation); 955 switch (rotated) { 956 case BOUNDS_POSITION_TOP: 957 return Gravity.TOP; 958 case BOUNDS_POSITION_BOTTOM: 959 return Gravity.BOTTOM; 960 case BOUNDS_POSITION_LEFT: 961 return Gravity.LEFT; 962 case BOUNDS_POSITION_RIGHT: 963 return Gravity.RIGHT; 964 default: 965 throw new IllegalArgumentException("unknown bound position: " + pos); 966 } 967 } 968 969 @VisibleForTesting getBoundPositionFromRotation(@oundsPosition int pos, int rotation)970 static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) { 971 return (pos - rotation) < 0 972 ? pos - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH 973 : pos - rotation; 974 } 975 setupCameraListener()976 private void setupCameraListener() { 977 // TODO(b/238143614) Support dual screen camera protection 978 Resources res = mContext.getResources(); 979 boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection); 980 if (enabled) { 981 mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mExecutor); 982 mCameraListener.addTransitionCallback(mCameraTransitionCallback); 983 mCameraListener.startListening(); 984 } 985 } 986 987 private final UserTracker.Callback mUserChangedCallback = 988 new UserTracker.Callback() { 989 @Override 990 public void onUserChanged(int newUser, @NonNull Context userContext) { 991 mLogger.logUserSwitched(newUser); 992 // update color inversion setting to the new user 993 mColorInversionSetting.setUserId(newUser); 994 updateColorInversion(mColorInversionSetting.getValue()); 995 } 996 }; 997 998 /** 999 * Use the current value of {@link ScreenDecorations#mColorInversionSetting} and passes it 1000 * to {@link ScreenDecorations#updateColorInversion} 1001 */ updateColorInversionDefault()1002 private void updateColorInversionDefault() { 1003 int inversion = 0; 1004 if (mColorInversionSetting != null) { 1005 inversion = mColorInversionSetting.getValue(); 1006 } 1007 1008 updateColorInversion(inversion); 1009 } 1010 1011 /** 1012 * Update the tint color of screen decoration assets. Defaults to Color.BLACK. In the case of 1013 * a color inversion being set, use Color.WHITE (which inverts to black). 1014 * 1015 * When {@link ScreenDecorations#mDebug} is {@code true}, this value is updated to use 1016 * {@link ScreenDecorations#mDebugColor}, and does not handle inversion. 1017 * 1018 * @param colorsInvertedValue if non-zero, assume that colors are inverted, and use Color.WHITE 1019 * for screen decoration tint 1020 */ updateColorInversion(int colorsInvertedValue)1021 private void updateColorInversion(int colorsInvertedValue) { 1022 mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK; 1023 if (mDebug) { 1024 mTintColor = mDebugColor; 1025 mDebugRoundedCornerDelegate.setColor(mTintColor); 1026 //TODO(b/285941724): update the hwc layer color here too (or disable it in debug mode) 1027 } 1028 1029 updateOverlayProviderViews(new Integer[] { 1030 mFaceScanningViewId, 1031 R.id.display_cutout, 1032 R.id.display_cutout_left, 1033 R.id.display_cutout_right, 1034 R.id.display_cutout_bottom, 1035 R.id.rounded_corner_top_left, 1036 R.id.rounded_corner_top_right, 1037 R.id.rounded_corner_bottom_left, 1038 R.id.rounded_corner_bottom_right 1039 }); 1040 } 1041 1042 @VisibleForTesting getPhysicalPixelDisplaySizeRatio()1043 float getPhysicalPixelDisplaySizeRatio() { 1044 mContext.getDisplay().getDisplayInfo(mDisplayInfo); 1045 final Display.Mode maxDisplayMode = 1046 DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes); 1047 if (maxDisplayMode == null) { 1048 return 1f; 1049 } 1050 return DisplayUtils.getPhysicalPixelDisplaySizeRatio( 1051 maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(), 1052 mDisplayInfo.getNaturalWidth(), mDisplayInfo.getNaturalHeight()); 1053 } 1054 1055 @Override onConfigurationChanged(Configuration newConfig)1056 public void onConfigurationChanged(Configuration newConfig) { 1057 if (DEBUG_DISABLE_SCREEN_DECORATIONS) { 1058 Log.i(TAG, "ScreenDecorations is disabled"); 1059 return; 1060 } 1061 1062 mExecutor.execute(() -> { 1063 Trace.beginSection("ScreenDecorations#onConfigurationChanged"); 1064 int oldRotation = mRotation; 1065 mPendingConfigChange = false; 1066 updateConfiguration(); 1067 if (oldRotation != mRotation) { 1068 mLogger.logRotationChanged(oldRotation, mRotation); 1069 } 1070 setupDecorations(); 1071 if (mOverlays != null) { 1072 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(), 1073 // which ensures that the forced seamless rotation will end, even if we updated 1074 // the rotation before window manager was ready (and was still waiting for sending 1075 // the updated rotation). 1076 updateLayoutParams(); 1077 } 1078 Trace.endSection(); 1079 }); 1080 } 1081 alphaInterpretationToString(int alpha)1082 private static String alphaInterpretationToString(int alpha) { 1083 switch (alpha) { 1084 case AlphaInterpretation.COVERAGE: return "COVERAGE"; 1085 case AlphaInterpretation.MASK: return "MASK"; 1086 default: return "Unknown: " + alpha; 1087 } 1088 } 1089 1090 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)1091 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 1092 pw.println("ScreenDecorations state:"); 1093 IndentingPrintWriter ipw = asIndenting(pw); 1094 ipw.increaseIndent(); 1095 ipw.println("DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS); 1096 if (DEBUG_DISABLE_SCREEN_DECORATIONS) { 1097 return; 1098 } 1099 ipw.println("mDebug:" + mDebug); 1100 1101 ipw.println("mIsPrivacyDotEnabled:" + isPrivacyDotEnabled()); 1102 ipw.println("shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility()); 1103 final boolean supportsShowingFaceScanningAnim = mFaceScanningFactory.getHasProviders(); 1104 ipw.println("supportsShowingFaceScanningAnim:" + supportsShowingFaceScanningAnim); 1105 if (supportsShowingFaceScanningAnim) { 1106 ipw.increaseIndent(); 1107 ipw.println("canShowFaceScanningAnim:" 1108 + mFaceScanningFactory.canShowFaceScanningAnim()); 1109 ipw.println("shouldShowFaceScanningAnim (at time dump was taken):" 1110 + mFaceScanningFactory.shouldShowFaceScanningAnim()); 1111 ipw.decreaseIndent(); 1112 } 1113 FaceScanningOverlay faceScanningOverlay = 1114 (FaceScanningOverlay) getOverlayView(mFaceScanningViewId); 1115 if (faceScanningOverlay != null) { 1116 faceScanningOverlay.dump(ipw); 1117 } 1118 ipw.println("mPendingConfigChange:" + mPendingConfigChange); 1119 if (mHwcScreenDecorationSupport != null) { 1120 ipw.increaseIndent(); 1121 ipw.println("mHwcScreenDecorationSupport:"); 1122 ipw.increaseIndent(); 1123 ipw.println("format=" 1124 + PixelFormat.formatToString(mHwcScreenDecorationSupport.format)); 1125 ipw.println("alphaInterpretation=" 1126 + alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation)); 1127 ipw.decreaseIndent(); 1128 ipw.decreaseIndent(); 1129 } else { 1130 ipw.increaseIndent(); 1131 pw.println("mHwcScreenDecorationSupport: null"); 1132 ipw.decreaseIndent(); 1133 } 1134 if (mScreenDecorHwcLayer != null) { 1135 ipw.increaseIndent(); 1136 mScreenDecorHwcLayer.dump(ipw); 1137 ipw.decreaseIndent(); 1138 } else { 1139 ipw.println("mScreenDecorHwcLayer: null"); 1140 } 1141 if (mOverlays != null) { 1142 ipw.println("mOverlays(left,top,right,bottom)=(" 1143 + (mOverlays[BOUNDS_POSITION_LEFT] != null) + "," 1144 + (mOverlays[BOUNDS_POSITION_TOP] != null) + "," 1145 + (mOverlays[BOUNDS_POSITION_RIGHT] != null) + "," 1146 + (mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")"); 1147 1148 for (int i = BOUNDS_POSITION_LEFT; i < BOUNDS_POSITION_LENGTH; i++) { 1149 if (mOverlays[i] != null) { 1150 mOverlays[i].dump(pw, getWindowTitleByPos(i)); 1151 } 1152 } 1153 } 1154 mRoundedCornerResDelegate.dump(pw, args); 1155 mDebugRoundedCornerDelegate.dump(pw); 1156 } 1157 1158 @VisibleForTesting updateConfiguration()1159 void updateConfiguration() { 1160 Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), 1161 "must call on " + mHandler.getLooper().getThread() 1162 + ", but was " + Thread.currentThread()); 1163 1164 mContext.getDisplay().getDisplayInfo(mDisplayInfo); 1165 final int newRotation = mDisplayInfo.rotation; 1166 if (mRotation != newRotation) { 1167 mDotViewController.setNewRotation(newRotation); 1168 } 1169 final DisplayCutout newCutout = mDisplayInfo.displayCutout; 1170 1171 if (!mPendingConfigChange 1172 && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo) 1173 || !Objects.equals(newCutout, mDisplayCutout))) { 1174 mRotation = newRotation; 1175 mDisplaySize.x = mDisplayInfo.getNaturalWidth(); 1176 mDisplaySize.y = mDisplayInfo.getNaturalHeight(); 1177 mDisplayCutout = newCutout; 1178 float ratio = getPhysicalPixelDisplaySizeRatio(); 1179 mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio); 1180 mDebugRoundedCornerDelegate.setPhysicalPixelDisplaySizeRatio(ratio); 1181 if (mScreenDecorHwcLayer != null) { 1182 mScreenDecorHwcLayer.pendingConfigChange = false; 1183 mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId); 1184 updateHwLayerRoundedCornerExistAndSize(); 1185 updateHwLayerRoundedCornerDrawable(); 1186 } 1187 updateLayoutParams(); 1188 1189 // update all provider views inside overlay 1190 updateOverlayProviderViews(null); 1191 } 1192 1193 FaceScanningOverlay faceScanningOverlay = 1194 (FaceScanningOverlay) getOverlayView(mFaceScanningViewId); 1195 if (faceScanningOverlay != null) { 1196 faceScanningOverlay.setFaceScanningAnimColor( 1197 Utils.getColorAttrDefaultColor(faceScanningOverlay.getContext(), 1198 com.android.systemui.R.attr.wallpaperTextColorAccent)); 1199 } 1200 } 1201 hasRoundedCorners()1202 private boolean hasRoundedCorners() { 1203 return mRoundedCornerFactory.getHasProviders() 1204 || mDebugRoundedCornerFactory.getHasProviders(); 1205 } 1206 shouldOptimizeVisibility()1207 private boolean shouldOptimizeVisibility() { 1208 return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders()) 1209 && (mHwcScreenDecorationSupport != null 1210 || (!hasRoundedCorners() && !shouldDrawCutout()) 1211 ); 1212 } 1213 shouldDrawCutout()1214 private boolean shouldDrawCutout() { 1215 return mCutoutFactory.getHasProviders(); 1216 } 1217 shouldDrawCutout(Context context)1218 static boolean shouldDrawCutout(Context context) { 1219 return DisplayCutout.getFillBuiltInDisplayCutout( 1220 context.getResources(), context.getDisplay().getUniqueId()); 1221 } 1222 1223 @VisibleForTesting updateOverlayProviderViews(@ullable Integer[] filterIds)1224 void updateOverlayProviderViews(@Nullable Integer[] filterIds) { 1225 if (mOverlays == null) { 1226 return; 1227 } 1228 ++mProviderRefreshToken; 1229 for (final OverlayWindow overlay: mOverlays) { 1230 if (overlay == null) { 1231 continue; 1232 } 1233 overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation, mTintColor, 1234 mDisplayUniqueId); 1235 } 1236 } 1237 updateLayoutParams()1238 private void updateLayoutParams() { 1239 //ToDo: We should skip unnecessary call to update view layout. 1240 Trace.beginSection("ScreenDecorations#updateLayoutParams"); 1241 if (mScreenDecorHwcWindow != null) { 1242 mWindowManager.updateViewLayout(mScreenDecorHwcWindow, getHwcWindowLayoutParams()); 1243 } 1244 1245 if (mOverlays != null) { 1246 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { 1247 if (mOverlays[i] == null) { 1248 continue; 1249 } 1250 mWindowManager.updateViewLayout( 1251 mOverlays[i].getRootView(), getWindowLayoutParams(i)); 1252 } 1253 } 1254 Trace.endSection(); 1255 } 1256 updateHwLayerRoundedCornerDrawable()1257 private void updateHwLayerRoundedCornerDrawable() { 1258 if (mScreenDecorHwcLayer == null) { 1259 return; 1260 } 1261 1262 Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable(); 1263 Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable(); 1264 if (mDebug && (mDebugRoundedCornerFactory.getHasProviders())) { 1265 topDrawable = mDebugRoundedCornerDelegate.getTopRoundedDrawable(); 1266 bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable(); 1267 } 1268 1269 if (topDrawable == null && bottomDrawable == null) { 1270 return; 1271 } 1272 mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable); 1273 } 1274 updateHwLayerRoundedCornerExistAndSize()1275 private void updateHwLayerRoundedCornerExistAndSize() { 1276 if (mScreenDecorHwcLayer == null) { 1277 return; 1278 } 1279 if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) { 1280 mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize( 1281 mDebugRoundedCornerDelegate.getHasTop(), 1282 mDebugRoundedCornerDelegate.getHasBottom(), 1283 mDebugRoundedCornerDelegate.getTopRoundedSize().getWidth(), 1284 mDebugRoundedCornerDelegate.getBottomRoundedSize().getWidth()); 1285 } else { 1286 mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize( 1287 mRoundedCornerResDelegate.getHasTop(), 1288 mRoundedCornerResDelegate.getHasBottom(), 1289 mRoundedCornerResDelegate.getTopRoundedSize().getWidth(), 1290 mRoundedCornerResDelegate.getBottomRoundedSize().getWidth()); 1291 } 1292 } 1293 1294 @VisibleForTesting setSize(View view, Size pixelSize)1295 protected void setSize(View view, Size pixelSize) { 1296 LayoutParams params = view.getLayoutParams(); 1297 params.width = pixelSize.getWidth(); 1298 params.height = pixelSize.getHeight(); 1299 view.setLayoutParams(params); 1300 } 1301 1302 public static class DisplayCutoutView extends DisplayCutoutBaseView { 1303 final List<Rect> mBounds = new ArrayList(); 1304 final Rect mBoundingRect = new Rect(); 1305 Rect mTotalBounds = new Rect(); 1306 1307 private int mColor = Color.BLACK; 1308 private int mRotation; 1309 private int mInitialPosition; 1310 private int mPosition; 1311 DisplayCutoutView(Context context, @BoundsPosition int pos)1312 public DisplayCutoutView(Context context, @BoundsPosition int pos) { 1313 super(context); 1314 mInitialPosition = pos; 1315 1316 paint.setColor(mColor); 1317 paint.setStyle(Paint.Style.FILL); 1318 if (DEBUG_LOGGING) { 1319 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, 1320 getWindowTitleByPos(pos) + " drawn in rot " + mRotation)); 1321 } 1322 } 1323 setColor(int color)1324 public void setColor(int color) { 1325 if (color == mColor) { 1326 return; 1327 } 1328 mColor = color; 1329 paint.setColor(mColor); 1330 invalidate(); 1331 } 1332 1333 @Override updateRotation(int rotation)1334 public void updateRotation(int rotation) { 1335 // updateRotation() is called inside CutoutDecorProviderImpl::onReloadResAndMeasure() 1336 // during onDisplayChanged. In order to prevent reloading cutout info in super class, 1337 // check mRotation at first 1338 if (rotation == mRotation) { 1339 return; 1340 } 1341 mRotation = rotation; 1342 super.updateRotation(rotation); 1343 } 1344 1345 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 1346 @Override updateCutout()1347 public void updateCutout() { 1348 if (!isAttachedToWindow() || pendingConfigChange) { 1349 return; 1350 } 1351 mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation); 1352 requestLayout(); 1353 getDisplay().getDisplayInfo(displayInfo); 1354 mBounds.clear(); 1355 mBoundingRect.setEmpty(); 1356 cutoutPath.reset(); 1357 int newVisible; 1358 if (shouldDrawCutout(getContext()) && hasCutout()) { 1359 mBounds.addAll(displayInfo.displayCutout.getBoundingRects()); 1360 localBounds(mBoundingRect); 1361 updateGravity(); 1362 updateBoundingPath(); 1363 invalidate(); 1364 newVisible = VISIBLE; 1365 } else { 1366 newVisible = GONE; 1367 } 1368 if (updateVisOnUpdateCutout() && newVisible != getVisibility()) { 1369 setVisibility(newVisible); 1370 } 1371 } 1372 updateVisOnUpdateCutout()1373 protected boolean updateVisOnUpdateCutout() { 1374 return true; 1375 } 1376 updateBoundingPath()1377 private void updateBoundingPath() { 1378 final Path path = displayInfo.displayCutout.getCutoutPath(); 1379 if (path != null) { 1380 cutoutPath.set(path); 1381 } else { 1382 cutoutPath.reset(); 1383 } 1384 } 1385 updateGravity()1386 private void updateGravity() { 1387 LayoutParams lp = getLayoutParams(); 1388 if (lp instanceof FrameLayout.LayoutParams) { 1389 FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp; 1390 int newGravity = getGravity(displayInfo.displayCutout); 1391 if (flp.gravity != newGravity) { 1392 flp.gravity = newGravity; 1393 setLayoutParams(flp); 1394 } 1395 } 1396 } 1397 hasCutout()1398 private boolean hasCutout() { 1399 final DisplayCutout displayCutout = displayInfo.displayCutout; 1400 if (displayCutout == null) { 1401 return false; 1402 } 1403 1404 if (mPosition == BOUNDS_POSITION_LEFT) { 1405 return !displayCutout.getBoundingRectLeft().isEmpty(); 1406 } else if (mPosition == BOUNDS_POSITION_TOP) { 1407 return !displayCutout.getBoundingRectTop().isEmpty(); 1408 } else if (mPosition == BOUNDS_POSITION_BOTTOM) { 1409 return !displayCutout.getBoundingRectBottom().isEmpty(); 1410 } else if (mPosition == BOUNDS_POSITION_RIGHT) { 1411 return !displayCutout.getBoundingRectRight().isEmpty(); 1412 } 1413 return false; 1414 } 1415 1416 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1417 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1418 if (mBounds.isEmpty()) { 1419 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1420 return; 1421 } 1422 1423 if (showProtection) { 1424 // Make sure that our measured height encompasses the protection 1425 mTotalBounds.set(mBoundingRect); 1426 mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top, 1427 (int) protectionRect.right, (int) protectionRect.bottom); 1428 setMeasuredDimension( 1429 resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0), 1430 resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0)); 1431 } else { 1432 setMeasuredDimension( 1433 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), 1434 resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)); 1435 } 1436 } 1437 boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out)1438 public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, 1439 Rect out) { 1440 switch (gravity) { 1441 case Gravity.TOP: 1442 out.set(displayCutout.getBoundingRectTop()); 1443 break; 1444 case Gravity.LEFT: 1445 out.set(displayCutout.getBoundingRectLeft()); 1446 break; 1447 case Gravity.BOTTOM: 1448 out.set(displayCutout.getBoundingRectBottom()); 1449 break; 1450 case Gravity.RIGHT: 1451 out.set(displayCutout.getBoundingRectRight()); 1452 break; 1453 default: 1454 out.setEmpty(); 1455 } 1456 } 1457 localBounds(Rect out)1458 private void localBounds(Rect out) { 1459 DisplayCutout displayCutout = displayInfo.displayCutout; 1460 boundsFromDirection(displayCutout, getGravity(displayCutout), out); 1461 } 1462 getGravity(DisplayCutout displayCutout)1463 private int getGravity(DisplayCutout displayCutout) { 1464 if (mPosition == BOUNDS_POSITION_LEFT) { 1465 if (!displayCutout.getBoundingRectLeft().isEmpty()) { 1466 return Gravity.LEFT; 1467 } 1468 } else if (mPosition == BOUNDS_POSITION_TOP) { 1469 if (!displayCutout.getBoundingRectTop().isEmpty()) { 1470 return Gravity.TOP; 1471 } 1472 } else if (mPosition == BOUNDS_POSITION_BOTTOM) { 1473 if (!displayCutout.getBoundingRectBottom().isEmpty()) { 1474 return Gravity.BOTTOM; 1475 } 1476 } else if (mPosition == BOUNDS_POSITION_RIGHT) { 1477 if (!displayCutout.getBoundingRectRight().isEmpty()) { 1478 return Gravity.RIGHT; 1479 } 1480 } 1481 return Gravity.NO_GRAVITY; 1482 } 1483 } 1484 1485 /** 1486 * A pre-draw listener, that cancels the draw and restarts the traversal with the updated 1487 * window attributes. 1488 */ 1489 private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener { 1490 1491 private final View mView; 1492 private final int mTargetRotation; 1493 private final Point mTargetDisplaySize; 1494 // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific 1495 // position. 1496 private final int mPosition; 1497 RestartingPreDrawListener(View view, @BoundsPosition int position, int targetRotation, Point targetDisplaySize)1498 private RestartingPreDrawListener(View view, @BoundsPosition int position, 1499 int targetRotation, Point targetDisplaySize) { 1500 mView = view; 1501 mTargetRotation = targetRotation; 1502 mTargetDisplaySize = targetDisplaySize; 1503 mPosition = position; 1504 } 1505 1506 @Override onPreDraw()1507 public boolean onPreDraw() { 1508 mView.getViewTreeObserver().removeOnPreDrawListener(this); 1509 if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) { 1510 if (DEBUG_LOGGING) { 1511 final String title = mPosition < 0 ? "ScreenDecorHwcLayer" 1512 : getWindowTitleByPos(mPosition); 1513 Log.i(TAG, title + " already in target rot " 1514 + mTargetRotation + " and in target resolution " 1515 + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y 1516 + ", allow draw without restarting it"); 1517 } 1518 return true; 1519 } 1520 1521 mPendingConfigChange = false; 1522 // This changes the window attributes - we need to restart the traversal for them to 1523 // take effect. 1524 updateConfiguration(); 1525 if (DEBUG_LOGGING) { 1526 final String title = mPosition < 0 ? "ScreenDecorHwcLayer" 1527 : getWindowTitleByPos(mPosition); 1528 Log.i(TAG, title 1529 + " restarting listener fired, restarting draw for rot " + mRotation 1530 + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y); 1531 } 1532 mView.invalidate(); 1533 return false; 1534 } 1535 } 1536 1537 /** 1538 * A pre-draw listener, that validates that the rotation and display resolution we draw in 1539 * matches the display's rotation and resolution before continuing the draw. 1540 * 1541 * This is to prevent a race condition, where we have not received the display changed event 1542 * yet, and would thus draw in an old orientation. 1543 */ 1544 private class ValidatingPreDrawListener implements ViewTreeObserver.OnPreDrawListener { 1545 1546 private final View mView; 1547 1548 public ValidatingPreDrawListener(View view) { 1549 mView = view; 1550 } 1551 1552 @Override 1553 public boolean onPreDraw() { 1554 mContext.getDisplay().getDisplayInfo(mDisplayInfo); 1555 final int displayRotation = mDisplayInfo.rotation; 1556 if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)) 1557 && !mPendingConfigChange) { 1558 if (DEBUG_LOGGING) { 1559 if (displayRotation != mRotation) { 1560 Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot " 1561 + displayRotation + ". Restarting draw"); 1562 } 1563 if (displaySizeChanged(mDisplaySize, mDisplayInfo)) { 1564 Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y 1565 + ", but display is at " 1566 + mDisplayInfo.getNaturalWidth() + "x" 1567 + mDisplayInfo.getNaturalHeight() + ". Restarting draw"); 1568 } 1569 } 1570 mView.invalidate(); 1571 return false; 1572 } 1573 return true; 1574 } 1575 } 1576 } 1577