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