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.Surface.ROTATION_0;
18 import static android.view.Surface.ROTATION_180;
19 import static android.view.Surface.ROTATION_270;
20 import static android.view.Surface.ROTATION_90;
21 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
22 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
23 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
24 
25 import static com.android.systemui.tuner.TunablePadding.FLAG_END;
26 import static com.android.systemui.tuner.TunablePadding.FLAG_START;
27 
28 import android.annotation.Dimension;
29 import android.app.Fragment;
30 import android.content.Context;
31 import android.content.res.ColorStateList;
32 import android.content.res.Configuration;
33 import android.graphics.Canvas;
34 import android.graphics.Color;
35 import android.graphics.Matrix;
36 import android.graphics.Paint;
37 import android.graphics.Path;
38 import android.graphics.PixelFormat;
39 import android.graphics.Rect;
40 import android.graphics.Region;
41 import android.hardware.display.DisplayManager;
42 import android.os.SystemProperties;
43 import android.provider.Settings.Secure;
44 import android.support.annotation.VisibleForTesting;
45 import android.util.DisplayMetrics;
46 import android.view.DisplayCutout;
47 import android.view.DisplayInfo;
48 import android.view.Gravity;
49 import android.view.LayoutInflater;
50 import android.view.Surface;
51 import android.view.View;
52 import android.view.View.OnLayoutChangeListener;
53 import android.view.ViewGroup;
54 import android.view.ViewGroup.LayoutParams;
55 import android.view.WindowManager;
56 import android.widget.FrameLayout;
57 import android.widget.ImageView;
58 
59 import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
60 import com.android.systemui.fragments.FragmentHostManager;
61 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
62 import com.android.systemui.plugins.qs.QS;
63 import com.android.systemui.qs.SecureSetting;
64 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
65 import com.android.systemui.statusbar.phone.StatusBar;
66 import com.android.systemui.tuner.TunablePadding;
67 import com.android.systemui.tuner.TunerService;
68 import com.android.systemui.tuner.TunerService.Tunable;
69 import com.android.systemui.util.leak.RotationUtils;
70 
71 /**
72  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
73  * for antialiasing and emulation purposes.
74  */
75 public class ScreenDecorations extends SystemUI implements Tunable {
76     public static final String SIZE = "sysui_rounded_size";
77     public static final String PADDING = "sysui_rounded_content_padding";
78     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
79             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
80 
81     private DisplayManager mDisplayManager;
82     private DisplayManager.DisplayListener mDisplayListener;
83 
84     private int mRoundedDefault;
85     private int mRoundedDefaultTop;
86     private int mRoundedDefaultBottom;
87     private View mOverlay;
88     private View mBottomOverlay;
89     private float mDensity;
90     private WindowManager mWindowManager;
91     private int mRotation;
92 
93     @Override
start()94     public void start() {
95         mWindowManager = mContext.getSystemService(WindowManager.class);
96         mRoundedDefault = mContext.getResources().getDimensionPixelSize(
97                 R.dimen.rounded_corner_radius);
98         mRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
99                 R.dimen.rounded_corner_radius_top);
100         mRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
101                 R.dimen.rounded_corner_radius_bottom);
102         if (hasRoundedCorners() || shouldDrawCutout()) {
103             setupDecorations();
104         }
105 
106         int padding = mContext.getResources().getDimensionPixelSize(
107                 R.dimen.rounded_corner_content_padding);
108         if (padding != 0) {
109             setupPadding(padding);
110         }
111 
112         mDisplayListener = new DisplayManager.DisplayListener() {
113             @Override
114             public void onDisplayAdded(int displayId) {
115                 // do nothing
116             }
117 
118             @Override
119             public void onDisplayRemoved(int displayId) {
120                 // do nothing
121             }
122 
123             @Override
124             public void onDisplayChanged(int displayId) {
125                 updateOrientation();
126             }
127         };
128 
129         mRotation = -1;
130         mDisplayManager = (DisplayManager) mContext.getSystemService(
131                 Context.DISPLAY_SERVICE);
132         mDisplayManager.registerDisplayListener(mDisplayListener, null);
133     }
134 
setupDecorations()135     private void setupDecorations() {
136         mOverlay = LayoutInflater.from(mContext)
137                 .inflate(R.layout.rounded_corners, null);
138         DisplayCutoutView cutoutTop = new DisplayCutoutView(mContext, true,
139                 this::updateWindowVisibilities);
140         ((ViewGroup)mOverlay).addView(cutoutTop);
141         mBottomOverlay = LayoutInflater.from(mContext)
142                 .inflate(R.layout.rounded_corners, null);
143         DisplayCutoutView cutoutBottom = new DisplayCutoutView(mContext, false,
144                 this::updateWindowVisibilities);
145         ((ViewGroup)mBottomOverlay).addView(cutoutBottom);
146 
147         mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
148         mOverlay.setAlpha(0);
149 
150         mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
151         mBottomOverlay.setAlpha(0);
152 
153         updateViews();
154 
155         mWindowManager.addView(mOverlay, getWindowLayoutParams());
156         mWindowManager.addView(mBottomOverlay, getBottomLayoutParams());
157 
158         DisplayMetrics metrics = new DisplayMetrics();
159         mWindowManager.getDefaultDisplay().getMetrics(metrics);
160         mDensity = metrics.density;
161 
162         Dependency.get(TunerService.class).addTunable(this, SIZE);
163 
164         // Watch color inversion and invert the overlay as needed.
165         SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER),
166                 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
167             @Override
168             protected void handleValueChanged(int value, boolean observedChange) {
169                 int tint = value != 0 ? Color.WHITE : Color.BLACK;
170                 ColorStateList tintList = ColorStateList.valueOf(tint);
171                 ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList);
172                 ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList);
173                 ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList);
174                 ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList);
175                 cutoutTop.setColor(tint);
176                 cutoutBottom.setColor(tint);
177             }
178         };
179         setting.setListening(true);
180         setting.onChange(false);
181 
182         mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
183             @Override
184             public void onLayoutChange(View v, int left, int top, int right, int bottom,
185                     int oldLeft,
186                     int oldTop, int oldRight, int oldBottom) {
187                 mOverlay.removeOnLayoutChangeListener(this);
188                 mOverlay.animate()
189                         .alpha(1)
190                         .setDuration(1000)
191                         .start();
192                 mBottomOverlay.animate()
193                         .alpha(1)
194                         .setDuration(1000)
195                         .start();
196             }
197         });
198     }
199 
200     @Override
onConfigurationChanged(Configuration newConfig)201     protected void onConfigurationChanged(Configuration newConfig) {
202         updateOrientation();
203         if (shouldDrawCutout() && mOverlay == null) {
204             setupDecorations();
205         }
206     }
207 
updateOrientation()208     protected void updateOrientation() {
209         int newRotation = RotationUtils.getExactRotation(mContext);
210         if (newRotation != mRotation) {
211             mRotation = newRotation;
212 
213             if (mOverlay != null) {
214                 updateLayoutParams();
215                 updateViews();
216             }
217         }
218     }
219 
updateViews()220     private void updateViews() {
221         View topLeft = mOverlay.findViewById(R.id.left);
222         View topRight = mOverlay.findViewById(R.id.right);
223         View bottomLeft = mBottomOverlay.findViewById(R.id.left);
224         View bottomRight = mBottomOverlay.findViewById(R.id.right);
225 
226         if (mRotation == RotationUtils.ROTATION_NONE) {
227             updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
228             updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
229             updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
230             updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
231         } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
232             updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
233             updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
234             updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
235             updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
236         } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
237             updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
238             updateView(topRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
239             updateView(bottomLeft, Gravity.TOP | Gravity.LEFT, 0);
240             updateView(bottomRight, Gravity.TOP | Gravity.RIGHT, 90);
241         } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
242             updateView(topLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
243             updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90);
244             updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
245             updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
246         }
247 
248         updateWindowVisibilities();
249     }
250 
updateView(View v, int gravity, int rotation)251     private void updateView(View v, int gravity, int rotation) {
252         ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
253         v.setRotation(rotation);
254     }
255 
updateWindowVisibilities()256     private void updateWindowVisibilities() {
257         updateWindowVisibility(mOverlay);
258         updateWindowVisibility(mBottomOverlay);
259     }
260 
updateWindowVisibility(View overlay)261     private void updateWindowVisibility(View overlay) {
262         boolean visibleForCutout = shouldDrawCutout()
263                 && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
264         boolean visibleForRoundedCorners = hasRoundedCorners();
265         overlay.setVisibility(visibleForCutout || visibleForRoundedCorners
266                 ? View.VISIBLE : View.GONE);
267     }
268 
hasRoundedCorners()269     private boolean hasRoundedCorners() {
270         return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
271     }
272 
shouldDrawCutout()273     private boolean shouldDrawCutout() {
274         return shouldDrawCutout(mContext);
275     }
276 
shouldDrawCutout(Context context)277     static boolean shouldDrawCutout(Context context) {
278         return context.getResources().getBoolean(
279                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
280     }
281 
setupPadding(int padding)282     private void setupPadding(int padding) {
283         // Add some padding to all the content near the edge of the screen.
284         StatusBar sb = getComponent(StatusBar.class);
285         View statusBar = (sb != null ? sb.getStatusBarWindow() : null);
286         if (statusBar != null) {
287             TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
288                     padding, FLAG_END);
289 
290             FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
291             fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
292                     new TunablePaddingTagListener(padding, R.id.status_bar));
293             fragmentHostManager.addTagListener(QS.TAG,
294                     new TunablePaddingTagListener(padding, R.id.header));
295         }
296     }
297 
298     @VisibleForTesting
getWindowLayoutParams()299     WindowManager.LayoutParams getWindowLayoutParams() {
300         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
301                 ViewGroup.LayoutParams.MATCH_PARENT,
302                 LayoutParams.WRAP_CONTENT,
303                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
304                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
305                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
306                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
307                         | WindowManager.LayoutParams.FLAG_SLIPPERY
308                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
309                 PixelFormat.TRANSLUCENT);
310         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
311                 | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
312 
313         if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
314             lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
315         }
316 
317         lp.setTitle("ScreenDecorOverlay");
318         if (mRotation == RotationUtils.ROTATION_SEASCAPE
319                 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
320             lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
321         } else {
322             lp.gravity = Gravity.TOP | Gravity.LEFT;
323         }
324         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
325         if (isLandscape(mRotation)) {
326             lp.width = WRAP_CONTENT;
327             lp.height = MATCH_PARENT;
328         }
329         return lp;
330     }
331 
getBottomLayoutParams()332     private WindowManager.LayoutParams getBottomLayoutParams() {
333         WindowManager.LayoutParams lp = getWindowLayoutParams();
334         lp.setTitle("ScreenDecorOverlayBottom");
335         if (mRotation == RotationUtils.ROTATION_SEASCAPE
336                 || mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
337             lp.gravity = Gravity.TOP | Gravity.LEFT;
338         } else {
339             lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
340         }
341         return lp;
342     }
343 
updateLayoutParams()344     private void updateLayoutParams() {
345         mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
346         mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
347     }
348 
349     @Override
onTuningChanged(String key, String newValue)350     public void onTuningChanged(String key, String newValue) {
351         if (mOverlay == null) return;
352         if (SIZE.equals(key)) {
353             int size = mRoundedDefault;
354             int sizeTop = mRoundedDefaultTop;
355             int sizeBottom = mRoundedDefaultBottom;
356             if (newValue != null) {
357                 try {
358                     size = (int) (Integer.parseInt(newValue) * mDensity);
359                 } catch (Exception e) {
360                 }
361             }
362 
363             if (sizeTop == 0) {
364                 sizeTop = size;
365             }
366             if (sizeBottom == 0) {
367                 sizeBottom = size;
368             }
369 
370             setSize(mOverlay.findViewById(R.id.left), sizeTop);
371             setSize(mOverlay.findViewById(R.id.right), sizeTop);
372             setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
373             setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
374         }
375     }
376 
setSize(View view, int pixelSize)377     private void setSize(View view, int pixelSize) {
378         LayoutParams params = view.getLayoutParams();
379         params.width = pixelSize;
380         params.height = pixelSize;
381         view.setLayoutParams(params);
382     }
383 
384     @VisibleForTesting
385     static class TunablePaddingTagListener implements FragmentListener {
386 
387         private final int mPadding;
388         private final int mId;
389         private TunablePadding mTunablePadding;
390 
TunablePaddingTagListener(int padding, int id)391         public TunablePaddingTagListener(int padding, int id) {
392             mPadding = padding;
393             mId = id;
394         }
395 
396         @Override
onFragmentViewCreated(String tag, Fragment fragment)397         public void onFragmentViewCreated(String tag, Fragment fragment) {
398             if (mTunablePadding != null) {
399                 mTunablePadding.destroy();
400             }
401             View view = fragment.getView();
402             if (mId != 0) {
403                 view = view.findViewById(mId);
404             }
405             mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
406                     FLAG_START | FLAG_END);
407         }
408     }
409 
410     public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
411             RegionInterceptableView {
412 
413         private final DisplayInfo mInfo = new DisplayInfo();
414         private final Paint mPaint = new Paint();
415         private final Region mBounds = new Region();
416         private final Rect mBoundingRect = new Rect();
417         private final Path mBoundingPath = new Path();
418         private final int[] mLocation = new int[2];
419         private final boolean mStart;
420         private final Runnable mVisibilityChangedListener;
421         private int mColor = Color.BLACK;
422 
DisplayCutoutView(Context context, boolean start, Runnable visibilityChangedListener)423         public DisplayCutoutView(Context context, boolean start,
424                 Runnable visibilityChangedListener) {
425             super(context);
426             mStart = start;
427             mVisibilityChangedListener = visibilityChangedListener;
428             setId(R.id.display_cutout);
429         }
430 
setColor(int color)431         public void setColor(int color) {
432             mColor = color;
433             invalidate();
434         }
435 
436         @Override
onAttachedToWindow()437         protected void onAttachedToWindow() {
438             super.onAttachedToWindow();
439             mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
440                     getHandler());
441             update();
442         }
443 
444         @Override
onDetachedFromWindow()445         protected void onDetachedFromWindow() {
446             super.onDetachedFromWindow();
447             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
448         }
449 
450         @Override
onDraw(Canvas canvas)451         protected void onDraw(Canvas canvas) {
452             super.onDraw(canvas);
453             getLocationOnScreen(mLocation);
454             canvas.translate(-mLocation[0], -mLocation[1]);
455             if (!mBoundingPath.isEmpty()) {
456                 mPaint.setColor(mColor);
457                 mPaint.setStyle(Paint.Style.FILL);
458                 mPaint.setAntiAlias(true);
459                 canvas.drawPath(mBoundingPath, mPaint);
460             }
461         }
462 
463         @Override
onDisplayAdded(int displayId)464         public void onDisplayAdded(int displayId) {
465         }
466 
467         @Override
onDisplayRemoved(int displayId)468         public void onDisplayRemoved(int displayId) {
469         }
470 
471         @Override
onDisplayChanged(int displayId)472         public void onDisplayChanged(int displayId) {
473             if (displayId == getDisplay().getDisplayId()) {
474                 update();
475             }
476         }
477 
update()478         private void update() {
479             requestLayout();
480             getDisplay().getDisplayInfo(mInfo);
481             mBounds.setEmpty();
482             mBoundingRect.setEmpty();
483             mBoundingPath.reset();
484             int newVisible;
485             if (shouldDrawCutout(getContext()) && hasCutout()) {
486                 mBounds.set(mInfo.displayCutout.getBounds());
487                 localBounds(mBoundingRect);
488                 updateBoundingPath();
489                 invalidate();
490                 newVisible = VISIBLE;
491             } else {
492                 newVisible = GONE;
493             }
494             if (newVisible != getVisibility()) {
495                 setVisibility(newVisible);
496                 mVisibilityChangedListener.run();
497             }
498         }
499 
updateBoundingPath()500         private void updateBoundingPath() {
501             int lw = mInfo.logicalWidth;
502             int lh = mInfo.logicalHeight;
503 
504             boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
505 
506             int dw = flipped ? lh : lw;
507             int dh = flipped ? lw : lh;
508 
509             mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
510             Matrix m = new Matrix();
511             transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
512             mBoundingPath.transform(m);
513         }
514 
transformPhysicalToLogicalCoordinates(@urface.Rotation int rotation, @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out)515         private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
516                 @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
517             switch (rotation) {
518                 case ROTATION_0:
519                     out.reset();
520                     break;
521                 case ROTATION_90:
522                     out.setRotate(270);
523                     out.postTranslate(0, physicalWidth);
524                     break;
525                 case ROTATION_180:
526                     out.setRotate(180);
527                     out.postTranslate(physicalWidth, physicalHeight);
528                     break;
529                 case ROTATION_270:
530                     out.setRotate(90);
531                     out.postTranslate(physicalHeight, 0);
532                     break;
533                 default:
534                     throw new IllegalArgumentException("Unknown rotation: " + rotation);
535             }
536         }
537 
hasCutout()538         private boolean hasCutout() {
539             final DisplayCutout displayCutout = mInfo.displayCutout;
540             if (displayCutout == null) {
541                 return false;
542             }
543             if (mStart) {
544                 return displayCutout.getSafeInsetLeft() > 0
545                         || displayCutout.getSafeInsetTop() > 0;
546             } else {
547                 return displayCutout.getSafeInsetRight() > 0
548                         || displayCutout.getSafeInsetBottom() > 0;
549             }
550         }
551 
552         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)553         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
554             if (mBounds.isEmpty()) {
555                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
556                 return;
557             }
558             setMeasuredDimension(
559                     resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
560                     resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
561         }
562 
boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out)563         public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) {
564             Region bounds = displayCutout.getBounds();
565             switch (gravity) {
566                 case Gravity.TOP:
567                     bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(),
568                             Region.Op.INTERSECT);
569                     out.set(bounds.getBounds());
570                     break;
571                 case Gravity.LEFT:
572                     bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE,
573                             Region.Op.INTERSECT);
574                     out.set(bounds.getBounds());
575                     break;
576                 case Gravity.BOTTOM:
577                     bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE,
578                             Integer.MAX_VALUE, Region.Op.INTERSECT);
579                     out.set(bounds.getBounds());
580                     break;
581                 case Gravity.RIGHT:
582                     bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE,
583                             Integer.MAX_VALUE, Region.Op.INTERSECT);
584                     out.set(bounds.getBounds());
585                     break;
586             }
587             bounds.recycle();
588         }
589 
localBounds(Rect out)590         private void localBounds(Rect out) {
591             final DisplayCutout displayCutout = mInfo.displayCutout;
592 
593             if (mStart) {
594                 if (displayCutout.getSafeInsetLeft() > 0) {
595                     boundsFromDirection(displayCutout, Gravity.LEFT, out);
596                 } else if (displayCutout.getSafeInsetTop() > 0) {
597                     boundsFromDirection(displayCutout, Gravity.TOP, out);
598                 }
599             } else {
600                 if (displayCutout.getSafeInsetRight() > 0) {
601                     boundsFromDirection(displayCutout, Gravity.RIGHT, out);
602                 } else if (displayCutout.getSafeInsetBottom() > 0) {
603                     boundsFromDirection(displayCutout, Gravity.BOTTOM, out);
604                 }
605             }
606         }
607 
608         @Override
shouldInterceptTouch()609         public boolean shouldInterceptTouch() {
610             return mInfo.displayCutout != null && getVisibility() == VISIBLE;
611         }
612 
613         @Override
getInterceptRegion()614         public Region getInterceptRegion() {
615             if (mInfo.displayCutout == null) {
616                 return null;
617             }
618 
619             View rootView = getRootView();
620             Region cutoutBounds = mInfo.displayCutout.getBounds();
621 
622             // Transform to window's coordinate space
623             rootView.getLocationOnScreen(mLocation);
624             cutoutBounds.translate(-mLocation[0], -mLocation[1]);
625 
626             // Intersect with window's frame
627             cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
628                     rootView.getBottom(), Region.Op.INTERSECT);
629 
630             return cutoutBounds;
631         }
632     }
633 
isLandscape(int rotation)634     private boolean isLandscape(int rotation) {
635         return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
636                 RotationUtils.ROTATION_SEASCAPE;
637     }
638 }
639