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 17 package com.android.systemui.statusbar.phone; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.annotation.NonNull; 23 import android.content.Context; 24 import android.os.Handler; 25 import android.util.Log; 26 import android.view.View; 27 import android.view.animation.Interpolator; 28 29 import com.android.keyguard.KeyguardStatusView; 30 import com.android.systemui.Interpolators; 31 import com.android.systemui.doze.DozeHost; 32 import com.android.systemui.doze.DozeLog; 33 import com.android.systemui.doze.DozeTriggers; 34 35 /** 36 * Controller which handles all the doze animations of the scrims. 37 */ 38 public class DozeScrimController { 39 private static final String TAG = "DozeScrimController"; 40 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 41 42 private final DozeParameters mDozeParameters; 43 private final Handler mHandler = new Handler(); 44 private final ScrimController mScrimController; 45 46 private final Context mContext; 47 48 private boolean mDozing; 49 private DozeHost.PulseCallback mPulseCallback; 50 private int mPulseReason; 51 private Animator mInFrontAnimator; 52 private Animator mBehindAnimator; 53 private float mInFrontTarget; 54 private float mBehindTarget; 55 private boolean mDozingAborted; 56 private boolean mWakeAndUnlocking; 57 58 public DozeScrimController(ScrimController scrimController, Context context) { 59 mContext = context; 60 mScrimController = scrimController; 61 mDozeParameters = new DozeParameters(context); 62 } 63 64 public void setDozing(boolean dozing, boolean animate) { 65 if (mDozing == dozing) return; 66 mDozing = dozing; 67 mWakeAndUnlocking = false; 68 if (mDozing) { 69 mDozingAborted = false; 70 abortAnimations(); 71 mScrimController.setDozeBehindAlpha(1f); 72 mScrimController.setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? 0f : 1f); 73 } else { 74 cancelPulsing(); 75 if (animate) { 76 startScrimAnimation(false /* inFront */, 0f /* target */, 77 NotificationPanelView.DOZE_ANIMATION_DURATION, 78 Interpolators.LINEAR_OUT_SLOW_IN); 79 startScrimAnimation(true /* inFront */, 0f /* target */, 80 NotificationPanelView.DOZE_ANIMATION_DURATION, 81 Interpolators.LINEAR_OUT_SLOW_IN); 82 } else { 83 abortAnimations(); 84 mScrimController.setDozeBehindAlpha(0f); 85 mScrimController.setDozeInFrontAlpha(0f); 86 } 87 } 88 } 89 90 public void setWakeAndUnlocking() { 91 // Immediately abort the doze scrims in case of wake-and-unlock 92 // for pulsing so the Keyguard fade-out animation scrim can take over. 93 if (!mWakeAndUnlocking) { 94 mWakeAndUnlocking = true; 95 mScrimController.setDozeBehindAlpha(0f); 96 mScrimController.setDozeInFrontAlpha(0f); 97 } 98 } 99 100 /** When dozing, fade screen contents in and out using the front scrim. */ 101 public void pulse(@NonNull DozeHost.PulseCallback callback, int reason) { 102 if (callback == null) { 103 throw new IllegalArgumentException("callback must not be null"); 104 } 105 106 if (!mDozing || mPulseCallback != null) { 107 // Pulse suppressed. 108 callback.onPulseFinished(); 109 return; 110 } 111 112 // Begin pulse. Note that it's very important that the pulse finished callback 113 // be invoked when we're done so that the caller can drop the pulse wakelock. 114 mPulseCallback = callback; 115 mPulseReason = reason; 116 mHandler.post(mPulseIn); 117 } 118 119 /** 120 * Aborts pulsing immediately. 121 */ 122 public void abortPulsing() { 123 cancelPulsing(); 124 if (mDozing && !mWakeAndUnlocking) { 125 mScrimController.setDozeBehindAlpha(1f); 126 mScrimController.setDozeInFrontAlpha( 127 mDozeParameters.getAlwaysOn() && !mDozingAborted ? 0f : 1f); 128 } 129 } 130 131 /** 132 * Aborts dozing immediately. 133 */ 134 public void abortDoze() { 135 mDozingAborted = true; 136 abortPulsing(); 137 } 138 139 public void onScreenTurnedOn() { 140 if (isPulsing()) { 141 final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP 142 || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; 143 startScrimAnimation(true /* inFront */, 0f, 144 mDozeParameters.getPulseInDuration(pickupOrDoubleTap), 145 pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT, 146 mPulseInFinished); 147 } 148 } 149 150 public boolean isPulsing() { 151 return mPulseCallback != null; 152 } 153 154 public boolean isDozing() { 155 return mDozing; 156 } 157 158 public void extendPulse() { 159 mHandler.removeCallbacks(mPulseOut); 160 } 161 162 private void cancelPulsing() { 163 if (DEBUG) Log.d(TAG, "Cancel pulsing"); 164 165 if (mPulseCallback != null) { 166 mHandler.removeCallbacks(mPulseIn); 167 mHandler.removeCallbacks(mPulseOut); 168 mHandler.removeCallbacks(mPulseOutExtended); 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 abortAnimations() { 187 if (mInFrontAnimator != null) { 188 mInFrontAnimator.cancel(); 189 } 190 if (mBehindAnimator != null) { 191 mBehindAnimator.cancel(); 192 } 193 } 194 195 private void startScrimAnimation(final boolean inFront, float target, long duration, 196 Interpolator interpolator) { 197 startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */); 198 } 199 200 private void startScrimAnimation(final boolean inFront, float target, long duration, 201 Interpolator interpolator, final Runnable endRunnable) { 202 Animator current = getCurrentAnimator(inFront); 203 if (current != null) { 204 float currentTarget = getCurrentTarget(inFront); 205 if (currentTarget == target) { 206 return; 207 } 208 current.cancel(); 209 } 210 ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target); 211 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 212 @Override 213 public void onAnimationUpdate(ValueAnimator animation) { 214 float value = (float) animation.getAnimatedValue(); 215 setDozeAlpha(inFront, value); 216 } 217 }); 218 anim.setInterpolator(interpolator); 219 anim.setDuration(duration); 220 anim.addListener(new AnimatorListenerAdapter() { 221 @Override 222 public void onAnimationEnd(Animator animation) { 223 setCurrentAnimator(inFront, null); 224 if (endRunnable != null) { 225 endRunnable.run(); 226 } 227 } 228 }); 229 anim.start(); 230 setCurrentAnimator(inFront, anim); 231 setCurrentTarget(inFront, target); 232 } 233 234 private float getCurrentTarget(boolean inFront) { 235 return inFront ? mInFrontTarget : mBehindTarget; 236 } 237 238 private void setCurrentTarget(boolean inFront, float target) { 239 if (inFront) { 240 mInFrontTarget = target; 241 } else { 242 mBehindTarget = target; 243 } 244 } 245 246 private Animator getCurrentAnimator(boolean inFront) { 247 return inFront ? mInFrontAnimator : mBehindAnimator; 248 } 249 250 private void setCurrentAnimator(boolean inFront, Animator animator) { 251 if (inFront) { 252 mInFrontAnimator = animator; 253 } else { 254 mBehindAnimator = animator; 255 } 256 } 257 258 private void setDozeAlpha(boolean inFront, float alpha) { 259 if (mWakeAndUnlocking) { 260 return; 261 } 262 if (inFront) { 263 mScrimController.setDozeInFrontAlpha(alpha); 264 } else { 265 mScrimController.setDozeBehindAlpha(alpha); 266 } 267 } 268 269 private float getDozeAlpha(boolean inFront) { 270 return inFront 271 ? mScrimController.getDozeInFrontAlpha() 272 : mScrimController.getDozeBehindAlpha(); 273 } 274 275 private final Runnable mPulseIn = new Runnable() { 276 @Override 277 public void run() { 278 if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" 279 + DozeLog.pulseReasonToString(mPulseReason)); 280 if (!mDozing) return; 281 DozeLog.tracePulseStart(mPulseReason); 282 283 // Signal that the pulse is ready to turn the screen on and draw. 284 pulseStarted(); 285 286 if (mDozeParameters.getAlwaysOn()) { 287 mHandler.post(DozeScrimController.this::onScreenTurnedOn); 288 } 289 } 290 }; 291 292 private final Runnable mPulseInFinished = new Runnable() { 293 @Override 294 public void run() { 295 if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); 296 if (!mDozing) return; 297 mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); 298 mHandler.postDelayed(mPulseOutExtended, 299 mDozeParameters.getPulseVisibleDurationExtended()); 300 } 301 }; 302 303 private final Runnable mPulseOutExtended = new Runnable() { 304 @Override 305 public void run() { 306 mHandler.removeCallbacks(mPulseOut); 307 mPulseOut.run(); 308 } 309 }; 310 311 private final Runnable mPulseOut = new Runnable() { 312 @Override 313 public void run() { 314 mHandler.removeCallbacks(mPulseOutExtended); 315 if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing); 316 if (!mDozing) return; 317 startScrimAnimation(true /* inFront */, mDozeParameters.getAlwaysOn() ? 0 : 1, 318 mDozeParameters.getPulseOutDuration(), 319 Interpolators.ALPHA_IN, mPulseOutFinished); 320 } 321 }; 322 323 private final Runnable mPulseOutFinished = new Runnable() { 324 @Override 325 public void run() { 326 if (DEBUG) Log.d(TAG, "Pulse out finished"); 327 DozeLog.tracePulseFinish(); 328 329 // Signal that the pulse is all finished so we can turn the screen off now. 330 pulseFinished(); 331 } 332 }; 333 } 334