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