ScrimController.java revision dbc3dce886cb7abba23a9f18e60d0f5af5c93226
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.ValueAnimator; 22import android.graphics.Color; 23import android.graphics.drawable.ColorDrawable; 24import android.util.Log; 25import android.view.View; 26import android.view.ViewTreeObserver; 27import android.view.animation.DecelerateInterpolator; 28import android.view.animation.Interpolator; 29 30import com.android.systemui.R; 31 32/** 33 * Controls both the scrim behind the notifications and in front of the notifications (when a 34 * security method gets shown). 35 */ 36public class ScrimController implements ViewTreeObserver.OnPreDrawListener { 37 private static final String TAG = "ScrimController"; 38 private static final boolean DEBUG = false; 39 40 private static final float SCRIM_BEHIND_ALPHA = 0.62f; 41 private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.5f; 42 private static final float SCRIM_IN_FRONT_ALPHA = 0.75f; 43 private static final long ANIMATION_DURATION = 220; 44 private static final int TAG_KEY_ANIM = R.id.scrim; 45 46 private static final int NUM_TEASES = 3; 47 private static final long TEASE_IN_ANIMATION_DURATION = 1000; 48 private static final long TEASE_VISIBLE_DURATION = 2000; 49 private static final long TEASE_OUT_ANIMATION_DURATION = 1000; 50 private static final long TEASE_INVISIBLE_DURATION = 1000; 51 private static final long TEASE_DURATION = TEASE_IN_ANIMATION_DURATION 52 + TEASE_VISIBLE_DURATION + TEASE_OUT_ANIMATION_DURATION + TEASE_INVISIBLE_DURATION; 53 private static final long PRE_TEASE_DELAY = 1000; 54 55 private final View mScrimBehind; 56 private final View mScrimInFront; 57 private final UnlockMethodCache mUnlockMethodCache; 58 59 private boolean mKeyguardShowing; 60 private float mFraction; 61 62 private boolean mDarkenWhileDragging; 63 private boolean mBouncerShowing; 64 private boolean mAnimateChange; 65 private boolean mUpdatePending; 66 private boolean mExpanding; 67 private boolean mAnimateKeyguardFadingOut; 68 private long mDurationOverride = -1; 69 private long mAnimationDelay; 70 private Runnable mOnAnimationFinished; 71 private boolean mAnimationStarted; 72 private boolean mDozing; 73 private int mTeasesRemaining; 74 private final Interpolator mInterpolator = new DecelerateInterpolator(); 75 76 public ScrimController(View scrimBehind, View scrimInFront) { 77 mScrimBehind = scrimBehind; 78 mScrimInFront = scrimInFront; 79 mUnlockMethodCache = UnlockMethodCache.getInstance(scrimBehind.getContext()); 80 } 81 82 public void setKeyguardShowing(boolean showing) { 83 mKeyguardShowing = showing; 84 scheduleUpdate(); 85 } 86 87 public void onTrackingStarted() { 88 mExpanding = true; 89 mDarkenWhileDragging = !mUnlockMethodCache.isMethodInsecure(); 90 } 91 92 public void onExpandingFinished() { 93 mExpanding = false; 94 } 95 96 public void setPanelExpansion(float fraction) { 97 if (mFraction != fraction) { 98 mFraction = fraction; 99 scheduleUpdate(); 100 } 101 } 102 103 public void setBouncerShowing(boolean showing) { 104 mBouncerShowing = showing; 105 mAnimateChange = !mExpanding; 106 scheduleUpdate(); 107 } 108 109 public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) { 110 mAnimateKeyguardFadingOut = true; 111 mDurationOverride = duration; 112 mAnimationDelay = delay; 113 mAnimateChange = true; 114 mOnAnimationFinished = onAnimationFinished; 115 scheduleUpdate(); 116 } 117 118 public void animateGoingToFullShade(long delay, long duration) { 119 mDurationOverride = duration; 120 mAnimationDelay = delay; 121 mAnimateChange = true; 122 scheduleUpdate(); 123 } 124 125 public void setDozing(boolean dozing) { 126 if (mDozing == dozing) return; 127 mDozing = dozing; 128 if (!mDozing) { 129 cancelTeasing(); 130 } 131 scheduleUpdate(); 132 } 133 134 /** When dozing, fade screen contents in and out a few times using the front scrim. */ 135 public long tease() { 136 if (!mDozing) return 0; 137 mTeasesRemaining = NUM_TEASES; 138 mScrimInFront.postDelayed(mTeaseIn, PRE_TEASE_DELAY); 139 return PRE_TEASE_DELAY + NUM_TEASES * TEASE_DURATION; 140 } 141 142 private void cancelTeasing() { 143 mTeasesRemaining = 0; 144 mScrimInFront.removeCallbacks(mTeaseIn); 145 mScrimInFront.removeCallbacks(mTeaseOut); 146 } 147 148 private void scheduleUpdate() { 149 if (mUpdatePending) return; 150 151 // Make sure that a frame gets scheduled. 152 mScrimBehind.invalidate(); 153 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 154 mUpdatePending = true; 155 } 156 157 private void updateScrims() { 158 if (mAnimateKeyguardFadingOut) { 159 setScrimInFrontColor(0f); 160 setScrimBehindColor(0f); 161 }else if (!mKeyguardShowing && !mBouncerShowing) { 162 updateScrimNormal(); 163 setScrimInFrontColor(0); 164 } else { 165 updateScrimKeyguard(); 166 } 167 mAnimateChange = false; 168 } 169 170 private void updateScrimKeyguard() { 171 if (mExpanding && mDarkenWhileDragging) { 172 float behindFraction = Math.max(0, Math.min(mFraction, 1)); 173 float fraction = 1 - behindFraction; 174 setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA); 175 setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD); 176 } else if (mBouncerShowing) { 177 setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); 178 setScrimBehindColor(0f); 179 } else if (mDozing) { 180 setScrimInFrontColor(1); 181 } else { 182 setScrimInFrontColor(0f); 183 setScrimBehindColor(SCRIM_BEHIND_ALPHA_KEYGUARD); 184 } 185 } 186 187 private void updateScrimNormal() { 188 float frac = mFraction; 189 // let's start this 20% of the way down the screen 190 frac = frac * 1.2f - 0.2f; 191 if (frac <= 0) { 192 setScrimBehindColor(0); 193 } else { 194 // woo, special effects 195 final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); 196 setScrimBehindColor(k * SCRIM_BEHIND_ALPHA); 197 } 198 } 199 200 private void setScrimBehindColor(float alpha) { 201 setScrimColor(mScrimBehind, alpha); 202 } 203 204 private void setScrimInFrontColor(float alpha) { 205 setScrimColor(mScrimInFront, alpha); 206 if (alpha == 0f) { 207 mScrimInFront.setClickable(false); 208 } else { 209 210 // Eat touch events. 211 mScrimInFront.setClickable(true); 212 } 213 } 214 215 private void setScrimColor(View scrim, float alpha) { 216 int color = Color.argb((int) (alpha * 255), 0, 0, 0); 217 if (mAnimateChange) { 218 startScrimAnimation(scrim, color); 219 } else { 220 scrim.setBackgroundColor(color); 221 } 222 } 223 224 private void startScrimAnimation(final View scrim, int targetColor) { 225 int current = getBackgroundAlpha(scrim); 226 int target = Color.alpha(targetColor); 227 if (current == targetColor) { 228 return; 229 } 230 Object runningAnim = scrim.getTag(TAG_KEY_ANIM); 231 if (runningAnim instanceof ValueAnimator) { 232 ((ValueAnimator) runningAnim).cancel(); 233 } 234 ValueAnimator anim = ValueAnimator.ofInt(current, target); 235 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 236 @Override 237 public void onAnimationUpdate(ValueAnimator animation) { 238 int value = (int) animation.getAnimatedValue(); 239 scrim.setBackgroundColor(Color.argb(value, 0, 0, 0)); 240 } 241 }); 242 anim.setInterpolator(mInterpolator); 243 anim.setStartDelay(mAnimationDelay); 244 anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); 245 anim.addListener(new AnimatorListenerAdapter() { 246 247 @Override 248 public void onAnimationEnd(Animator animation) { 249 if (mOnAnimationFinished != null) { 250 mOnAnimationFinished.run(); 251 mOnAnimationFinished = null; 252 } 253 scrim.setTag(TAG_KEY_ANIM, null); 254 } 255 }); 256 anim.start(); 257 scrim.setTag(TAG_KEY_ANIM, anim); 258 mAnimationStarted = true; 259 } 260 261 private int getBackgroundAlpha(View scrim) { 262 if (scrim.getBackground() instanceof ColorDrawable) { 263 ColorDrawable drawable = (ColorDrawable) scrim.getBackground(); 264 return Color.alpha(drawable.getColor()); 265 } else { 266 return 0; 267 } 268 } 269 270 @Override 271 public boolean onPreDraw() { 272 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 273 mUpdatePending = false; 274 updateScrims(); 275 mAnimateKeyguardFadingOut = false; 276 mDurationOverride = -1; 277 mAnimationDelay = 0; 278 279 // Make sure that we always call the listener even if we didn't start an animation. 280 if (!mAnimationStarted && mOnAnimationFinished != null) { 281 mOnAnimationFinished.run(); 282 mOnAnimationFinished = null; 283 } 284 mAnimationStarted = false; 285 return true; 286 } 287 288 private final Runnable mTeaseIn = new Runnable() { 289 @Override 290 public void run() { 291 if (DEBUG) Log.d(TAG, "Tease in, mDozing=" + mDozing 292 + " mTeasesRemaining=" + mTeasesRemaining); 293 if (!mDozing || mTeasesRemaining == 0) return; 294 mTeasesRemaining--; 295 mDurationOverride = TEASE_IN_ANIMATION_DURATION; 296 mAnimationDelay = 0; 297 mAnimateChange = true; 298 mOnAnimationFinished = mTeaseInFinished; 299 setScrimColor(mScrimInFront, 0); 300 } 301 }; 302 303 private final Runnable mTeaseInFinished = new Runnable() { 304 @Override 305 public void run() { 306 if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing); 307 if (!mDozing) return; 308 mScrimInFront.postDelayed(mTeaseOut, TEASE_VISIBLE_DURATION); 309 } 310 }; 311 312 private final Runnable mTeaseOut = new Runnable() { 313 @Override 314 public void run() { 315 if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing); 316 if (!mDozing) return; 317 mDurationOverride = TEASE_OUT_ANIMATION_DURATION; 318 mAnimationDelay = 0; 319 mAnimateChange = true; 320 mOnAnimationFinished = mTeaseOutFinished; 321 setScrimColor(mScrimInFront, 1); 322 } 323 }; 324 325 private final Runnable mTeaseOutFinished = new Runnable() { 326 @Override 327 public void run() { 328 if (DEBUG) Log.d(TAG, "Tease out finished, mTeasesRemaining=" + mTeasesRemaining); 329 if (mTeasesRemaining > 0) { 330 mScrimInFront.postDelayed(mTeaseIn, TEASE_INVISIBLE_DURATION); 331 } 332 } 333 }; 334} 335