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