• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Display.DEFAULT_DISPLAY;
18 import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
19 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
20 import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
21 import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
22 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
23 import static android.view.Surface.ROTATION_0;
24 import static android.view.Surface.ROTATION_180;
25 import static android.view.Surface.ROTATION_270;
26 import static android.view.Surface.ROTATION_90;
27 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
28 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
29 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
30 
31 import android.animation.Animator;
32 import android.animation.AnimatorListenerAdapter;
33 import android.animation.ValueAnimator;
34 import android.annotation.Dimension;
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.app.ActivityManager;
38 import android.content.BroadcastReceiver;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.res.ColorStateList;
43 import android.content.res.Configuration;
44 import android.content.res.Resources;
45 import android.graphics.Canvas;
46 import android.graphics.Color;
47 import android.graphics.Matrix;
48 import android.graphics.Paint;
49 import android.graphics.Path;
50 import android.graphics.PixelFormat;
51 import android.graphics.Point;
52 import android.graphics.Rect;
53 import android.graphics.RectF;
54 import android.graphics.Region;
55 import android.graphics.drawable.Drawable;
56 import android.hardware.display.DisplayManager;
57 import android.os.Handler;
58 import android.os.HandlerExecutor;
59 import android.os.HandlerThread;
60 import android.os.SystemProperties;
61 import android.os.UserHandle;
62 import android.provider.Settings.Secure;
63 import android.util.DisplayMetrics;
64 import android.util.Log;
65 import android.view.Display;
66 import android.view.DisplayCutout;
67 import android.view.DisplayCutout.BoundsPosition;
68 import android.view.DisplayInfo;
69 import android.view.Gravity;
70 import android.view.LayoutInflater;
71 import android.view.Surface;
72 import android.view.View;
73 import android.view.View.OnLayoutChangeListener;
74 import android.view.ViewGroup;
75 import android.view.ViewGroup.LayoutParams;
76 import android.view.ViewTreeObserver;
77 import android.view.WindowManager;
78 import android.widget.FrameLayout;
79 import android.widget.ImageView;
80 
81 import androidx.annotation.VisibleForTesting;
82 
83 import com.android.internal.util.Preconditions;
84 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
85 import com.android.systemui.broadcast.BroadcastDispatcher;
86 import com.android.systemui.dagger.qualifiers.Main;
87 import com.android.systemui.qs.SecureSetting;
88 import com.android.systemui.tuner.TunerService;
89 import com.android.systemui.tuner.TunerService.Tunable;
90 
91 import java.util.ArrayList;
92 import java.util.List;
93 
94 import javax.inject.Inject;
95 import javax.inject.Singleton;
96 
97 /**
98  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
99  * for antialiasing and emulation purposes.
100  */
101 @Singleton
102 public class ScreenDecorations extends SystemUI implements Tunable {
103     private static final boolean DEBUG = false;
104     private static final String TAG = "ScreenDecorations";
105 
106     public static final String SIZE = "sysui_rounded_size";
107     public static final String PADDING = "sysui_rounded_content_padding";
108     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
109             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
110     private static final boolean VERBOSE = false;
111     private static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
112 
113     private DisplayManager mDisplayManager;
114     @VisibleForTesting
115     protected boolean mIsRegistered;
116     private final BroadcastDispatcher mBroadcastDispatcher;
117     private final Handler mMainHandler;
118     private final TunerService mTunerService;
119     private DisplayManager.DisplayListener mDisplayListener;
120     private CameraAvailabilityListener mCameraListener;
121 
122     //TODO: These are piecemeal being updated to Points for now to support non-square rounded
123     // corners. for now it is only supposed when reading the intrinsic size from the drawables with
124     // mIsRoundedCornerMultipleRadius is set
125     @VisibleForTesting
126     protected Point mRoundedDefault = new Point(0, 0);
127     @VisibleForTesting
128     protected Point mRoundedDefaultTop = new Point(0, 0);
129     @VisibleForTesting
130     protected Point mRoundedDefaultBottom = new Point(0, 0);
131     @VisibleForTesting
132     protected View[] mOverlays;
133     @Nullable
134     private DisplayCutoutView[] mCutoutViews;
135     private float mDensity;
136     private WindowManager mWindowManager;
137     private int mRotation;
138     private SecureSetting mColorInversionSetting;
139     private Handler mHandler;
140     private boolean mPendingRotationChange;
141     private boolean mIsRoundedCornerMultipleRadius;
142 
143     private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
144             new CameraAvailabilityListener.CameraTransitionCallback() {
145         @Override
146         public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
147             if (mCutoutViews == null) {
148                 Log.w(TAG, "DisplayCutoutView do not initialized");
149                 return;
150             }
151             // Show the extra protection around the front facing camera if necessary
152             for (DisplayCutoutView dcv : mCutoutViews) {
153                 // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
154                 if (dcv != null) {
155                     dcv.setProtection(protectionPath, bounds);
156                     dcv.setShowProtection(true);
157                 }
158             }
159         }
160 
161         @Override
162         public void onHideCameraProtection() {
163             if (mCutoutViews == null) {
164                 Log.w(TAG, "DisplayCutoutView do not initialized");
165                 return;
166             }
167             // Go back to the regular anti-aliasing
168             for (DisplayCutoutView dcv : mCutoutViews) {
169                 // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
170                 if (dcv != null) {
171                     dcv.setShowProtection(false);
172                 }
173             }
174         }
175     };
176 
177     /**
178      * Converts a set of {@link Rect}s into a {@link Region}
179      *
180      * @hide
181      */
rectsToRegion(List<Rect> rects)182     public static Region rectsToRegion(List<Rect> rects) {
183         Region result = Region.obtain();
184         if (rects != null) {
185             for (Rect r : rects) {
186                 if (r != null && !r.isEmpty()) {
187                     result.op(r, Region.Op.UNION);
188                 }
189             }
190         }
191         return result;
192     }
193 
194     @Inject
ScreenDecorations(Context context, @Main Handler handler, BroadcastDispatcher broadcastDispatcher, TunerService tunerService)195     public ScreenDecorations(Context context,
196             @Main Handler handler,
197             BroadcastDispatcher broadcastDispatcher,
198             TunerService tunerService) {
199         super(context);
200         mMainHandler = handler;
201         mBroadcastDispatcher = broadcastDispatcher;
202         mTunerService = tunerService;
203     }
204 
205     @Override
start()206     public void start() {
207         mHandler = startHandlerThread();
208         mHandler.post(this::startOnScreenDecorationsThread);
209     }
210 
211     @VisibleForTesting
startHandlerThread()212     Handler startHandlerThread() {
213         HandlerThread thread = new HandlerThread("ScreenDecorations");
214         thread.start();
215         return thread.getThreadHandler();
216     }
217 
startOnScreenDecorationsThread()218     private void startOnScreenDecorationsThread() {
219         mRotation = mContext.getDisplay().getRotation();
220         mWindowManager = mContext.getSystemService(WindowManager.class);
221         mDisplayManager = mContext.getSystemService(DisplayManager.class);
222         mIsRoundedCornerMultipleRadius = mContext.getResources().getBoolean(
223                 R.bool.config_roundedCornerMultipleRadius);
224         updateRoundedCornerRadii();
225         setupDecorations();
226         setupCameraListener();
227 
228         mDisplayListener = new DisplayManager.DisplayListener() {
229             @Override
230             public void onDisplayAdded(int displayId) {
231                 // do nothing
232             }
233 
234             @Override
235             public void onDisplayRemoved(int displayId) {
236                 // do nothing
237             }
238 
239             @Override
240             public void onDisplayChanged(int displayId) {
241                 final int newRotation = mContext.getDisplay().getRotation();
242                 if (mOverlays != null && mRotation != newRotation) {
243                     // We cannot immediately update the orientation. Otherwise
244                     // WindowManager is still deferring layout until it has finished dispatching
245                     // the config changes, which may cause divergence between what we draw
246                     // (new orientation), and where we are placed on the screen (old orientation).
247                     // Instead we wait until either:
248                     // - we are trying to redraw. This because WM resized our window and told us to.
249                     // - the config change has been dispatched, so WM is no longer deferring layout.
250                     mPendingRotationChange = true;
251                     if (DEBUG) {
252                         Log.i(TAG, "Rotation changed, deferring " + newRotation + ", staying at "
253                                 + mRotation);
254                     }
255 
256                     for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
257                         if (mOverlays[i] != null) {
258                             mOverlays[i].getViewTreeObserver().addOnPreDrawListener(
259                                     new RestartingPreDrawListener(mOverlays[i], i, newRotation));
260                         }
261                     }
262                 }
263                 updateOrientation();
264             }
265         };
266 
267         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
268         updateOrientation();
269     }
270 
setupDecorations()271     private void setupDecorations() {
272         if (hasRoundedCorners() || shouldDrawCutout()) {
273             final DisplayCutout cutout = getCutout();
274             final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
275             int rotatedPos;
276             for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
277                 rotatedPos = getBoundPositionFromRotation(i, mRotation);
278                 if ((bounds != null && !bounds[rotatedPos].isEmpty())
279                         || shouldShowRoundedCorner(i)) {
280                     createOverlay(i);
281                 } else {
282                     removeOverlay(i);
283                 }
284             }
285         } else {
286             removeAllOverlays();
287         }
288 
289         if (hasOverlays()) {
290             if (mIsRegistered) {
291                 return;
292             }
293             DisplayMetrics metrics = new DisplayMetrics();
294             mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics);
295             mDensity = metrics.density;
296 
297             mMainHandler.post(() -> mTunerService.addTunable(this, SIZE));
298 
299             // Watch color inversion and invert the overlay as needed.
300             if (mColorInversionSetting == null) {
301                 mColorInversionSetting = new SecureSetting(mContext, mHandler,
302                         Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
303                     @Override
304                     protected void handleValueChanged(int value, boolean observedChange) {
305                         updateColorInversion(value);
306                     }
307                 };
308 
309                 mColorInversionSetting.setListening(true);
310                 mColorInversionSetting.onChange(false);
311             }
312 
313             IntentFilter filter = new IntentFilter();
314             filter.addAction(Intent.ACTION_USER_SWITCHED);
315             mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter,
316                     new HandlerExecutor(mHandler), UserHandle.ALL);
317             mIsRegistered = true;
318         } else {
319             mMainHandler.post(() -> mTunerService.removeTunable(this));
320 
321             if (mColorInversionSetting != null) {
322                 mColorInversionSetting.setListening(false);
323             }
324 
325             mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver);
326             mIsRegistered = false;
327         }
328     }
329 
330     @VisibleForTesting
getCutout()331     DisplayCutout getCutout() {
332         return mContext.getDisplay().getCutout();
333     }
334 
335     @VisibleForTesting
hasOverlays()336     boolean hasOverlays() {
337         if (mOverlays == null) {
338             return false;
339         }
340 
341         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
342             if (mOverlays[i] != null) {
343                 return true;
344             }
345         }
346         mOverlays = null;
347         return false;
348     }
349 
removeAllOverlays()350     private void removeAllOverlays() {
351         if (mOverlays == null) {
352             return;
353         }
354 
355         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
356             if (mOverlays[i] != null) {
357                 removeOverlay(i);
358             }
359         }
360         mOverlays = null;
361     }
362 
removeOverlay(@oundsPosition int pos)363     private void removeOverlay(@BoundsPosition int pos) {
364         if (mOverlays == null || mOverlays[pos] == null) {
365             return;
366         }
367         mWindowManager.removeViewImmediate(mOverlays[pos]);
368         mOverlays[pos] = null;
369     }
370 
createOverlay(@oundsPosition int pos)371     private void createOverlay(@BoundsPosition int pos) {
372         if (mOverlays == null) {
373             mOverlays = new View[BOUNDS_POSITION_LENGTH];
374         }
375 
376         if (mCutoutViews == null) {
377             mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH];
378         }
379 
380         if (mOverlays[pos] != null) {
381             return;
382         }
383         mOverlays[pos] = overlayForPosition(pos);
384 
385         mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
386         ((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
387 
388         mOverlays[pos].setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
389         mOverlays[pos].setAlpha(0);
390         mOverlays[pos].setForceDarkAllowed(false);
391 
392         updateView(pos);
393 
394         mWindowManager.addView(mOverlays[pos], getWindowLayoutParams(pos));
395 
396         mOverlays[pos].addOnLayoutChangeListener(new OnLayoutChangeListener() {
397             @Override
398             public void onLayoutChange(View v, int left, int top, int right, int bottom,
399                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
400                 mOverlays[pos].removeOnLayoutChangeListener(this);
401                 mOverlays[pos].animate()
402                         .alpha(1)
403                         .setDuration(1000)
404                         .start();
405             }
406         });
407 
408         mOverlays[pos].getViewTreeObserver().addOnPreDrawListener(
409                 new ValidatingPreDrawListener(mOverlays[pos]));
410     }
411 
412     /**
413      * Allow overrides for top/bottom positions
414      */
overlayForPosition(@oundsPosition int pos)415     private View overlayForPosition(@BoundsPosition int pos) {
416         switch (pos) {
417             case BOUNDS_POSITION_TOP:
418                 return LayoutInflater.from(mContext)
419                         .inflate(R.layout.rounded_corners_top, null);
420             case BOUNDS_POSITION_BOTTOM:
421                 return LayoutInflater.from(mContext)
422                         .inflate(R.layout.rounded_corners_bottom, null);
423             default:
424                 return LayoutInflater.from(mContext)
425                         .inflate(R.layout.rounded_corners, null);
426         }
427     }
428 
updateView(@oundsPosition int pos)429     private void updateView(@BoundsPosition int pos) {
430         if (mOverlays == null || mOverlays[pos] == null) {
431             return;
432         }
433 
434         // update rounded corner view rotation
435         updateRoundedCornerView(pos, R.id.left);
436         updateRoundedCornerView(pos, R.id.right);
437         updateRoundedCornerSize(mRoundedDefault, mRoundedDefaultTop, mRoundedDefaultBottom);
438 
439         // update cutout view rotation
440         if (mCutoutViews != null && mCutoutViews[pos] != null) {
441             mCutoutViews[pos].setRotation(mRotation);
442         }
443     }
444 
445     @VisibleForTesting
getWindowLayoutParams(@oundsPosition int pos)446     WindowManager.LayoutParams getWindowLayoutParams(@BoundsPosition int pos) {
447         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
448                 getWidthLayoutParamByPos(pos),
449                 getHeightLayoutParamByPos(pos),
450                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
451                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
452                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
453                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
454                         | WindowManager.LayoutParams.FLAG_SLIPPERY
455                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
456                 PixelFormat.TRANSLUCENT);
457         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
458                 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
459 
460         if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
461             lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
462         }
463 
464         lp.setTitle(getWindowTitleByPos(pos));
465         lp.gravity = getOverlayWindowGravity(pos);
466         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
467         lp.setFitInsetsTypes(0 /* types */);
468         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
469         return lp;
470     }
471 
getWidthLayoutParamByPos(@oundsPosition int pos)472     private int getWidthLayoutParamByPos(@BoundsPosition int pos) {
473         final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
474         return rotatedPos == BOUNDS_POSITION_TOP || rotatedPos == BOUNDS_POSITION_BOTTOM
475                 ? MATCH_PARENT : WRAP_CONTENT;
476     }
477 
getHeightLayoutParamByPos(@oundsPosition int pos)478     private int getHeightLayoutParamByPos(@BoundsPosition int pos) {
479         final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
480         return rotatedPos == BOUNDS_POSITION_TOP || rotatedPos == BOUNDS_POSITION_BOTTOM
481                 ? WRAP_CONTENT : MATCH_PARENT;
482     }
483 
getWindowTitleByPos(@oundsPosition int pos)484     private static String getWindowTitleByPos(@BoundsPosition int pos) {
485         switch (pos) {
486             case BOUNDS_POSITION_LEFT:
487                 return "ScreenDecorOverlayLeft";
488             case BOUNDS_POSITION_TOP:
489                 return "ScreenDecorOverlay";
490             case BOUNDS_POSITION_RIGHT:
491                 return "ScreenDecorOverlayRight";
492             case BOUNDS_POSITION_BOTTOM:
493                 return "ScreenDecorOverlayBottom";
494             default:
495                 throw new IllegalArgumentException("unknown bound position: " + pos);
496         }
497     }
498 
getOverlayWindowGravity(@oundsPosition int pos)499     private int getOverlayWindowGravity(@BoundsPosition int pos) {
500         final int rotated = getBoundPositionFromRotation(pos, mRotation);
501         switch (rotated) {
502             case BOUNDS_POSITION_TOP:
503                 return Gravity.TOP;
504             case BOUNDS_POSITION_BOTTOM:
505                 return Gravity.BOTTOM;
506             case BOUNDS_POSITION_LEFT:
507                 return Gravity.LEFT;
508             case BOUNDS_POSITION_RIGHT:
509                 return Gravity.RIGHT;
510             default:
511                 throw new IllegalArgumentException("unknown bound position: " + pos);
512         }
513     }
514 
getBoundPositionFromRotation(@oundsPosition int pos, int rotation)515     private static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) {
516         return (pos - rotation) < 0
517                 ? pos - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
518                 : pos - rotation;
519     }
520 
setupCameraListener()521     private void setupCameraListener() {
522         Resources res = mContext.getResources();
523         boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
524         if (enabled) {
525             mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mHandler::post);
526             mCameraListener.addTransitionCallback(mCameraTransitionCallback);
527             mCameraListener.startListening();
528         }
529     }
530 
531     private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
532         @Override
533         public void onReceive(Context context, Intent intent) {
534             int newUserId = ActivityManager.getCurrentUser();
535             if (DEBUG) {
536                 Log.d(TAG, "UserSwitched newUserId=" + newUserId);
537             }
538             // update color inversion setting to the new user
539             mColorInversionSetting.setUserId(newUserId);
540             updateColorInversion(mColorInversionSetting.getValue());
541         }
542     };
543 
updateColorInversion(int colorsInvertedValue)544     private void updateColorInversion(int colorsInvertedValue) {
545         int tint = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
546         if (DEBUG_COLOR) {
547             tint = Color.RED;
548         }
549         ColorStateList tintList = ColorStateList.valueOf(tint);
550 
551         if (mOverlays == null) {
552             return;
553         }
554         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
555             if (mOverlays[i] == null) {
556                 continue;
557             }
558             final int size = ((ViewGroup) mOverlays[i]).getChildCount();
559             View child;
560             for (int j = 0; j < size; j++) {
561                 child = ((ViewGroup) mOverlays[i]).getChildAt(j);
562                 if (child instanceof ImageView) {
563                     ((ImageView) child).setImageTintList(tintList);
564                 } else if (child instanceof DisplayCutoutView) {
565                     ((DisplayCutoutView) child).setColor(tint);
566                 }
567             }
568         }
569     }
570 
571     @Override
onConfigurationChanged(Configuration newConfig)572     protected void onConfigurationChanged(Configuration newConfig) {
573         mHandler.post(() -> {
574             int oldRotation = mRotation;
575             mPendingRotationChange = false;
576             updateOrientation();
577             updateRoundedCornerRadii();
578             if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
579             setupDecorations();
580             if (mOverlays != null) {
581                 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
582                 // which ensures that the forced seamless rotation will end, even if we updated
583                 // the rotation before window manager was ready (and was still waiting for sending
584                 // the updated rotation).
585                 updateLayoutParams();
586             }
587         });
588     }
589 
updateOrientation()590     private void updateOrientation() {
591         Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
592                 "must call on " + mHandler.getLooper().getThread()
593                         + ", but was " + Thread.currentThread());
594         if (mPendingRotationChange) {
595             return;
596         }
597         int newRotation = mContext.getDisplay().getRotation();
598         if (newRotation != mRotation) {
599             mRotation = newRotation;
600 
601             if (mOverlays != null) {
602                 updateLayoutParams();
603                 for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
604                     if (mOverlays[i] == null) {
605                         continue;
606                     }
607                     updateView(i);
608                 }
609             }
610         }
611     }
612 
updateRoundedCornerRadii()613     private void updateRoundedCornerRadii() {
614         // We should eventually move to just using the intrinsic size of the drawables since
615         // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
616         // upgrading all of the configs to contain (width, height) pairs. Instead assume that a
617         // device configured using the single integer config value is okay with drawing the corners
618         // as a square
619         final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
620                 com.android.internal.R.dimen.rounded_corner_radius);
621         final int newRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
622                 com.android.internal.R.dimen.rounded_corner_radius_top);
623         final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
624                 com.android.internal.R.dimen.rounded_corner_radius_bottom);
625 
626         final boolean changed = mRoundedDefault.x != newRoundedDefault
627                         || mRoundedDefaultTop.x != newRoundedDefault
628                         || mRoundedDefaultBottom.x != newRoundedDefault;
629 
630         if (changed) {
631             // If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
632             // (width, height) size of drawable/rounded.xml instead of rounded_corner_radius
633             if (mIsRoundedCornerMultipleRadius) {
634                 Drawable d =  mContext.getDrawable(R.drawable.rounded);
635                 mRoundedDefault.set(d.getIntrinsicWidth(), d.getIntrinsicHeight());
636                 d =  mContext.getDrawable(R.drawable.rounded_corner_top);
637                 mRoundedDefaultTop.set(d.getIntrinsicWidth(), d.getIntrinsicHeight());
638                 d =  mContext.getDrawable(R.drawable.rounded_corner_bottom);
639                 mRoundedDefaultBottom.set(d.getIntrinsicWidth(), d.getIntrinsicHeight());
640             } else {
641                 mRoundedDefault.set(newRoundedDefault, newRoundedDefault);
642                 mRoundedDefaultTop.set(newRoundedDefaultTop, newRoundedDefaultTop);
643                 mRoundedDefaultBottom.set(newRoundedDefaultBottom, newRoundedDefaultBottom);
644             }
645             onTuningChanged(SIZE, null);
646         }
647     }
648 
updateRoundedCornerView(@oundsPosition int pos, int id)649     private void updateRoundedCornerView(@BoundsPosition int pos, int id) {
650         final View rounded = mOverlays[pos].findViewById(id);
651         if (rounded == null) {
652             return;
653         }
654         rounded.setVisibility(View.GONE);
655         if (shouldShowRoundedCorner(pos)) {
656             final int gravity = getRoundedCornerGravity(pos, id == R.id.left);
657             ((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity;
658             setRoundedCornerOrientation(rounded, gravity);
659             rounded.setVisibility(View.VISIBLE);
660         }
661     }
662 
getRoundedCornerGravity(@oundsPosition int pos, boolean isStart)663     private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) {
664         final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
665         switch (rotatedPos) {
666             case BOUNDS_POSITION_LEFT:
667                 return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.BOTTOM | Gravity.LEFT;
668             case BOUNDS_POSITION_TOP:
669                 return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.TOP | Gravity.RIGHT;
670             case BOUNDS_POSITION_RIGHT:
671                 return isStart ? Gravity.TOP | Gravity.RIGHT : Gravity.BOTTOM | Gravity.RIGHT;
672             case BOUNDS_POSITION_BOTTOM:
673                 return isStart ? Gravity.BOTTOM | Gravity.LEFT : Gravity.BOTTOM | Gravity.RIGHT;
674             default:
675                 throw new IllegalArgumentException("Incorrect position: " + rotatedPos);
676         }
677     }
678 
679     /**
680      * Configures the rounded corner drawable's view matrix based on the gravity.
681      *
682      * The gravity describes which corner to configure for, and the drawable we are rotating is
683      * assumed to be oriented for the top-left corner of the device regardless of the target corner.
684      * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or
685      * y-axis for the top-right and bottom-left corners.
686      */
setRoundedCornerOrientation(View corner, int gravity)687     private void setRoundedCornerOrientation(View corner, int gravity) {
688         corner.setRotation(0);
689         corner.setScaleX(1);
690         corner.setScaleY(1);
691         switch (gravity) {
692             case Gravity.TOP | Gravity.LEFT:
693                 return;
694             case Gravity.TOP | Gravity.RIGHT:
695                 corner.setScaleX(-1); // flip X axis
696                 return;
697             case Gravity.BOTTOM | Gravity.LEFT:
698                 corner.setScaleY(-1); // flip Y axis
699                 return;
700             case Gravity.BOTTOM | Gravity.RIGHT:
701                 corner.setRotation(180);
702                 return;
703             default:
704                 throw new IllegalArgumentException("Unsupported gravity: " + gravity);
705         }
706     }
hasRoundedCorners()707     private boolean hasRoundedCorners() {
708         return mRoundedDefault.x > 0
709                 || mRoundedDefaultBottom.x > 0
710                 || mRoundedDefaultTop.x > 0
711                 || mIsRoundedCornerMultipleRadius;
712     }
713 
shouldShowRoundedCorner(@oundsPosition int pos)714     private boolean shouldShowRoundedCorner(@BoundsPosition int pos) {
715         if (!hasRoundedCorners()) {
716             return false;
717         }
718 
719         DisplayCutout cutout = getCutout();
720         // for cutout is null or cutout with only waterfall.
721         final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty();
722         // Shows rounded corner on left and right overlays only when there is no top or bottom
723         // cutout.
724         final int rotatedTop = getBoundPositionFromRotation(BOUNDS_POSITION_TOP, mRotation);
725         final int rotatedBottom = getBoundPositionFromRotation(BOUNDS_POSITION_BOTTOM, mRotation);
726         if (emptyBoundsOrWaterfall || !cutout.getBoundingRectsAll()[rotatedTop].isEmpty()
727                 || !cutout.getBoundingRectsAll()[rotatedBottom].isEmpty()) {
728             return pos == BOUNDS_POSITION_TOP || pos == BOUNDS_POSITION_BOTTOM;
729         } else {
730             return pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_RIGHT;
731         }
732     }
733 
shouldDrawCutout()734     private boolean shouldDrawCutout() {
735         return shouldDrawCutout(mContext);
736     }
737 
shouldDrawCutout(Context context)738     static boolean shouldDrawCutout(Context context) {
739         return context.getResources().getBoolean(
740                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
741     }
742 
updateLayoutParams()743     private void updateLayoutParams() {
744         if (mOverlays == null) {
745             return;
746         }
747         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
748             if (mOverlays[i] == null) {
749                 continue;
750             }
751             mWindowManager.updateViewLayout(mOverlays[i], getWindowLayoutParams(i));
752         }
753     }
754 
755     @Override
onTuningChanged(String key, String newValue)756     public void onTuningChanged(String key, String newValue) {
757         mHandler.post(() -> {
758             if (mOverlays == null) return;
759             if (SIZE.equals(key)) {
760                 Point size = mRoundedDefault;
761                 Point sizeTop = mRoundedDefaultTop;
762                 Point sizeBottom = mRoundedDefaultBottom;
763                 if (newValue != null) {
764                     try {
765                         int s = (int) (Integer.parseInt(newValue) * mDensity);
766                         size = new Point(s, s);
767                     } catch (Exception e) {
768                     }
769                 }
770                 updateRoundedCornerSize(size, sizeTop, sizeBottom);
771             }
772         });
773     }
774 
updateRoundedCornerSize( Point sizeDefault, Point sizeTop, Point sizeBottom)775     private void updateRoundedCornerSize(
776             Point sizeDefault,
777             Point sizeTop,
778             Point sizeBottom) {
779         if (mOverlays == null) {
780             return;
781         }
782         if (sizeTop.x == 0) {
783             sizeTop = sizeDefault;
784         }
785         if (sizeBottom.x == 0) {
786             sizeBottom = sizeDefault;
787         }
788 
789         for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
790             if (mOverlays[i] == null) {
791                 continue;
792             }
793             if (i == BOUNDS_POSITION_LEFT || i == BOUNDS_POSITION_RIGHT) {
794                 if (mRotation == ROTATION_270) {
795                     setSize(mOverlays[i].findViewById(R.id.left), sizeBottom);
796                     setSize(mOverlays[i].findViewById(R.id.right), sizeTop);
797                 } else {
798                     setSize(mOverlays[i].findViewById(R.id.left), sizeTop);
799                     setSize(mOverlays[i].findViewById(R.id.right), sizeBottom);
800                 }
801             } else if (i == BOUNDS_POSITION_TOP) {
802                 setSize(mOverlays[i].findViewById(R.id.left), sizeTop);
803                 setSize(mOverlays[i].findViewById(R.id.right), sizeTop);
804             } else if (i == BOUNDS_POSITION_BOTTOM) {
805                 setSize(mOverlays[i].findViewById(R.id.left), sizeBottom);
806                 setSize(mOverlays[i].findViewById(R.id.right), sizeBottom);
807             }
808         }
809     }
810 
811     @VisibleForTesting
setSize(View view, Point pixelSize)812     protected void setSize(View view, Point pixelSize) {
813         LayoutParams params = view.getLayoutParams();
814         params.width = pixelSize.x;
815         params.height = pixelSize.y;
816         view.setLayoutParams(params);
817     }
818 
819     public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
820             RegionInterceptableView {
821 
822         private static final float HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f;
823 
824         private Display.Mode mDisplayMode = null;
825         private final DisplayInfo mInfo = new DisplayInfo();
826         private final Paint mPaint = new Paint();
827         private final List<Rect> mBounds = new ArrayList();
828         private final Rect mBoundingRect = new Rect();
829         private final Path mBoundingPath = new Path();
830         // Don't initialize these yet because they may never exist
831         private RectF mProtectionRect;
832         private RectF mProtectionRectOrig;
833         private Path mProtectionPath;
834         private Path mProtectionPathOrig;
835         private Rect mTotalBounds = new Rect();
836         // Whether or not to show the cutout protection path
837         private boolean mShowProtection = false;
838 
839         private final int[] mLocation = new int[2];
840         private final ScreenDecorations mDecorations;
841         private int mColor = Color.BLACK;
842         private int mRotation;
843         private int mInitialPosition;
844         private int mPosition;
845         private float mCameraProtectionProgress = HIDDEN_CAMERA_PROTECTION_SCALE;
846         private ValueAnimator mCameraProtectionAnimator;
847 
DisplayCutoutView(Context context, @BoundsPosition int pos, ScreenDecorations decorations)848         public DisplayCutoutView(Context context, @BoundsPosition int pos,
849                 ScreenDecorations decorations) {
850             super(context);
851             mInitialPosition = pos;
852             mDecorations = decorations;
853             setId(R.id.display_cutout);
854             if (DEBUG) {
855                 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
856                         getWindowTitleByPos(pos) + " drawn in rot " + mRotation));
857             }
858         }
859 
setColor(int color)860         public void setColor(int color) {
861             mColor = color;
862             invalidate();
863         }
864 
865         @Override
onAttachedToWindow()866         protected void onAttachedToWindow() {
867             super.onAttachedToWindow();
868             mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
869                     getHandler());
870             update();
871         }
872 
873         @Override
onDetachedFromWindow()874         protected void onDetachedFromWindow() {
875             super.onDetachedFromWindow();
876             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
877         }
878 
879         @Override
onDraw(Canvas canvas)880         protected void onDraw(Canvas canvas) {
881             super.onDraw(canvas);
882             getLocationOnScreen(mLocation);
883             canvas.translate(-mLocation[0], -mLocation[1]);
884 
885             if (!mBoundingPath.isEmpty()) {
886                 mPaint.setColor(mColor);
887                 mPaint.setStyle(Paint.Style.FILL);
888                 mPaint.setAntiAlias(true);
889                 canvas.drawPath(mBoundingPath, mPaint);
890             }
891             if (mCameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE
892                     && !mProtectionRect.isEmpty()) {
893                 canvas.scale(mCameraProtectionProgress, mCameraProtectionProgress,
894                         mProtectionRect.centerX(), mProtectionRect.centerY());
895                 canvas.drawPath(mProtectionPath, mPaint);
896             }
897         }
898 
899         @Override
onDisplayAdded(int displayId)900         public void onDisplayAdded(int displayId) {
901         }
902 
903         @Override
onDisplayRemoved(int displayId)904         public void onDisplayRemoved(int displayId) {
905         }
906 
907         @Override
onDisplayChanged(int displayId)908         public void onDisplayChanged(int displayId) {
909             Display.Mode oldMode = mDisplayMode;
910             mDisplayMode = getDisplay().getMode();
911 
912             // Display mode hasn't meaningfully changed, we can ignore it
913             if (!modeChanged(oldMode, mDisplayMode)) {
914                 return;
915             }
916 
917             if (displayId == getDisplay().getDisplayId()) {
918                 update();
919             }
920         }
921 
modeChanged(Display.Mode oldMode, Display.Mode newMode)922         private boolean modeChanged(Display.Mode oldMode, Display.Mode newMode) {
923             if (oldMode == null) {
924                 return true;
925             }
926 
927             boolean changed = false;
928             changed |= oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
929             changed |= oldMode.getPhysicalWidth() != newMode.getPhysicalWidth();
930             // We purposely ignore refresh rate and id changes here, because we don't need to
931             // invalidate for those, and they can trigger the refresh rate to increase
932 
933             return changed;
934         }
935 
setRotation(int rotation)936         public void setRotation(int rotation) {
937             mRotation = rotation;
938             update();
939         }
940 
setProtection(Path protectionPath, Rect pathBounds)941         void setProtection(Path protectionPath, Rect pathBounds) {
942             if (mProtectionPathOrig == null) {
943                 mProtectionPathOrig = new Path();
944                 mProtectionPath = new Path();
945             }
946             mProtectionPathOrig.set(protectionPath);
947             if (mProtectionRectOrig == null) {
948                 mProtectionRectOrig = new RectF();
949                 mProtectionRect = new RectF();
950             }
951             mProtectionRectOrig.set(pathBounds);
952         }
953 
setShowProtection(boolean shouldShow)954         void setShowProtection(boolean shouldShow) {
955             if (mShowProtection == shouldShow) {
956                 return;
957             }
958 
959             mShowProtection = shouldShow;
960             updateBoundingPath();
961             // Delay the relayout until the end of the animation when hiding the cutout,
962             // otherwise we'd clip it.
963             if (mShowProtection) {
964                 requestLayout();
965             }
966             if (mCameraProtectionAnimator != null) {
967                 mCameraProtectionAnimator.cancel();
968             }
969             mCameraProtectionAnimator = ValueAnimator.ofFloat(mCameraProtectionProgress,
970                     mShowProtection ? 1.0f : HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750);
971             mCameraProtectionAnimator.setInterpolator(Interpolators.DECELERATE_QUINT);
972             mCameraProtectionAnimator.addUpdateListener(animation -> {
973                 mCameraProtectionProgress = (float) animation.getAnimatedValue();
974                 invalidate();
975             });
976             mCameraProtectionAnimator.addListener(new AnimatorListenerAdapter() {
977                 @Override
978                 public void onAnimationEnd(Animator animation) {
979                     mCameraProtectionAnimator = null;
980                     if (!mShowProtection) {
981                         requestLayout();
982                     }
983                 }
984             });
985             mCameraProtectionAnimator.start();
986         }
987 
update()988         private void update() {
989             if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
990                 return;
991             }
992             mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation);
993             requestLayout();
994             getDisplay().getDisplayInfo(mInfo);
995             mBounds.clear();
996             mBoundingRect.setEmpty();
997             mBoundingPath.reset();
998             int newVisible;
999             if (shouldDrawCutout(getContext()) && hasCutout()) {
1000                 mBounds.addAll(mInfo.displayCutout.getBoundingRects());
1001                 localBounds(mBoundingRect);
1002                 updateGravity();
1003                 updateBoundingPath();
1004                 invalidate();
1005                 newVisible = VISIBLE;
1006             } else {
1007                 newVisible = GONE;
1008             }
1009             if (newVisible != getVisibility()) {
1010                 setVisibility(newVisible);
1011             }
1012         }
1013 
updateBoundingPath()1014         private void updateBoundingPath() {
1015             int lw = mInfo.logicalWidth;
1016             int lh = mInfo.logicalHeight;
1017 
1018             boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
1019 
1020             int dw = flipped ? lh : lw;
1021             int dh = flipped ? lw : lh;
1022 
1023             Path path = DisplayCutout.pathFromResources(getResources(), dw, dh);
1024             if (path != null) {
1025                 mBoundingPath.set(path);
1026             } else {
1027                 mBoundingPath.reset();
1028             }
1029             Matrix m = new Matrix();
1030             transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
1031             mBoundingPath.transform(m);
1032             if (mProtectionPathOrig != null) {
1033                 // Reset the protection path so we don't aggregate rotations
1034                 mProtectionPath.set(mProtectionPathOrig);
1035                 mProtectionPath.transform(m);
1036                 m.mapRect(mProtectionRect, mProtectionRectOrig);
1037             }
1038         }
1039 
transformPhysicalToLogicalCoordinates(@urface.Rotation int rotation, @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out)1040         private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
1041                 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
1042             switch (rotation) {
1043                 case ROTATION_0:
1044                     out.reset();
1045                     break;
1046                 case ROTATION_90:
1047                     out.setRotate(270);
1048                     out.postTranslate(0, physicalWidth);
1049                     break;
1050                 case ROTATION_180:
1051                     out.setRotate(180);
1052                     out.postTranslate(physicalWidth, physicalHeight);
1053                     break;
1054                 case ROTATION_270:
1055                     out.setRotate(90);
1056                     out.postTranslate(physicalHeight, 0);
1057                     break;
1058                 default:
1059                     throw new IllegalArgumentException("Unknown rotation: " + rotation);
1060             }
1061         }
1062 
updateGravity()1063         private void updateGravity() {
1064             LayoutParams lp = getLayoutParams();
1065             if (lp instanceof FrameLayout.LayoutParams) {
1066                 FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp;
1067                 int newGravity = getGravity(mInfo.displayCutout);
1068                 if (flp.gravity != newGravity) {
1069                     flp.gravity = newGravity;
1070                     setLayoutParams(flp);
1071                 }
1072             }
1073         }
1074 
hasCutout()1075         private boolean hasCutout() {
1076             final DisplayCutout displayCutout = mInfo.displayCutout;
1077             if (displayCutout == null) {
1078                 return false;
1079             }
1080 
1081             if (mPosition == BOUNDS_POSITION_LEFT) {
1082                 return !displayCutout.getBoundingRectLeft().isEmpty();
1083             } else if (mPosition == BOUNDS_POSITION_TOP) {
1084                 return !displayCutout.getBoundingRectTop().isEmpty();
1085             } else if (mPosition == BOUNDS_POSITION_BOTTOM) {
1086                 return !displayCutout.getBoundingRectBottom().isEmpty();
1087             } else if (mPosition == BOUNDS_POSITION_RIGHT) {
1088                 return !displayCutout.getBoundingRectRight().isEmpty();
1089             }
1090             return false;
1091         }
1092 
1093         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)1094         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1095             if (mBounds.isEmpty()) {
1096                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1097                 return;
1098             }
1099 
1100             if (mShowProtection) {
1101                 // Make sure that our measured height encompases the protection
1102                 mTotalBounds.union(mBoundingRect);
1103                 mTotalBounds.union((int) mProtectionRect.left, (int) mProtectionRect.top,
1104                         (int) mProtectionRect.right, (int) mProtectionRect.bottom);
1105                 setMeasuredDimension(
1106                         resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
1107                         resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0));
1108             } else {
1109                 setMeasuredDimension(
1110                         resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
1111                         resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
1112             }
1113         }
1114 
boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out)1115         public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,
1116                 Rect out) {
1117             switch (gravity) {
1118                 case Gravity.TOP:
1119                     out.set(displayCutout.getBoundingRectTop());
1120                     break;
1121                 case Gravity.LEFT:
1122                     out.set(displayCutout.getBoundingRectLeft());
1123                     break;
1124                 case Gravity.BOTTOM:
1125                     out.set(displayCutout.getBoundingRectBottom());
1126                     break;
1127                 case Gravity.RIGHT:
1128                     out.set(displayCutout.getBoundingRectRight());
1129                     break;
1130                 default:
1131                     out.setEmpty();
1132             }
1133         }
1134 
localBounds(Rect out)1135         private void localBounds(Rect out) {
1136             DisplayCutout displayCutout = mInfo.displayCutout;
1137             boundsFromDirection(displayCutout, getGravity(displayCutout), out);
1138         }
1139 
getGravity(DisplayCutout displayCutout)1140         private int getGravity(DisplayCutout displayCutout) {
1141             if (mPosition == BOUNDS_POSITION_LEFT) {
1142                 if (!displayCutout.getBoundingRectLeft().isEmpty()) {
1143                     return Gravity.LEFT;
1144                 }
1145             } else if (mPosition == BOUNDS_POSITION_TOP) {
1146                 if (!displayCutout.getBoundingRectTop().isEmpty()) {
1147                     return Gravity.TOP;
1148                 }
1149             } else if (mPosition == BOUNDS_POSITION_BOTTOM) {
1150                 if (!displayCutout.getBoundingRectBottom().isEmpty()) {
1151                     return Gravity.BOTTOM;
1152                 }
1153             } else if (mPosition == BOUNDS_POSITION_RIGHT) {
1154                 if (!displayCutout.getBoundingRectRight().isEmpty()) {
1155                     return Gravity.RIGHT;
1156                 }
1157             }
1158             return Gravity.NO_GRAVITY;
1159         }
1160 
1161         @Override
shouldInterceptTouch()1162         public boolean shouldInterceptTouch() {
1163             return mInfo.displayCutout != null && getVisibility() == VISIBLE;
1164         }
1165 
1166         @Override
getInterceptRegion()1167         public Region getInterceptRegion() {
1168             if (mInfo.displayCutout == null) {
1169                 return null;
1170             }
1171 
1172             View rootView = getRootView();
1173             Region cutoutBounds = rectsToRegion(
1174                     mInfo.displayCutout.getBoundingRects());
1175 
1176             // Transform to window's coordinate space
1177             rootView.getLocationOnScreen(mLocation);
1178             cutoutBounds.translate(-mLocation[0], -mLocation[1]);
1179 
1180             // Intersect with window's frame
1181             cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
1182                     rootView.getBottom(), Region.Op.INTERSECT);
1183 
1184             return cutoutBounds;
1185         }
1186     }
1187 
1188     /**
1189      * A pre-draw listener, that cancels the draw and restarts the traversal with the updated
1190      * window attributes.
1191      */
1192     private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1193 
1194         private final View mView;
1195         private final int mTargetRotation;
1196         private final int mPosition;
1197 
RestartingPreDrawListener(View view, @BoundsPosition int position, int targetRotation)1198         private RestartingPreDrawListener(View view, @BoundsPosition int position,
1199                 int targetRotation) {
1200             mView = view;
1201             mTargetRotation = targetRotation;
1202             mPosition = position;
1203         }
1204 
1205         @Override
onPreDraw()1206         public boolean onPreDraw() {
1207             mView.getViewTreeObserver().removeOnPreDrawListener(this);
1208 
1209             if (mTargetRotation == mRotation) {
1210                 if (DEBUG) {
1211                     Log.i(TAG, getWindowTitleByPos(mPosition) + " already in target rot "
1212                             + mTargetRotation + ", allow draw without restarting it");
1213                 }
1214                 return true;
1215             }
1216 
1217             mPendingRotationChange = false;
1218             // This changes the window attributes - we need to restart the traversal for them to
1219             // take effect.
1220             updateOrientation();
1221             if (DEBUG) {
1222                 Log.i(TAG, getWindowTitleByPos(mPosition)
1223                         + " restarting listener fired, restarting draw for rot " + mRotation);
1224             }
1225             mView.invalidate();
1226             return false;
1227         }
1228     }
1229 
1230     /**
1231      * A pre-draw listener, that validates that the rotation we draw in matches the displays
1232      * rotation before continuing the draw.
1233      *
1234      * This is to prevent a race condition, where we have not received the display changed event
1235      * yet, and would thus draw in an old orientation.
1236      */
1237     private class ValidatingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
1238 
1239         private final View mView;
1240 
ValidatingPreDrawListener(View view)1241         public ValidatingPreDrawListener(View view) {
1242             mView = view;
1243         }
1244 
1245         @Override
onPreDraw()1246         public boolean onPreDraw() {
1247             final int displayRotation = mContext.getDisplay().getRotation();
1248             if (displayRotation != mRotation && !mPendingRotationChange) {
1249                 if (DEBUG) {
1250                     Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
1251                             + displayRotation + ". Restarting draw");
1252                 }
1253                 mView.invalidate();
1254                 return false;
1255             }
1256             return true;
1257         }
1258     }
1259 }
1260