2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar.phone;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.PropertyValuesHolder;
22 import android.animation.ValueAnimator;
23 import android.content.Context;
24 import android.graphics.Color;
25 import android.graphics.Rect;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.ViewTreeObserver;
29 import android.view.animation.DecelerateInterpolator;
30 import android.view.animation.Interpolator;
31 import android.view.animation.PathInterpolator;
33 import com.android.systemui.R;
34 import com.android.systemui.statusbar.ExpandableNotificationRow;
35 import com.android.systemui.statusbar.NotificationData;
36 import com.android.systemui.statusbar.ScrimView;
37 import com.android.systemui.statusbar.policy.HeadsUpManager;
38 import com.android.systemui.statusbar.stack.StackStateAnimator;
41 * Controls both the scrim behind the notifications and in front of the notifications (when a
42 * security method gets shown).
44 public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
45 HeadsUpManager.OnHeadsUpChangedListener {
46 public static final long ANIMATION_DURATION = 220;
47 public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
48 = new PathInterpolator(0f, 0, 0.7f, 1f);
49 private static final float SCRIM_BEHIND_ALPHA = 0.62f;
50 private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
51 private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
52 private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
53 private static final int TAG_KEY_ANIM = R.id.scrim;
54 private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
55 private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
56 private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
58 protected final ScrimView mScrimBehind;
59 private final ScrimView mScrimInFront;
60 private final UnlockMethodCache mUnlockMethodCache;
61 private final View mHeadsUpScrim;
63 private float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
64 private float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
65 private float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
67 protected boolean mKeyguardShowing;
68 private float mFraction;
70 private boolean mDarkenWhileDragging;
71 protected boolean mBouncerShowing;
72 private boolean mWakeAndUnlocking;
73 private boolean mAnimateChange;
74 private boolean mUpdatePending;
75 private boolean mExpanding;
76 private boolean mAnimateKeyguardFadingOut;
77 private long mDurationOverride = -1;
78 private long mAnimationDelay;
79 private Runnable mOnAnimationFinished;
80 private final Interpolator mInterpolator = new DecelerateInterpolator();
81 private boolean mDozing;
82 private float mDozeInFrontAlpha;
83 private float mDozeBehindAlpha;
84 private float mCurrentInFrontAlpha;
85 private float mCurrentBehindAlpha;
86 private float mCurrentHeadsUpAlpha = 1;
87 private int mPinnedHeadsUpCount;
88 private float mTopHeadsUpDragAmount;
89 private View mDraggedHeadsUpView;
90 private boolean mForceHideScrims;
91 private boolean mSkipFirstFrame;
92 private boolean mDontAnimateBouncerChanges;
93 private boolean mKeyguardFadingOutInProgress;
94 private ValueAnimator mKeyguardFadeoutAnimation;
96 public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim) {
97 mScrimBehind = scrimBehind;
98 mScrimInFront = scrimInFront;
99 mHeadsUpScrim = headsUpScrim;
100 final Context context = scrimBehind.getContext();
101 mUnlockMethodCache = UnlockMethodCache.getInstance(context);
102 updateHeadsUpScrim(false);
105 public void setKeyguardShowing(boolean showing) {
106 mKeyguardShowing = showing;
110 public void setShowScrimBehind(boolean show) {
112 mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
113 mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
114 mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
116 mScrimBehindAlpha = 0;
117 mScrimBehindAlphaKeyguard = 0;
118 mScrimBehindAlphaUnlocking = 0;
123 public void onTrackingStarted() {
125 mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
128 public void onExpandingFinished() {
132 public void setPanelExpansion(float fraction) {
133 if (mFraction != fraction) {
134 mFraction = fraction;
136 if (mPinnedHeadsUpCount != 0) {
137 updateHeadsUpScrim(false);
139 if (mKeyguardFadeoutAnimation != null) {
140 mKeyguardFadeoutAnimation.cancel();
145 public void setBouncerShowing(boolean showing) {
146 mBouncerShowing = showing;
147 mAnimateChange = !mExpanding && !mDontAnimateBouncerChanges;
151 public void setWakeAndUnlocking() {
152 mWakeAndUnlocking = true;
156 public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
157 boolean skipFirstFrame) {
158 mWakeAndUnlocking = false;
159 mAnimateKeyguardFadingOut = true;
160 mDurationOverride = duration;
161 mAnimationDelay = delay;
162 mAnimateChange = true;
163 mSkipFirstFrame = skipFirstFrame;
164 mOnAnimationFinished = onAnimationFinished;
167 // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
168 // the changes we just scheduled.
172 public void abortKeyguardFadingOut() {
173 if (mAnimateKeyguardFadingOut) {
174 endAnimateKeyguardFadingOut(true /* force */);
178 public void animateGoingToFullShade(long delay, long duration) {
179 mDurationOverride = duration;
180 mAnimationDelay = delay;
181 mAnimateChange = true;
185 public void animateNextChange() {
186 mAnimateChange = true;
189 public void setDozing(boolean dozing) {
190 if (mDozing != dozing) {
196 public void setDozeInFrontAlpha(float alpha) {
197 mDozeInFrontAlpha = alpha;
198 updateScrimColor(mScrimInFront);
201 public void setDozeBehindAlpha(float alpha) {
202 mDozeBehindAlpha = alpha;
203 updateScrimColor(mScrimBehind);
206 public float getDozeBehindAlpha() {
207 return mDozeBehindAlpha;
210 public float getDozeInFrontAlpha() {
211 return mDozeInFrontAlpha;
214 private void scheduleUpdate() {
215 if (mUpdatePending) return;
217 // Make sure that a frame gets scheduled.
218 mScrimBehind.invalidate();
219 mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
220 mUpdatePending = true;
223 protected void updateScrims() {
224 if (mAnimateKeyguardFadingOut || mForceHideScrims) {
225 setScrimInFrontColor(0f);
226 setScrimBehindColor(0f);
227 } else if (mWakeAndUnlocking) {
229 // During wake and unlock, we first hide everything behind a black scrim, which then
230 // gets faded out from animateKeyguardFadingOut.
232 setScrimInFrontColor(0f);
233 setScrimBehindColor(1f);
235 setScrimInFrontColor(1f);
236 setScrimBehindColor(0f);
238 } else if (!mKeyguardShowing && !mBouncerShowing) {
240 setScrimInFrontColor(0);
242 updateScrimKeyguard();
244 mAnimateChange = false;
247 private void updateScrimKeyguard() {
248 if (mExpanding && mDarkenWhileDragging) {
249 float behindFraction = Math.max(0, Math.min(mFraction, 1));
250 float fraction = 1 - behindFraction;
251 fraction = (float) Math.pow(fraction, 0.8f);
252 behindFraction = (float) Math.pow(behindFraction, 0.8f);
253 setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
254 setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
255 } else if (mBouncerShowing) {
256 setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
257 setScrimBehindColor(0f);
259 float fraction = Math.max(0, Math.min(mFraction, 1));
260 setScrimInFrontColor(0f);
261 setScrimBehindColor(fraction
262 * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
263 + mScrimBehindAlphaUnlocking);
267 private void updateScrimNormal() {
268 float frac = mFraction;
269 // let's start this 20% of the way down the screen
270 frac = frac * 1.2f - 0.2f;
272 setScrimBehindColor(0);
274 // woo, special effects
275 final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
276 setScrimBehindColor(k * mScrimBehindAlpha);
280 private void setScrimBehindColor(float alpha) {
281 setScrimColor(mScrimBehind, alpha);
284 private void setScrimInFrontColor(float alpha) {
285 setScrimColor(mScrimInFront, alpha);
287 mScrimInFront.setClickable(false);
290 // Eat touch events (unless dozing).
291 mScrimInFront.setClickable(!mDozing);
295 private void setScrimColor(View scrim, float alpha) {
296 updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
299 private float getDozeAlpha(View scrim) {
300 return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
303 private float getCurrentScrimAlpha(View scrim) {
304 return scrim == mScrimBehind ? mCurrentBehindAlpha
305 : scrim == mScrimInFront ? mCurrentInFrontAlpha
306 : mCurrentHeadsUpAlpha;
309 private void setCurrentScrimAlpha(View scrim, float alpha) {
310 if (scrim == mScrimBehind) {
311 mCurrentBehindAlpha = alpha;
312 } else if (scrim == mScrimInFront) {
313 mCurrentInFrontAlpha = alpha;
315 alpha = Math.max(0.0f, Math.min(1.0f, alpha));
316 mCurrentHeadsUpAlpha = alpha;
320 private void updateScrimColor(View scrim) {
321 float alpha1 = getCurrentScrimAlpha(scrim);
322 if (scrim instanceof ScrimView) {
323 float alpha2 = getDozeAlpha(scrim);
324 float alpha = 1 - (1 - alpha1) * (1 - alpha2);
325 alpha = Math.max(0, Math.min(1.0f, alpha));
326 ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
328 scrim.setAlpha(alpha1);
332 private void startScrimAnimation(final View scrim, float target) {
333 float current = getCurrentScrimAlpha(scrim);
334 ValueAnimator anim = ValueAnimator.ofFloat(current, target);
335 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
337 public void onAnimationUpdate(ValueAnimator animation) {
338 float alpha = (float) animation.getAnimatedValue();
339 setCurrentScrimAlpha(scrim, alpha);
340 updateScrimColor(scrim);
343 anim.setInterpolator(getInterpolator());
344 anim.setStartDelay(mAnimationDelay);
345 anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
346 anim.addListener(new AnimatorListenerAdapter() {
348 public void onAnimationEnd(Animator animation) {
349 if (mOnAnimationFinished != null) {
350 mOnAnimationFinished.run();
351 mOnAnimationFinished = null;
353 if (mKeyguardFadingOutInProgress) {
354 mKeyguardFadeoutAnimation = null;
355 mKeyguardFadingOutInProgress = false;
357 scrim.setTag(TAG_KEY_ANIM, null);
358 scrim.setTag(TAG_KEY_ANIM_TARGET, null);
362 if (mAnimateKeyguardFadingOut) {
363 mKeyguardFadingOutInProgress = true;
364 mKeyguardFadeoutAnimation = anim;
366 if (mSkipFirstFrame) {
367 anim.setCurrentPlayTime(16);
369 scrim.setTag(TAG_KEY_ANIM, anim);
370 scrim.setTag(TAG_KEY_ANIM_TARGET, target);
373 private Interpolator getInterpolator() {
374 return mAnimateKeyguardFadingOut ? KEYGUARD_FADE_OUT_INTERPOLATOR : mInterpolator;
378 public boolean onPreDraw() {
379 mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
380 mUpdatePending = false;
381 if (mDontAnimateBouncerChanges) {
382 mDontAnimateBouncerChanges = false;
385 mDurationOverride = -1;
387 mSkipFirstFrame = false;
389 // Make sure that we always call the listener even if we didn't start an animation.
390 endAnimateKeyguardFadingOut(false /* force */);
394 private void endAnimateKeyguardFadingOut(boolean force) {
395 mAnimateKeyguardFadingOut = false;
396 if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
397 if (mOnAnimationFinished != null) {
398 mOnAnimationFinished.run();
399 mOnAnimationFinished = null;
401 mKeyguardFadingOutInProgress = false;
405 private boolean isAnimating(View scrim) {
406 return scrim.getTag(TAG_KEY_ANIM) != null;
409 public void setDrawBehindAsSrc(boolean asSrc) {
410 mScrimBehind.setDrawAsSrc(asSrc);
414 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
418 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
419 mPinnedHeadsUpCount++;
420 updateHeadsUpScrim(true);
424 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
425 mPinnedHeadsUpCount--;
426 if (headsUp == mDraggedHeadsUpView) {
427 mDraggedHeadsUpView = null;
428 mTopHeadsUpDragAmount = 0.0f;
430 updateHeadsUpScrim(true);
434 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
437 private void updateHeadsUpScrim(boolean animate) {
438 updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
441 private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
442 if (mKeyguardFadingOutInProgress) {
446 ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim,
448 float animEndValue = -1;
449 if (previousAnimator != null) {
450 if (animate || alpha == currentAlpha) {
451 previousAnimator.cancel();
453 animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA);
456 if (alpha != currentAlpha && alpha != animEndValue) {
458 startScrimAnimation(scrim, alpha);
459 scrim.setTag(TAG_START_ALPHA, currentAlpha);
460 scrim.setTag(TAG_END_ALPHA, alpha);
462 if (previousAnimator != null) {
463 float previousStartValue = StackStateAnimator.getChildTag(scrim,
465 float previousEndValue = StackStateAnimator.getChildTag(scrim,
467 // we need to increase all animation keyframes of the previous animator by the
468 // relative change to the end value
469 PropertyValuesHolder[] values = previousAnimator.getValues();
470 float relativeDiff = alpha - previousEndValue;
471 float newStartValue = previousStartValue + relativeDiff;
472 newStartValue = Math.max(0, Math.min(1.0f, newStartValue));
473 values[0].setFloatValues(newStartValue, alpha);
474 scrim.setTag(TAG_START_ALPHA, newStartValue);
475 scrim.setTag(TAG_END_ALPHA, alpha);
476 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
478 // update the alpha directly
479 setCurrentScrimAlpha(scrim, alpha);
480 updateScrimColor(scrim);
487 * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
488 * the heads up is in its resting space and 1 means it's fully dragged out.
490 * @param draggedHeadsUpView the dragged view
491 * @param topHeadsUpDragAmount how far is it dragged
493 public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) {
494 mTopHeadsUpDragAmount = topHeadsUpDragAmount;
495 mDraggedHeadsUpView = draggedHeadsUpView;
496 updateHeadsUpScrim(false);
499 private float calculateHeadsUpAlpha() {
501 if (mPinnedHeadsUpCount >= 2) {
503 } else if (mPinnedHeadsUpCount == 0) {
506 alpha = 1.0f - mTopHeadsUpDragAmount;
508 float expandFactor = (1.0f - mFraction);
509 expandFactor = Math.max(expandFactor, 0.0f);
510 return alpha * expandFactor;
513 public void forceHideScrims(boolean hide) {
514 mForceHideScrims = hide;
515 mAnimateChange = false;
519 public void dontAnimateBouncerChangesUntilNextFrame() {
520 mDontAnimateBouncerChanges = true;
523 public void setExcludedBackgroundArea(Rect area) {
524 mScrimBehind.setExcludedArea(area);
527 public int getScrimBehindColor() {
528 return mScrimBehind.getScrimColorWithAlpha();
531 public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
532 mScrimBehind.setChangeRunnable(changeRunnable);
535 public void onDensityOrFontScaleChanged() {
536 ViewGroup.LayoutParams layoutParams = mHeadsUpScrim.getLayoutParams();
537 layoutParams.height = mHeadsUpScrim.getResources().getDimensionPixelSize(
538 R.dimen.heads_up_scrim_height);
539 mHeadsUpScrim.setLayoutParams(layoutParams);