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