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