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