1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar.phone; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.PropertyValuesHolder; 22 import android.animation.ValueAnimator; 23 import android.content.Context; 24 import android.graphics.Color; 25 import android.graphics.Rect; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.view.ViewTreeObserver; 29 import android.view.animation.DecelerateInterpolator; 30 import android.view.animation.Interpolator; 31 import android.view.animation.PathInterpolator; 32 33 import com.android.keyguard.KeyguardUpdateMonitor; 34 import com.android.systemui.R; 35 import com.android.systemui.statusbar.ExpandableNotificationRow; 36 import com.android.systemui.statusbar.NotificationData; 37 import com.android.systemui.statusbar.ScrimView; 38 import com.android.systemui.statusbar.policy.HeadsUpManager; 39 import com.android.systemui.statusbar.stack.StackStateAnimator; 40 41 /** 42 * Controls both the scrim behind the notifications and in front of the notifications (when a 43 * security method gets shown). 44 */ 45 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, 46 HeadsUpManager.OnHeadsUpChangedListener { 47 public static final long ANIMATION_DURATION = 220; 48 public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR 49 = new PathInterpolator(0f, 0, 0.7f, 1f); 50 public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED 51 = new PathInterpolator(0.3f, 0f, 0.8f, 1f); 52 private static final float SCRIM_BEHIND_ALPHA = 0.62f; 53 protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f; 54 protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; 55 private static final float SCRIM_IN_FRONT_ALPHA = 0.75f; 56 private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = 0.85f; 57 private static final int TAG_KEY_ANIM = R.id.scrim; 58 private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target; 59 private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; 60 private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; 61 62 protected final ScrimView mScrimBehind; 63 private final ScrimView mScrimInFront; 64 private final UnlockMethodCache mUnlockMethodCache; 65 private final View mHeadsUpScrim; 66 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 67 68 private float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA; 69 private float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; 70 private float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; 71 72 protected boolean mKeyguardShowing; 73 private float mFraction; 74 75 private boolean mDarkenWhileDragging; 76 protected boolean mBouncerShowing; 77 private boolean mWakeAndUnlocking; 78 private boolean mAnimateChange; 79 private boolean mUpdatePending; 80 private boolean mExpanding; 81 private boolean mAnimateKeyguardFadingOut; 82 private long mDurationOverride = -1; 83 private long mAnimationDelay; 84 private Runnable mOnAnimationFinished; 85 private final Interpolator mInterpolator = new DecelerateInterpolator(); 86 private boolean mDozing; 87 private float mDozeInFrontAlpha; 88 private float mDozeBehindAlpha; 89 private float mCurrentInFrontAlpha; 90 private float mCurrentBehindAlpha; 91 private float mCurrentHeadsUpAlpha = 1; 92 private int mPinnedHeadsUpCount; 93 private float mTopHeadsUpDragAmount; 94 private View mDraggedHeadsUpView; 95 private boolean mForceHideScrims; 96 private boolean mSkipFirstFrame; 97 private boolean mDontAnimateBouncerChanges; 98 private boolean mKeyguardFadingOutInProgress; 99 private ValueAnimator mKeyguardFadeoutAnimation; 100 101 public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim) { 102 mScrimBehind = scrimBehind; 103 mScrimInFront = scrimInFront; 104 mHeadsUpScrim = headsUpScrim; 105 final Context context = scrimBehind.getContext(); 106 mUnlockMethodCache = UnlockMethodCache.getInstance(context); 107 mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); 108 updateHeadsUpScrim(false); 109 } 110 111 public void setKeyguardShowing(boolean showing) { 112 mKeyguardShowing = showing; 113 scheduleUpdate(); 114 } 115 116 public void setShowScrimBehind(boolean show) { 117 if (show) { 118 mScrimBehindAlpha = SCRIM_BEHIND_ALPHA; 119 mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; 120 mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; 121 } else { 122 mScrimBehindAlpha = 0; 123 mScrimBehindAlphaKeyguard = 0; 124 mScrimBehindAlphaUnlocking = 0; 125 } 126 scheduleUpdate(); 127 } 128 129 protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, 130 float scrimBehindAlphaUnlocking) { 131 mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; 132 mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; 133 scheduleUpdate(); 134 } 135 136 public void onTrackingStarted() { 137 mExpanding = true; 138 mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); 139 } 140 141 public void onExpandingFinished() { 142 mExpanding = false; 143 } 144 145 public void setPanelExpansion(float fraction) { 146 if (mFraction != fraction) { 147 mFraction = fraction; 148 scheduleUpdate(); 149 if (mPinnedHeadsUpCount != 0) { 150 updateHeadsUpScrim(false); 151 } 152 if (mKeyguardFadeoutAnimation != null) { 153 mKeyguardFadeoutAnimation.cancel(); 154 } 155 } 156 } 157 158 public void setBouncerShowing(boolean showing) { 159 mBouncerShowing = showing; 160 mAnimateChange = !mExpanding && !mDontAnimateBouncerChanges; 161 scheduleUpdate(); 162 } 163 164 public void setWakeAndUnlocking() { 165 mWakeAndUnlocking = true; 166 scheduleUpdate(); 167 } 168 169 public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished, 170 boolean skipFirstFrame) { 171 mWakeAndUnlocking = false; 172 mAnimateKeyguardFadingOut = true; 173 mDurationOverride = duration; 174 mAnimationDelay = delay; 175 mAnimateChange = true; 176 mSkipFirstFrame = skipFirstFrame; 177 mOnAnimationFinished = onAnimationFinished; 178 179 if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { 180 scheduleUpdate(); 181 182 // No need to wait for the next frame to be drawn for this case - onPreDraw will execute 183 // the changes we just scheduled. 184 onPreDraw(); 185 } else { 186 187 // In case the user isn't unlocked, make sure to delay a bit because the system is hosed 188 // with too many things in this case, in order to not skip the initial frames. 189 mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); 190 } 191 } 192 193 public void abortKeyguardFadingOut() { 194 if (mAnimateKeyguardFadingOut) { 195 endAnimateKeyguardFadingOut(true /* force */); 196 } 197 } 198 199 public void animateKeyguardUnoccluding(long duration) { 200 mAnimateChange = false; 201 setScrimBehindColor(0f); 202 mAnimateChange = true; 203 scheduleUpdate(); 204 mDurationOverride = duration; 205 } 206 207 public void animateGoingToFullShade(long delay, long duration) { 208 mDurationOverride = duration; 209 mAnimationDelay = delay; 210 mAnimateChange = true; 211 scheduleUpdate(); 212 } 213 214 public void animateNextChange() { 215 mAnimateChange = true; 216 } 217 218 public void setDozing(boolean dozing) { 219 if (mDozing != dozing) { 220 mDozing = dozing; 221 scheduleUpdate(); 222 } 223 } 224 225 public void setDozeInFrontAlpha(float alpha) { 226 mDozeInFrontAlpha = alpha; 227 updateScrimColor(mScrimInFront); 228 } 229 230 public void setDozeBehindAlpha(float alpha) { 231 mDozeBehindAlpha = alpha; 232 updateScrimColor(mScrimBehind); 233 } 234 235 public float getDozeBehindAlpha() { 236 return mDozeBehindAlpha; 237 } 238 239 public float getDozeInFrontAlpha() { 240 return mDozeInFrontAlpha; 241 } 242 243 private float getScrimInFrontAlpha() { 244 return mKeyguardUpdateMonitor.needsSlowUnlockTransition() 245 ? SCRIM_IN_FRONT_ALPHA_LOCKED 246 : SCRIM_IN_FRONT_ALPHA; 247 } 248 private void scheduleUpdate() { 249 if (mUpdatePending) return; 250 251 // Make sure that a frame gets scheduled. 252 mScrimBehind.invalidate(); 253 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 254 mUpdatePending = true; 255 } 256 257 protected void updateScrims() { 258 if (mAnimateKeyguardFadingOut || mForceHideScrims) { 259 setScrimInFrontColor(0f); 260 setScrimBehindColor(0f); 261 } else if (mWakeAndUnlocking) { 262 263 // During wake and unlock, we first hide everything behind a black scrim, which then 264 // gets faded out from animateKeyguardFadingOut. 265 if (mDozing) { 266 setScrimInFrontColor(0f); 267 setScrimBehindColor(1f); 268 } else { 269 setScrimInFrontColor(1f); 270 setScrimBehindColor(0f); 271 } 272 } else if (!mKeyguardShowing && !mBouncerShowing) { 273 updateScrimNormal(); 274 setScrimInFrontColor(0); 275 } else { 276 updateScrimKeyguard(); 277 } 278 mAnimateChange = false; 279 } 280 281 private void updateScrimKeyguard() { 282 if (mExpanding && mDarkenWhileDragging) { 283 float behindFraction = Math.max(0, Math.min(mFraction, 1)); 284 float fraction = 1 - behindFraction; 285 fraction = (float) Math.pow(fraction, 0.8f); 286 behindFraction = (float) Math.pow(behindFraction, 0.8f); 287 setScrimInFrontColor(fraction * getScrimInFrontAlpha()); 288 setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard); 289 } else if (mBouncerShowing) { 290 setScrimInFrontColor(getScrimInFrontAlpha()); 291 setScrimBehindColor(0f); 292 } else { 293 float fraction = Math.max(0, Math.min(mFraction, 1)); 294 setScrimInFrontColor(0f); 295 setScrimBehindColor(fraction 296 * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking) 297 + mScrimBehindAlphaUnlocking); 298 } 299 } 300 301 private void updateScrimNormal() { 302 float frac = mFraction; 303 // let's start this 20% of the way down the screen 304 frac = frac * 1.2f - 0.2f; 305 if (frac <= 0) { 306 setScrimBehindColor(0); 307 } else { 308 // woo, special effects 309 final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); 310 setScrimBehindColor(k * mScrimBehindAlpha); 311 } 312 } 313 314 private void setScrimBehindColor(float alpha) { 315 setScrimColor(mScrimBehind, alpha); 316 } 317 318 private void setScrimInFrontColor(float alpha) { 319 setScrimColor(mScrimInFront, alpha); 320 if (alpha == 0f) { 321 mScrimInFront.setClickable(false); 322 } else { 323 324 // Eat touch events (unless dozing). 325 mScrimInFront.setClickable(!mDozing); 326 } 327 } 328 329 private void setScrimColor(View scrim, float alpha) { 330 updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim)); 331 } 332 333 private float getDozeAlpha(View scrim) { 334 return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha; 335 } 336 337 private float getCurrentScrimAlpha(View scrim) { 338 return scrim == mScrimBehind ? mCurrentBehindAlpha 339 : scrim == mScrimInFront ? mCurrentInFrontAlpha 340 : mCurrentHeadsUpAlpha; 341 } 342 343 private void setCurrentScrimAlpha(View scrim, float alpha) { 344 if (scrim == mScrimBehind) { 345 mCurrentBehindAlpha = alpha; 346 } else if (scrim == mScrimInFront) { 347 mCurrentInFrontAlpha = alpha; 348 } else { 349 alpha = Math.max(0.0f, Math.min(1.0f, alpha)); 350 mCurrentHeadsUpAlpha = alpha; 351 } 352 } 353 354 private void updateScrimColor(View scrim) { 355 float alpha1 = getCurrentScrimAlpha(scrim); 356 if (scrim instanceof ScrimView) { 357 float alpha2 = getDozeAlpha(scrim); 358 float alpha = 1 - (1 - alpha1) * (1 - alpha2); 359 alpha = Math.max(0, Math.min(1.0f, alpha)); 360 ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0)); 361 } else { 362 scrim.setAlpha(alpha1); 363 } 364 } 365 366 private void startScrimAnimation(final View scrim, float target) { 367 float current = getCurrentScrimAlpha(scrim); 368 ValueAnimator anim = ValueAnimator.ofFloat(current, target); 369 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 370 @Override 371 public void onAnimationUpdate(ValueAnimator animation) { 372 float alpha = (float) animation.getAnimatedValue(); 373 setCurrentScrimAlpha(scrim, alpha); 374 updateScrimColor(scrim); 375 } 376 }); 377 anim.setInterpolator(getInterpolator()); 378 anim.setStartDelay(mAnimationDelay); 379 anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); 380 anim.addListener(new AnimatorListenerAdapter() { 381 @Override 382 public void onAnimationEnd(Animator animation) { 383 if (mOnAnimationFinished != null) { 384 mOnAnimationFinished.run(); 385 mOnAnimationFinished = null; 386 } 387 if (mKeyguardFadingOutInProgress) { 388 mKeyguardFadeoutAnimation = null; 389 mKeyguardFadingOutInProgress = false; 390 } 391 scrim.setTag(TAG_KEY_ANIM, null); 392 scrim.setTag(TAG_KEY_ANIM_TARGET, null); 393 } 394 }); 395 anim.start(); 396 if (mAnimateKeyguardFadingOut) { 397 mKeyguardFadingOutInProgress = true; 398 mKeyguardFadeoutAnimation = anim; 399 } 400 if (mSkipFirstFrame) { 401 anim.setCurrentPlayTime(16); 402 } 403 scrim.setTag(TAG_KEY_ANIM, anim); 404 scrim.setTag(TAG_KEY_ANIM_TARGET, target); 405 } 406 407 private Interpolator getInterpolator() { 408 if (mAnimateKeyguardFadingOut && mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { 409 return KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED; 410 } else if (mAnimateKeyguardFadingOut) { 411 return KEYGUARD_FADE_OUT_INTERPOLATOR; 412 } else { 413 return mInterpolator; 414 } 415 } 416 417 @Override 418 public boolean onPreDraw() { 419 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 420 mUpdatePending = false; 421 if (mDontAnimateBouncerChanges) { 422 mDontAnimateBouncerChanges = false; 423 } 424 updateScrims(); 425 mDurationOverride = -1; 426 mAnimationDelay = 0; 427 mSkipFirstFrame = false; 428 429 // Make sure that we always call the listener even if we didn't start an animation. 430 endAnimateKeyguardFadingOut(false /* force */); 431 return true; 432 } 433 434 private void endAnimateKeyguardFadingOut(boolean force) { 435 mAnimateKeyguardFadingOut = false; 436 if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) { 437 if (mOnAnimationFinished != null) { 438 mOnAnimationFinished.run(); 439 mOnAnimationFinished = null; 440 } 441 mKeyguardFadingOutInProgress = false; 442 } 443 } 444 445 private boolean isAnimating(View scrim) { 446 return scrim.getTag(TAG_KEY_ANIM) != null; 447 } 448 449 public void setDrawBehindAsSrc(boolean asSrc) { 450 mScrimBehind.setDrawAsSrc(asSrc); 451 } 452 453 @Override 454 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { 455 } 456 457 @Override 458 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { 459 mPinnedHeadsUpCount++; 460 updateHeadsUpScrim(true); 461 } 462 463 @Override 464 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { 465 mPinnedHeadsUpCount--; 466 if (headsUp == mDraggedHeadsUpView) { 467 mDraggedHeadsUpView = null; 468 mTopHeadsUpDragAmount = 0.0f; 469 } 470 updateHeadsUpScrim(true); 471 } 472 473 @Override 474 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { 475 } 476 477 private void updateHeadsUpScrim(boolean animate) { 478 updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha); 479 } 480 481 private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) { 482 if (mKeyguardFadingOutInProgress) { 483 return; 484 } 485 486 ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim, 487 TAG_KEY_ANIM); 488 float animEndValue = -1; 489 if (previousAnimator != null) { 490 if (animate || alpha == currentAlpha) { 491 previousAnimator.cancel(); 492 } else { 493 animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA); 494 } 495 } 496 if (alpha != currentAlpha && alpha != animEndValue) { 497 if (animate) { 498 startScrimAnimation(scrim, alpha); 499 scrim.setTag(TAG_START_ALPHA, currentAlpha); 500 scrim.setTag(TAG_END_ALPHA, alpha); 501 } else { 502 if (previousAnimator != null) { 503 float previousStartValue = StackStateAnimator.getChildTag(scrim, 504 TAG_START_ALPHA); 505 float previousEndValue = StackStateAnimator.getChildTag(scrim, 506 TAG_END_ALPHA); 507 // we need to increase all animation keyframes of the previous animator by the 508 // relative change to the end value 509 PropertyValuesHolder[] values = previousAnimator.getValues(); 510 float relativeDiff = alpha - previousEndValue; 511 float newStartValue = previousStartValue + relativeDiff; 512 newStartValue = Math.max(0, Math.min(1.0f, newStartValue)); 513 values[0].setFloatValues(newStartValue, alpha); 514 scrim.setTag(TAG_START_ALPHA, newStartValue); 515 scrim.setTag(TAG_END_ALPHA, alpha); 516 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 517 } else { 518 // update the alpha directly 519 setCurrentScrimAlpha(scrim, alpha); 520 updateScrimColor(scrim); 521 } 522 } 523 } 524 } 525 526 /** 527 * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means 528 * the heads up is in its resting space and 1 means it's fully dragged out. 529 * 530 * @param draggedHeadsUpView the dragged view 531 * @param topHeadsUpDragAmount how far is it dragged 532 */ 533 public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) { 534 mTopHeadsUpDragAmount = topHeadsUpDragAmount; 535 mDraggedHeadsUpView = draggedHeadsUpView; 536 updateHeadsUpScrim(false); 537 } 538 539 private float calculateHeadsUpAlpha() { 540 float alpha; 541 if (mPinnedHeadsUpCount >= 2) { 542 alpha = 1.0f; 543 } else if (mPinnedHeadsUpCount == 0) { 544 alpha = 0.0f; 545 } else { 546 alpha = 1.0f - mTopHeadsUpDragAmount; 547 } 548 float expandFactor = (1.0f - mFraction); 549 expandFactor = Math.max(expandFactor, 0.0f); 550 return alpha * expandFactor; 551 } 552 553 public void forceHideScrims(boolean hide) { 554 mForceHideScrims = hide; 555 mAnimateChange = false; 556 scheduleUpdate(); 557 } 558 559 public void dontAnimateBouncerChangesUntilNextFrame() { 560 mDontAnimateBouncerChanges = true; 561 } 562 563 public void setExcludedBackgroundArea(Rect area) { 564 mScrimBehind.setExcludedArea(area); 565 } 566 567 public void setLeftInset(int inset) { 568 mScrimBehind.setLeftInset(inset); 569 } 570 571 public int getScrimBehindColor() { 572 return mScrimBehind.getScrimColorWithAlpha(); 573 } 574 575 public void setScrimBehindChangeRunnable(Runnable changeRunnable) { 576 mScrimBehind.setChangeRunnable(changeRunnable); 577 } 578 579 public void onDensityOrFontScaleChanged() { 580 ViewGroup.LayoutParams layoutParams = mHeadsUpScrim.getLayoutParams(); 581 layoutParams.height = mHeadsUpScrim.getResources().getDimensionPixelSize( 582 R.dimen.heads_up_scrim_height); 583 mHeadsUpScrim.setLayoutParams(layoutParams); 584 } 585 586 public void setCurrentUser(int currentUser) { 587 // Don't care in the base class. 588 } 589 } 590