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