ScrimController.java revision 4d69e2219390bce567b0d2c986d0bd3a3182eda5
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.annotation.NonNull; 23import android.content.Context; 24import android.graphics.Color; 25import android.graphics.drawable.ColorDrawable; 26import android.util.Log; 27import android.view.View; 28import android.view.ViewTreeObserver; 29import android.view.animation.AnimationUtils; 30import android.view.animation.DecelerateInterpolator; 31import android.view.animation.Interpolator; 32 33import com.android.systemui.R; 34import com.android.systemui.doze.DozeHost; 35import com.android.systemui.doze.DozeLog; 36 37/** 38 * Controls both the scrim behind the notifications and in front of the notifications (when a 39 * security method gets shown). 40 */ 41public class ScrimController implements ViewTreeObserver.OnPreDrawListener { 42 private static final String TAG = "ScrimController"; 43 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 44 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 53 private final View mScrimBehind; 54 private final View mScrimInFront; 55 private final UnlockMethodCache mUnlockMethodCache; 56 private final DozeParameters mDozeParameters; 57 58 private boolean mKeyguardShowing; 59 private float mFraction; 60 61 private boolean mDarkenWhileDragging; 62 private boolean mBouncerShowing; 63 private boolean mAnimateChange; 64 private boolean mUpdatePending; 65 private boolean mExpanding; 66 private boolean mAnimateKeyguardFadingOut; 67 private long mDurationOverride = -1; 68 private long mAnimationDelay; 69 private Runnable mOnAnimationFinished; 70 private boolean mAnimationStarted; 71 private boolean mDozing; 72 private DozeHost.PulseCallback mPulseCallback; 73 private final Interpolator mInterpolator = new DecelerateInterpolator(); 74 private final Interpolator mLinearOutSlowInInterpolator; 75 76 public ScrimController(View scrimBehind, View scrimInFront) { 77 mScrimBehind = scrimBehind; 78 mScrimInFront = scrimInFront; 79 final Context context = scrimBehind.getContext(); 80 mUnlockMethodCache = UnlockMethodCache.getInstance(context); 81 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 82 android.R.interpolator.linear_out_slow_in); 83 mDozeParameters = new DozeParameters(context); 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 mAnimateChange = true; 135 } else { 136 mAnimateChange = false; 137 } 138 scheduleUpdate(); 139 } 140 141 /** When dozing, fade screen contents in and out using the front scrim. */ 142 public void pulse(@NonNull DozeHost.PulseCallback callback) { 143 if (callback == null) { 144 throw new IllegalArgumentException("callback must not be null"); 145 } 146 147 if (!mDozing || mPulseCallback != null) { 148 // Pulse suppressed. 149 mPulseCallback.onPulseFinished(); 150 return; 151 } 152 153 // Begin pulse. Note that it's very important that the pulse finished callback 154 // be invoked when we're done so that the caller can drop the pulse wakelock. 155 mPulseCallback = callback; 156 mScrimInFront.post(mPulseIn); 157 } 158 159 public boolean isPulsing() { 160 return mPulseCallback != null; 161 } 162 163 private void cancelPulsing() { 164 if (DEBUG) Log.d(TAG, "Cancel pulsing"); 165 166 if (mPulseCallback != null) { 167 mScrimInFront.removeCallbacks(mPulseIn); 168 mScrimInFront.removeCallbacks(mPulseOut); 169 pulseFinished(); 170 } 171 } 172 173 private void pulseStarted() { 174 if (mPulseCallback != null) { 175 mPulseCallback.onPulseStarted(); 176 } 177 } 178 179 private void pulseFinished() { 180 if (mPulseCallback != null) { 181 mPulseCallback.onPulseFinished(); 182 mPulseCallback = null; 183 } 184 } 185 186 private void scheduleUpdate() { 187 if (mUpdatePending) return; 188 189 // Make sure that a frame gets scheduled. 190 mScrimBehind.invalidate(); 191 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 192 mUpdatePending = true; 193 } 194 195 private void updateScrims() { 196 if (mAnimateKeyguardFadingOut) { 197 setScrimInFrontColor(0f); 198 setScrimBehindColor(0f); 199 } else if (!mKeyguardShowing && !mBouncerShowing) { 200 updateScrimNormal(); 201 setScrimInFrontColor(0); 202 } else { 203 updateScrimKeyguard(); 204 } 205 mAnimateChange = false; 206 } 207 208 private void updateScrimKeyguard() { 209 if (mExpanding && mDarkenWhileDragging) { 210 float behindFraction = Math.max(0, Math.min(mFraction, 1)); 211 float fraction = 1 - behindFraction; 212 fraction = (float) Math.pow(fraction, 0.8f); 213 behindFraction = (float) Math.pow(behindFraction, 0.8f); 214 setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA); 215 setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD); 216 } else if (mBouncerShowing) { 217 setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); 218 setScrimBehindColor(0f); 219 } else if (mDozing) { 220 setScrimInFrontColor(1); 221 } else { 222 float fraction = Math.max(0, Math.min(mFraction, 1)); 223 setScrimInFrontColor(0f); 224 setScrimBehindColor(fraction 225 * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING) 226 + SCRIM_BEHIND_ALPHA_UNLOCKING); 227 } 228 } 229 230 private void updateScrimNormal() { 231 float frac = mFraction; 232 // let's start this 20% of the way down the screen 233 frac = frac * 1.2f - 0.2f; 234 if (frac <= 0) { 235 setScrimBehindColor(0); 236 } else { 237 // woo, special effects 238 final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); 239 setScrimBehindColor(k * SCRIM_BEHIND_ALPHA); 240 } 241 } 242 243 private void setScrimBehindColor(float alpha) { 244 setScrimColor(mScrimBehind, alpha); 245 } 246 247 private void setScrimInFrontColor(float alpha) { 248 setScrimColor(mScrimInFront, alpha); 249 if (alpha == 0f) { 250 mScrimInFront.setClickable(false); 251 } else { 252 253 // Eat touch events (unless dozing). 254 mScrimInFront.setClickable(!mDozing); 255 } 256 } 257 258 private void setScrimColor(View scrim, float alpha) { 259 int color = Color.argb((int) (alpha * 255), 0, 0, 0); 260 if (mAnimateChange) { 261 startScrimAnimation(scrim, color); 262 } else { 263 scrim.setBackgroundColor(color); 264 } 265 } 266 267 private void startScrimAnimation(final View scrim, int targetColor) { 268 int current = getBackgroundAlpha(scrim); 269 int target = Color.alpha(targetColor); 270 if (current == targetColor) { 271 return; 272 } 273 Object runningAnim = scrim.getTag(TAG_KEY_ANIM); 274 if (runningAnim instanceof ValueAnimator) { 275 ((ValueAnimator) runningAnim).cancel(); 276 } 277 ValueAnimator anim = ValueAnimator.ofInt(current, target); 278 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 279 @Override 280 public void onAnimationUpdate(ValueAnimator animation) { 281 int value = (int) animation.getAnimatedValue(); 282 scrim.setBackgroundColor(Color.argb(value, 0, 0, 0)); 283 } 284 }); 285 anim.setInterpolator(mAnimateKeyguardFadingOut 286 ? mLinearOutSlowInInterpolator 287 : mInterpolator); 288 anim.setStartDelay(mAnimationDelay); 289 anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); 290 anim.addListener(new AnimatorListenerAdapter() { 291 @Override 292 public void onAnimationEnd(Animator animation) { 293 if (mOnAnimationFinished != null) { 294 mOnAnimationFinished.run(); 295 mOnAnimationFinished = null; 296 } 297 scrim.setTag(TAG_KEY_ANIM, null); 298 } 299 }); 300 anim.start(); 301 scrim.setTag(TAG_KEY_ANIM, anim); 302 mAnimationStarted = true; 303 } 304 305 private int getBackgroundAlpha(View scrim) { 306 if (scrim.getBackground() instanceof ColorDrawable) { 307 ColorDrawable drawable = (ColorDrawable) scrim.getBackground(); 308 return Color.alpha(drawable.getColor()); 309 } else { 310 return 0; 311 } 312 } 313 314 @Override 315 public boolean onPreDraw() { 316 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 317 mUpdatePending = false; 318 updateScrims(); 319 mAnimateKeyguardFadingOut = false; 320 mDurationOverride = -1; 321 mAnimationDelay = 0; 322 323 // Make sure that we always call the listener even if we didn't start an animation. 324 if (!mAnimationStarted && mOnAnimationFinished != null) { 325 mOnAnimationFinished.run(); 326 mOnAnimationFinished = null; 327 } 328 mAnimationStarted = false; 329 return true; 330 } 331 332 private final Runnable mPulseIn = new Runnable() { 333 @Override 334 public void run() { 335 if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing); 336 if (!mDozing) return; 337 DozeLog.tracePulseStart(); 338 mDurationOverride = mDozeParameters.getPulseInDuration(); 339 mAnimationDelay = 0; 340 mAnimateChange = true; 341 mOnAnimationFinished = mPulseInFinished; 342 setScrimColor(mScrimInFront, 0); 343 344 // Signal that the pulse is ready to turn the screen on and draw. 345 pulseStarted(); 346 } 347 }; 348 349 private final Runnable mPulseInFinished = new Runnable() { 350 @Override 351 public void run() { 352 if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); 353 if (!mDozing) return; 354 mScrimInFront.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); 355 } 356 }; 357 358 private final Runnable mPulseOut = new Runnable() { 359 @Override 360 public void run() { 361 if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing); 362 if (!mDozing) return; 363 mDurationOverride = mDozeParameters.getPulseOutDuration(); 364 mAnimationDelay = 0; 365 mAnimateChange = true; 366 mOnAnimationFinished = mPulseOutFinished; 367 setScrimColor(mScrimInFront, 1); 368 } 369 }; 370 371 private final Runnable mPulseOutFinished = new Runnable() { 372 @Override 373 public void run() { 374 if (DEBUG) Log.d(TAG, "Pulse out finished"); 375 DozeLog.tracePulseFinish(); 376 377 // Signal that the pulse is all finished so we can turn the screen off now. 378 pulseFinished(); 379 } 380 }; 381} 382