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.view.View;
25import android.view.ViewTreeObserver;
26import android.view.animation.AnimationUtils;
27import android.view.animation.DecelerateInterpolator;
28import android.view.animation.Interpolator;
29
30import com.android.systemui.R;
31import com.android.systemui.statusbar.BackDropView;
32import com.android.systemui.statusbar.ScrimView;
33
34/**
35 * Controls both the scrim behind the notifications and in front of the notifications (when a
36 * security method gets shown).
37 */
38public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
39    public static final long ANIMATION_DURATION = 220;
40
41    private static final float SCRIM_BEHIND_ALPHA = 0.62f;
42    private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.55f;
43    private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
44    private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
45    private static final int TAG_KEY_ANIM = R.id.scrim;
46
47    private final ScrimView mScrimBehind;
48    private final ScrimView mScrimInFront;
49    private final UnlockMethodCache mUnlockMethodCache;
50
51    private boolean mKeyguardShowing;
52    private float mFraction;
53
54    private boolean mDarkenWhileDragging;
55    private boolean mBouncerShowing;
56    private boolean mAnimateChange;
57    private boolean mUpdatePending;
58    private boolean mExpanding;
59    private boolean mAnimateKeyguardFadingOut;
60    private long mDurationOverride = -1;
61    private long mAnimationDelay;
62    private Runnable mOnAnimationFinished;
63    private boolean mAnimationStarted;
64    private final Interpolator mInterpolator = new DecelerateInterpolator();
65    private final Interpolator mLinearOutSlowInInterpolator;
66    private BackDropView mBackDropView;
67    private boolean mScrimSrcEnabled;
68    private boolean mDozing;
69    private float mDozeInFrontAlpha;
70    private float mDozeBehindAlpha;
71    private float mCurrentInFrontAlpha;
72    private float mCurrentBehindAlpha;
73
74    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) {
75        mScrimBehind = scrimBehind;
76        mScrimInFront = scrimInFront;
77        final Context context = scrimBehind.getContext();
78        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
79        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
80                android.R.interpolator.linear_out_slow_in);
81        mScrimSrcEnabled = scrimSrcEnabled;
82    }
83
84    public void setKeyguardShowing(boolean showing) {
85        mKeyguardShowing = showing;
86        scheduleUpdate();
87    }
88
89    public void onTrackingStarted() {
90        mExpanding = true;
91        mDarkenWhileDragging = !mUnlockMethodCache.isCurrentlyInsecure();
92    }
93
94    public void onExpandingFinished() {
95        mExpanding = false;
96    }
97
98    public void setPanelExpansion(float fraction) {
99        if (mFraction != fraction) {
100            mFraction = fraction;
101            scheduleUpdate();
102        }
103    }
104
105    public void setBouncerShowing(boolean showing) {
106        mBouncerShowing = showing;
107        mAnimateChange = !mExpanding;
108        scheduleUpdate();
109    }
110
111    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) {
112        mAnimateKeyguardFadingOut = true;
113        mDurationOverride = duration;
114        mAnimationDelay = delay;
115        mAnimateChange = true;
116        mOnAnimationFinished = onAnimationFinished;
117        scheduleUpdate();
118    }
119
120    public void animateGoingToFullShade(long delay, long duration) {
121        mDurationOverride = duration;
122        mAnimationDelay = delay;
123        mAnimateChange = true;
124        scheduleUpdate();
125    }
126
127    public void setDozing(boolean dozing) {
128        mDozing = dozing;
129        scheduleUpdate();
130    }
131
132    public void setDozeInFrontAlpha(float alpha) {
133        mDozeInFrontAlpha = alpha;
134        updateScrimColor(mScrimInFront);
135    }
136
137    public void setDozeBehindAlpha(float alpha) {
138        mDozeBehindAlpha = alpha;
139        updateScrimColor(mScrimBehind);
140    }
141
142    public float getDozeBehindAlpha() {
143        return mDozeBehindAlpha;
144    }
145
146    public float getDozeInFrontAlpha() {
147        return mDozeInFrontAlpha;
148    }
149
150    private void scheduleUpdate() {
151        if (mUpdatePending) return;
152
153        // Make sure that a frame gets scheduled.
154        mScrimBehind.invalidate();
155        mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
156        mUpdatePending = true;
157    }
158
159    private void updateScrims() {
160        if (mAnimateKeyguardFadingOut) {
161            setScrimInFrontColor(0f);
162            setScrimBehindColor(0f);
163        } else if (!mKeyguardShowing && !mBouncerShowing) {
164            updateScrimNormal();
165            setScrimInFrontColor(0);
166        } else {
167            updateScrimKeyguard();
168        }
169        mAnimateChange = false;
170    }
171
172    private void updateScrimKeyguard() {
173        if (mExpanding && mDarkenWhileDragging) {
174            float behindFraction = Math.max(0, Math.min(mFraction, 1));
175            float fraction = 1 - behindFraction;
176            fraction = (float) Math.pow(fraction, 0.8f);
177            behindFraction = (float) Math.pow(behindFraction, 0.8f);
178            setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
179            setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD);
180        } else if (mBouncerShowing) {
181            setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
182            setScrimBehindColor(0f);
183        } else {
184            float fraction = Math.max(0, Math.min(mFraction, 1));
185            setScrimInFrontColor(0f);
186            setScrimBehindColor(fraction
187                    * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING)
188                    + SCRIM_BEHIND_ALPHA_UNLOCKING);
189        }
190    }
191
192    private void updateScrimNormal() {
193        float frac = mFraction;
194        // let's start this 20% of the way down the screen
195        frac = frac * 1.2f - 0.2f;
196        if (frac <= 0) {
197            setScrimBehindColor(0);
198        } else {
199            // woo, special effects
200            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
201            setScrimBehindColor(k * SCRIM_BEHIND_ALPHA);
202        }
203    }
204
205    private void setScrimBehindColor(float alpha) {
206        setScrimColor(mScrimBehind, alpha);
207    }
208
209    private void setScrimInFrontColor(float alpha) {
210        setScrimColor(mScrimInFront, alpha);
211        if (alpha == 0f) {
212            mScrimInFront.setClickable(false);
213        } else {
214
215            // Eat touch events (unless dozing).
216            mScrimInFront.setClickable(!mDozing);
217        }
218    }
219
220    private void setScrimColor(ScrimView scrim, float alpha) {
221        Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
222        if (runningAnim instanceof ValueAnimator) {
223            ((ValueAnimator) runningAnim).cancel();
224            scrim.setTag(TAG_KEY_ANIM, null);
225        }
226        if (mAnimateChange) {
227            startScrimAnimation(scrim, alpha);
228        } else {
229            setCurrentScrimAlpha(scrim, alpha);
230            updateScrimColor(scrim);
231        }
232    }
233
234    private float getDozeAlpha(View scrim) {
235        return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
236    }
237
238    private float getCurrentScrimAlpha(View scrim) {
239        return scrim == mScrimBehind ? mCurrentBehindAlpha : mCurrentInFrontAlpha;
240    }
241
242    private void setCurrentScrimAlpha(View scrim, float alpha) {
243        if (scrim == mScrimBehind) {
244            mCurrentBehindAlpha = alpha;
245        } else {
246            mCurrentInFrontAlpha = alpha;
247        }
248    }
249
250    private void updateScrimColor(ScrimView scrim) {
251        float alpha1 = getCurrentScrimAlpha(scrim);
252        float alpha2 = getDozeAlpha(scrim);
253        float alpha = 1 - (1 - alpha1) * (1 - alpha2);
254        scrim.setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
255    }
256
257    private void startScrimAnimation(final ScrimView scrim, float target) {
258        float current = getCurrentScrimAlpha(scrim);
259        ValueAnimator anim = ValueAnimator.ofFloat(current, target);
260        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
261            @Override
262            public void onAnimationUpdate(ValueAnimator animation) {
263                float alpha = (float) animation.getAnimatedValue();
264                setCurrentScrimAlpha(scrim, alpha);
265                updateScrimColor(scrim);
266            }
267        });
268        anim.setInterpolator(getInterpolator());
269        anim.setStartDelay(mAnimationDelay);
270        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
271        anim.addListener(new AnimatorListenerAdapter() {
272            @Override
273            public void onAnimationEnd(Animator animation) {
274                if (mOnAnimationFinished != null) {
275                    mOnAnimationFinished.run();
276                    mOnAnimationFinished = null;
277                }
278                scrim.setTag(TAG_KEY_ANIM, null);
279            }
280        });
281        anim.start();
282        scrim.setTag(TAG_KEY_ANIM, anim);
283        mAnimationStarted = true;
284    }
285
286    private Interpolator getInterpolator() {
287        return mAnimateKeyguardFadingOut ? mLinearOutSlowInInterpolator : mInterpolator;
288    }
289
290    @Override
291    public boolean onPreDraw() {
292        mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
293        mUpdatePending = false;
294        updateScrims();
295        mAnimateKeyguardFadingOut = false;
296        mDurationOverride = -1;
297        mAnimationDelay = 0;
298
299        // Make sure that we always call the listener even if we didn't start an animation.
300        if (!mAnimationStarted && mOnAnimationFinished != null) {
301            mOnAnimationFinished.run();
302            mOnAnimationFinished = null;
303        }
304        mAnimationStarted = false;
305        return true;
306    }
307
308    public void setBackDropView(BackDropView backDropView) {
309        mBackDropView = backDropView;
310        mBackDropView.setOnVisibilityChangedRunnable(new Runnable() {
311            @Override
312            public void run() {
313                updateScrimBehindDrawingMode();
314            }
315        });
316        updateScrimBehindDrawingMode();
317    }
318
319    private void updateScrimBehindDrawingMode() {
320        boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled;
321        mScrimBehind.setDrawAsSrc(asSrc);
322    }
323}
324