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