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.isUserUnlocked()) { 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 animateGoingToFullShade(long delay, long duration) { 200 mDurationOverride = duration; 201 mAnimationDelay = delay; 202 mAnimateChange = true; 203 scheduleUpdate(); 204 } 205 206 public void animateNextChange() { 207 mAnimateChange = true; 208 } 209 210 public void setDozing(boolean dozing) { 211 if (mDozing != dozing) { 212 mDozing = dozing; 213 scheduleUpdate(); 214 } 215 } 216 217 public void setDozeInFrontAlpha(float alpha) { 218 mDozeInFrontAlpha = alpha; 219 updateScrimColor(mScrimInFront); 220 } 221 222 public void setDozeBehindAlpha(float alpha) { 223 mDozeBehindAlpha = alpha; 224 updateScrimColor(mScrimBehind); 225 } 226 227 public float getDozeBehindAlpha() { 228 return mDozeBehindAlpha; 229 } 230 231 public float getDozeInFrontAlpha() { 232 return mDozeInFrontAlpha; 233 } 234 235 private float getScrimInFrontAlpha() { 236 return mKeyguardUpdateMonitor.isUserUnlocked() 237 ? SCRIM_IN_FRONT_ALPHA 238 : SCRIM_IN_FRONT_ALPHA_LOCKED; 239 } 240 private void scheduleUpdate() { 241 if (mUpdatePending) return; 242 243 // Make sure that a frame gets scheduled. 244 mScrimBehind.invalidate(); 245 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 246 mUpdatePending = true; 247 } 248 249 protected void updateScrims() { 250 if (mAnimateKeyguardFadingOut || mForceHideScrims) { 251 setScrimInFrontColor(0f); 252 setScrimBehindColor(0f); 253 } else if (mWakeAndUnlocking) { 254 255 // During wake and unlock, we first hide everything behind a black scrim, which then 256 // gets faded out from animateKeyguardFadingOut. 257 if (mDozing) { 258 setScrimInFrontColor(0f); 259 setScrimBehindColor(1f); 260 } else { 261 setScrimInFrontColor(1f); 262 setScrimBehindColor(0f); 263 } 264 } else if (!mKeyguardShowing && !mBouncerShowing) { 265 updateScrimNormal(); 266 setScrimInFrontColor(0); 267 } else { 268 updateScrimKeyguard(); 269 } 270 mAnimateChange = false; 271 } 272 273 private void updateScrimKeyguard() { 274 if (mExpanding && mDarkenWhileDragging) { 275 float behindFraction = Math.max(0, Math.min(mFraction, 1)); 276 float fraction = 1 - behindFraction; 277 fraction = (float) Math.pow(fraction, 0.8f); 278 behindFraction = (float) Math.pow(behindFraction, 0.8f); 279 setScrimInFrontColor(fraction * getScrimInFrontAlpha()); 280 setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard); 281 } else if (mBouncerShowing) { 282 setScrimInFrontColor(getScrimInFrontAlpha()); 283 setScrimBehindColor(0f); 284 } else { 285 float fraction = Math.max(0, Math.min(mFraction, 1)); 286 setScrimInFrontColor(0f); 287 setScrimBehindColor(fraction 288 * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking) 289 + mScrimBehindAlphaUnlocking); 290 } 291 } 292 293 private void updateScrimNormal() { 294 float frac = mFraction; 295 // let's start this 20% of the way down the screen 296 frac = frac * 1.2f - 0.2f; 297 if (frac <= 0) { 298 setScrimBehindColor(0); 299 } else { 300 // woo, special effects 301 final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); 302 setScrimBehindColor(k * mScrimBehindAlpha); 303 } 304 } 305 306 private void setScrimBehindColor(float alpha) { 307 setScrimColor(mScrimBehind, alpha); 308 } 309 310 private void setScrimInFrontColor(float alpha) { 311 setScrimColor(mScrimInFront, alpha); 312 if (alpha == 0f) { 313 mScrimInFront.setClickable(false); 314 } else { 315 316 // Eat touch events (unless dozing). 317 mScrimInFront.setClickable(!mDozing); 318 } 319 } 320 321 private void setScrimColor(View scrim, float alpha) { 322 updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim)); 323 } 324 325 private float getDozeAlpha(View scrim) { 326 return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha; 327 } 328 329 private float getCurrentScrimAlpha(View scrim) { 330 return scrim == mScrimBehind ? mCurrentBehindAlpha 331 : scrim == mScrimInFront ? mCurrentInFrontAlpha 332 : mCurrentHeadsUpAlpha; 333 } 334 335 private void setCurrentScrimAlpha(View scrim, float alpha) { 336 if (scrim == mScrimBehind) { 337 mCurrentBehindAlpha = alpha; 338 } else if (scrim == mScrimInFront) { 339 mCurrentInFrontAlpha = alpha; 340 } else { 341 alpha = Math.max(0.0f, Math.min(1.0f, alpha)); 342 mCurrentHeadsUpAlpha = alpha; 343 } 344 } 345 346 private void updateScrimColor(View scrim) { 347 float alpha1 = getCurrentScrimAlpha(scrim); 348 if (scrim instanceof ScrimView) { 349 float alpha2 = getDozeAlpha(scrim); 350 float alpha = 1 - (1 - alpha1) * (1 - alpha2); 351 alpha = Math.max(0, Math.min(1.0f, alpha)); 352 ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0)); 353 } else { 354 scrim.setAlpha(alpha1); 355 } 356 } 357 358 private void startScrimAnimation(final View scrim, float target) { 359 float current = getCurrentScrimAlpha(scrim); 360 ValueAnimator anim = ValueAnimator.ofFloat(current, target); 361 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 362 @Override 363 public void onAnimationUpdate(ValueAnimator animation) { 364 float alpha = (float) animation.getAnimatedValue(); 365 setCurrentScrimAlpha(scrim, alpha); 366 updateScrimColor(scrim); 367 } 368 }); 369 anim.setInterpolator(getInterpolator()); 370 anim.setStartDelay(mAnimationDelay); 371 anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); 372 anim.addListener(new AnimatorListenerAdapter() { 373 @Override 374 public void onAnimationEnd(Animator animation) { 375 if (mOnAnimationFinished != null) { 376 mOnAnimationFinished.run(); 377 mOnAnimationFinished = null; 378 } 379 if (mKeyguardFadingOutInProgress) { 380 mKeyguardFadeoutAnimation = null; 381 mKeyguardFadingOutInProgress = false; 382 } 383 scrim.setTag(TAG_KEY_ANIM, null); 384 scrim.setTag(TAG_KEY_ANIM_TARGET, null); 385 } 386 }); 387 anim.start(); 388 if (mAnimateKeyguardFadingOut) { 389 mKeyguardFadingOutInProgress = true; 390 mKeyguardFadeoutAnimation = anim; 391 } 392 if (mSkipFirstFrame) { 393 anim.setCurrentPlayTime(16); 394 } 395 scrim.setTag(TAG_KEY_ANIM, anim); 396 scrim.setTag(TAG_KEY_ANIM_TARGET, target); 397 } 398 399 private Interpolator getInterpolator() { 400 if (mAnimateKeyguardFadingOut && !mKeyguardUpdateMonitor.isUserUnlocked()) { 401 return KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED; 402 } else if (mAnimateKeyguardFadingOut) { 403 return KEYGUARD_FADE_OUT_INTERPOLATOR; 404 } else { 405 return mInterpolator; 406 } 407 } 408 409 @Override 410 public boolean onPreDraw() { 411 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 412 mUpdatePending = false; 413 if (mDontAnimateBouncerChanges) { 414 mDontAnimateBouncerChanges = false; 415 } 416 updateScrims(); 417 mDurationOverride = -1; 418 mAnimationDelay = 0; 419 mSkipFirstFrame = false; 420 421 // Make sure that we always call the listener even if we didn't start an animation. 422 endAnimateKeyguardFadingOut(false /* force */); 423 return true; 424 } 425 426 private void endAnimateKeyguardFadingOut(boolean force) { 427 mAnimateKeyguardFadingOut = false; 428 if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) { 429 if (mOnAnimationFinished != null) { 430 mOnAnimationFinished.run(); 431 mOnAnimationFinished = null; 432 } 433 mKeyguardFadingOutInProgress = false; 434 } 435 } 436 437 private boolean isAnimating(View scrim) { 438 return scrim.getTag(TAG_KEY_ANIM) != null; 439 } 440 441 public void setDrawBehindAsSrc(boolean asSrc) { 442 mScrimBehind.setDrawAsSrc(asSrc); 443 } 444 445 @Override 446 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { 447 } 448 449 @Override 450 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { 451 mPinnedHeadsUpCount++; 452 updateHeadsUpScrim(true); 453 } 454 455 @Override 456 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { 457 mPinnedHeadsUpCount--; 458 if (headsUp == mDraggedHeadsUpView) { 459 mDraggedHeadsUpView = null; 460 mTopHeadsUpDragAmount = 0.0f; 461 } 462 updateHeadsUpScrim(true); 463 } 464 465 @Override 466 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { 467 } 468 469 private void updateHeadsUpScrim(boolean animate) { 470 updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha); 471 } 472 473 private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) { 474 if (mKeyguardFadingOutInProgress) { 475 return; 476 } 477 478 ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim, 479 TAG_KEY_ANIM); 480 float animEndValue = -1; 481 if (previousAnimator != null) { 482 if (animate || alpha == currentAlpha) { 483 previousAnimator.cancel(); 484 } else { 485 animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA); 486 } 487 } 488 if (alpha != currentAlpha && alpha != animEndValue) { 489 if (animate) { 490 startScrimAnimation(scrim, alpha); 491 scrim.setTag(TAG_START_ALPHA, currentAlpha); 492 scrim.setTag(TAG_END_ALPHA, alpha); 493 } else { 494 if (previousAnimator != null) { 495 float previousStartValue = StackStateAnimator.getChildTag(scrim, 496 TAG_START_ALPHA); 497 float previousEndValue = StackStateAnimator.getChildTag(scrim, 498 TAG_END_ALPHA); 499 // we need to increase all animation keyframes of the previous animator by the 500 // relative change to the end value 501 PropertyValuesHolder[] values = previousAnimator.getValues(); 502 float relativeDiff = alpha - previousEndValue; 503 float newStartValue = previousStartValue + relativeDiff; 504 newStartValue = Math.max(0, Math.min(1.0f, newStartValue)); 505 values[0].setFloatValues(newStartValue, alpha); 506 scrim.setTag(TAG_START_ALPHA, newStartValue); 507 scrim.setTag(TAG_END_ALPHA, alpha); 508 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); 509 } else { 510 // update the alpha directly 511 setCurrentScrimAlpha(scrim, alpha); 512 updateScrimColor(scrim); 513 } 514 } 515 } 516 } 517 518 /** 519 * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means 520 * the heads up is in its resting space and 1 means it's fully dragged out. 521 * 522 * @param draggedHeadsUpView the dragged view 523 * @param topHeadsUpDragAmount how far is it dragged 524 */ 525 public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) { 526 mTopHeadsUpDragAmount = topHeadsUpDragAmount; 527 mDraggedHeadsUpView = draggedHeadsUpView; 528 updateHeadsUpScrim(false); 529 } 530 531 private float calculateHeadsUpAlpha() { 532 float alpha; 533 if (mPinnedHeadsUpCount >= 2) { 534 alpha = 1.0f; 535 } else if (mPinnedHeadsUpCount == 0) { 536 alpha = 0.0f; 537 } else { 538 alpha = 1.0f - mTopHeadsUpDragAmount; 539 } 540 float expandFactor = (1.0f - mFraction); 541 expandFactor = Math.max(expandFactor, 0.0f); 542 return alpha * expandFactor; 543 } 544 545 public void forceHideScrims(boolean hide) { 546 mForceHideScrims = hide; 547 mAnimateChange = false; 548 scheduleUpdate(); 549 } 550 551 public void dontAnimateBouncerChangesUntilNextFrame() { 552 mDontAnimateBouncerChanges = true; 553 } 554 555 public void setExcludedBackgroundArea(Rect area) { 556 mScrimBehind.setExcludedArea(area); 557 } 558 559 public void setLeftInset(int inset) { 560 mScrimBehind.setLeftInset(inset); 561 } 562 563 public int getScrimBehindColor() { 564 return mScrimBehind.getScrimColorWithAlpha(); 565 } 566 567 public void setScrimBehindChangeRunnable(Runnable changeRunnable) { 568 mScrimBehind.setChangeRunnable(changeRunnable); 569 } 570 571 public void onDensityOrFontScaleChanged() { 572 ViewGroup.LayoutParams layoutParams = mHeadsUpScrim.getLayoutParams(); 573 layoutParams.height = mHeadsUpScrim.getResources().getDimensionPixelSize( 574 R.dimen.heads_up_scrim_height); 575 mHeadsUpScrim.setLayoutParams(layoutParams); 576 } 577 578 public void setCurrentUser(int currentUser) { 579 // Don't care in the base class. 580 } 581 } 582