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
15package com.android.systemui;
16
17import static android.view.Surface.ROTATION_0;
18import static android.view.Surface.ROTATION_180;
19import static android.view.Surface.ROTATION_270;
20import static android.view.Surface.ROTATION_90;
21import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
22import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
23import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
24
25import static com.android.systemui.tuner.TunablePadding.FLAG_END;
26import static com.android.systemui.tuner.TunablePadding.FLAG_START;
27
28import android.annotation.Dimension;
29import android.app.Fragment;
30import android.content.Context;
31import android.content.res.ColorStateList;
32import android.content.res.Configuration;
33import android.graphics.Canvas;
34import android.graphics.Color;
35import android.graphics.Matrix;
36import android.graphics.Paint;
37import android.graphics.Path;
38import android.graphics.PixelFormat;
39import android.graphics.Rect;
40import android.graphics.Region;
41import android.hardware.display.DisplayManager;
42import android.os.SystemProperties;
43import android.provider.Settings.Secure;
44import android.support.annotation.VisibleForTesting;
45import android.util.DisplayMetrics;
46import android.view.DisplayCutout;
47import android.view.DisplayInfo;
48import android.view.Gravity;
49import android.view.LayoutInflater;
50import android.view.Surface;
51import android.view.View;
52import android.view.View.OnLayoutChangeListener;
53import android.view.ViewGroup;
54import android.view.ViewGroup.LayoutParams;
55import android.view.WindowManager;
56import android.widget.FrameLayout;
57import android.widget.ImageView;
58
59import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
60import com.android.systemui.fragments.FragmentHostManager;
61import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
62import com.android.systemui.plugins.qs.QS;
63import com.android.systemui.qs.SecureSetting;
64import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
65import com.android.systemui.statusbar.phone.StatusBar;
66import com.android.systemui.tuner.TunablePadding;
67import com.android.systemui.tuner.TunerService;
68import com.android.systemui.tuner.TunerService.Tunable;
69import 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 */
75public 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
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
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
201    protected void onConfigurationChanged(Configuration newConfig) {
202        updateOrientation();
203        if (shouldDrawCutout() && mOverlay == null) {
204            setupDecorations();
205        }
206    }
207
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
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
251    private void updateView(View v, int gravity, int rotation) {
252        ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
253        v.setRotation(rotation);
254    }
255
256    private void updateWindowVisibilities() {
257        updateWindowVisibility(mOverlay);
258        updateWindowVisibility(mBottomOverlay);
259    }
260
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
269    private boolean hasRoundedCorners() {
270        return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
271    }
272
273    private boolean shouldDrawCutout() {
274        return shouldDrawCutout(mContext);
275    }
276
277    static boolean shouldDrawCutout(Context context) {
278        return context.getResources().getBoolean(
279                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
280    }
281
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
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
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
344    private void updateLayoutParams() {
345        mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams());
346        mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams());
347    }
348
349    @Override
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
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
391        public TunablePaddingTagListener(int padding, int id) {
392            mPadding = padding;
393            mId = id;
394        }
395
396        @Override
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
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
431        public void setColor(int color) {
432            mColor = color;
433            invalidate();
434        }
435
436        @Override
437        protected void onAttachedToWindow() {
438            super.onAttachedToWindow();
439            mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
440                    getHandler());
441            update();
442        }
443
444        @Override
445        protected void onDetachedFromWindow() {
446            super.onDetachedFromWindow();
447            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
448        }
449
450        @Override
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
464        public void onDisplayAdded(int displayId) {
465        }
466
467        @Override
468        public void onDisplayRemoved(int displayId) {
469        }
470
471        @Override
472        public void onDisplayChanged(int displayId) {
473            if (displayId == getDisplay().getDisplayId()) {
474                update();
475            }
476        }
477
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
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
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
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
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
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
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
609        public boolean shouldInterceptTouch() {
610            return mInfo.displayCutout != null && getVisibility() == VISIBLE;
611        }
612
613        @Override
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
634    private boolean isLandscape(int rotation) {
635        return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
636                RotationUtils.ROTATION_SEASCAPE;
637    }
638}
639