ScrimController.java revision a0fad3ba06d0352f640532e69ed3d540b3795535
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.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; 32import com.android.systemui.doze.DozeLog; 33import com.android.systemui.statusbar.BackDropView; 34import com.android.systemui.statusbar.ScrimView; 35 36/** 37 * Controls both the scrim behind the notifications and in front of the notifications (when a 38 * security method gets shown). 39 */ 40public class ScrimController implements ViewTreeObserver.OnPreDrawListener { 41 private static final String TAG = "ScrimController"; 42 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 43 44 public static final long ANIMATION_DURATION = 220; 45 46 private static final float SCRIM_BEHIND_ALPHA = 0.62f; 47 private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.55f; 48 private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; 49 private static final float SCRIM_IN_FRONT_ALPHA = 0.75f; 50 private static final int TAG_KEY_ANIM = R.id.scrim; 51 52 private final ScrimView mScrimBehind; 53 private final ScrimView mScrimInFront; 54 private final UnlockMethodCache mUnlockMethodCache; 55 private final DozeParameters mDozeParameters; 56 57 private boolean mKeyguardShowing; 58 private float mFraction; 59 60 private boolean mDarkenWhileDragging; 61 private boolean mBouncerShowing; 62 private boolean mAnimateChange; 63 private boolean mUpdatePending; 64 private boolean mExpanding; 65 private boolean mAnimateKeyguardFadingOut; 66 private long mDurationOverride = -1; 67 private long mAnimationDelay; 68 private Runnable mOnAnimationFinished; 69 private boolean mAnimationStarted; 70 private boolean mDozing; 71 private long mPulseEndTime; 72 private final Interpolator mInterpolator = new DecelerateInterpolator(); 73 private final Interpolator mLinearOutSlowInInterpolator; 74 private BackDropView mBackDropView; 75 76 public ScrimController(ScrimView scrimBehind, ScrimView 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 long pulse() { 143 if (!mDozing) return 0; 144 final long now = System.currentTimeMillis(); 145 if (DEBUG) Log.d(TAG, "pulse mPulseEndTime=" + mPulseEndTime + " now=" + now); 146 if (mPulseEndTime != 0 && mPulseEndTime > now) return mPulseEndTime - now; 147 mScrimInFront.post(mPulseIn); 148 mPulseEndTime = now + mDozeParameters.getPulseDuration(); 149 return mPulseEndTime - now; 150 } 151 152 public boolean isPulsing() { 153 return mDozing && mPulseEndTime != 0; 154 } 155 156 private void cancelPulsing() { 157 if (DEBUG) Log.d(TAG, "Cancel pulsing"); 158 mScrimInFront.removeCallbacks(mPulseIn); 159 mScrimInFront.removeCallbacks(mPulseOut); 160 mPulseEndTime = 0; 161 } 162 163 private void scheduleUpdate() { 164 if (mUpdatePending) return; 165 166 // Make sure that a frame gets scheduled. 167 mScrimBehind.invalidate(); 168 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this); 169 mUpdatePending = true; 170 } 171 172 private void updateScrims() { 173 if (mAnimateKeyguardFadingOut) { 174 setScrimInFrontColor(0f); 175 setScrimBehindColor(0f); 176 } else if (!mKeyguardShowing && !mBouncerShowing) { 177 updateScrimNormal(); 178 setScrimInFrontColor(0); 179 } else { 180 updateScrimKeyguard(); 181 } 182 mAnimateChange = false; 183 } 184 185 private void updateScrimKeyguard() { 186 if (mExpanding && mDarkenWhileDragging) { 187 float behindFraction = Math.max(0, Math.min(mFraction, 1)); 188 float fraction = 1 - behindFraction; 189 fraction = (float) Math.pow(fraction, 0.8f); 190 behindFraction = (float) Math.pow(behindFraction, 0.8f); 191 setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA); 192 setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD); 193 } else if (mBouncerShowing) { 194 setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); 195 setScrimBehindColor(0f); 196 } else if (mDozing) { 197 setScrimInFrontColor(1); 198 } else { 199 float fraction = Math.max(0, Math.min(mFraction, 1)); 200 setScrimInFrontColor(0f); 201 setScrimBehindColor(fraction 202 * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING) 203 + SCRIM_BEHIND_ALPHA_UNLOCKING); 204 } 205 } 206 207 private void updateScrimNormal() { 208 float frac = mFraction; 209 // let's start this 20% of the way down the screen 210 frac = frac * 1.2f - 0.2f; 211 if (frac <= 0) { 212 setScrimBehindColor(0); 213 } else { 214 // woo, special effects 215 final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); 216 setScrimBehindColor(k * SCRIM_BEHIND_ALPHA); 217 } 218 } 219 220 private void setScrimBehindColor(float alpha) { 221 setScrimColor(mScrimBehind, alpha); 222 } 223 224 private void setScrimInFrontColor(float alpha) { 225 setScrimColor(mScrimInFront, alpha); 226 if (alpha == 0f) { 227 mScrimInFront.setClickable(false); 228 } else { 229 230 // Eat touch events (unless dozing). 231 mScrimInFront.setClickable(!mDozing); 232 } 233 } 234 235 private void setScrimColor(ScrimView scrim, float alpha) { 236 int color = Color.argb((int) (alpha * 255), 0, 0, 0); 237 if (mAnimateChange) { 238 startScrimAnimation(scrim, color); 239 } else { 240 scrim.setScrimColor(color); 241 } 242 } 243 244 private void startScrimAnimation(final ScrimView scrim, int targetColor) { 245 int current = Color.alpha(scrim.getScrimColor()); 246 int target = Color.alpha(targetColor); 247 if (current == targetColor) { 248 return; 249 } 250 Object runningAnim = scrim.getTag(TAG_KEY_ANIM); 251 if (runningAnim instanceof ValueAnimator) { 252 ((ValueAnimator) runningAnim).cancel(); 253 } 254 ValueAnimator anim = ValueAnimator.ofInt(current, target); 255 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 256 @Override 257 public void onAnimationUpdate(ValueAnimator animation) { 258 int value = (int) animation.getAnimatedValue(); 259 scrim.setScrimColor(Color.argb(value, 0, 0, 0)); 260 } 261 }); 262 anim.setInterpolator(mAnimateKeyguardFadingOut 263 ? mLinearOutSlowInInterpolator 264 : mInterpolator); 265 anim.setStartDelay(mAnimationDelay); 266 anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); 267 anim.addListener(new AnimatorListenerAdapter() { 268 269 @Override 270 public void onAnimationEnd(Animator animation) { 271 if (mOnAnimationFinished != null) { 272 mOnAnimationFinished.run(); 273 mOnAnimationFinished = null; 274 } 275 scrim.setTag(TAG_KEY_ANIM, null); 276 } 277 }); 278 anim.start(); 279 scrim.setTag(TAG_KEY_ANIM, anim); 280 mAnimationStarted = true; 281 } 282 283 @Override 284 public boolean onPreDraw() { 285 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); 286 mUpdatePending = false; 287 updateScrims(); 288 mAnimateKeyguardFadingOut = false; 289 mDurationOverride = -1; 290 mAnimationDelay = 0; 291 292 // Make sure that we always call the listener even if we didn't start an animation. 293 if (!mAnimationStarted && mOnAnimationFinished != null) { 294 mOnAnimationFinished.run(); 295 mOnAnimationFinished = null; 296 } 297 mAnimationStarted = false; 298 return true; 299 } 300 301 private final Runnable mPulseIn = new Runnable() { 302 @Override 303 public void run() { 304 if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing); 305 if (!mDozing) return; 306 DozeLog.tracePulseStart(); 307 mDurationOverride = mDozeParameters.getPulseInDuration(); 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, mDozeParameters.getPulseVisibleDuration()); 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 = mDozeParameters.getPulseOutDuration(); 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"); 341 DozeLog.tracePulseFinish(); 342 mPulseEndTime = 0; 343 } 344 }; 345 346 public void setBackDropView(BackDropView backDropView) { 347 mBackDropView = backDropView; 348 mBackDropView.setOnVisibilityChangedRunnable(new Runnable() { 349 @Override 350 public void run() { 351 updateScrimBehindDrawingMode(); 352 } 353 }); 354 updateScrimBehindDrawingMode(); 355 } 356 357 private void updateScrimBehindDrawingMode() { 358 boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE; 359 mScrimBehind.setDrawAsSrc(asSrc); 360 } 361} 362