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