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