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