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