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