NotificationPanelView.java revision 1fdd65e204c65f149ca537786a771a3e4351c0ba
1/*
2 * Copyright (C) 2012 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.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.ValueAnimator;
24import android.content.Context;
25import android.content.res.Configuration;
26import android.graphics.Color;
27import android.graphics.drawable.ColorDrawable;
28import android.util.AttributeSet;
29import android.util.MathUtils;
30import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
33import android.view.ViewTreeObserver;
34import android.view.accessibility.AccessibilityEvent;
35import android.view.animation.AnimationUtils;
36import android.view.animation.Interpolator;
37import android.widget.FrameLayout;
38import android.widget.TextView;
39
40import com.android.keyguard.KeyguardStatusView;
41import com.android.systemui.R;
42import com.android.systemui.qs.QSPanel;
43import com.android.systemui.statusbar.ExpandableView;
44import com.android.systemui.statusbar.FlingAnimationUtils;
45import com.android.systemui.statusbar.GestureRecorder;
46import com.android.systemui.statusbar.KeyguardAffordanceView;
47import com.android.systemui.statusbar.StatusBarState;
48import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
49import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
50import com.android.systemui.statusbar.stack.StackStateAnimator;
51
52public class NotificationPanelView extends PanelView implements
53        ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
54        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
55        KeyguardAffordanceHelper.Callback {
56
57    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
58    // changed.
59    private static final int CAP_HEIGHT = 1456;
60    private static final int FONT_HEIGHT = 2163;
61
62    private static final float HEADER_RUBBERBAND_FACTOR = 2.05f;
63    private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
64
65    private static final int DOZE_BACKGROUND_COLOR = 0xff000000;
66    private static final int TAG_KEY_ANIM = R.id.scrim;
67    private static final long DOZE_BACKGROUND_ANIM_DURATION = ScrimController.ANIMATION_DURATION;
68
69    private KeyguardAffordanceHelper mAfforanceHelper;
70    private StatusBarHeaderView mHeader;
71    private KeyguardUserSwitcher mKeyguardUserSwitcher;
72    private KeyguardStatusBarView mKeyguardStatusBar;
73    private View mQsContainer;
74    private QSPanel mQsPanel;
75    private KeyguardStatusView mKeyguardStatusView;
76    private ObservableScrollView mScrollView;
77    private TextView mClockView;
78    private View mReserveNotificationSpace;
79    private View mQsNavbarScrim;
80    private View mNotificationContainerParent;
81    private NotificationStackScrollLayout mNotificationStackScroller;
82    private int mNotificationTopPadding;
83    private boolean mAnimateNextTopPaddingChange;
84
85    private int mTrackingPointer;
86    private VelocityTracker mVelocityTracker;
87    private boolean mQsTracking;
88
89    /**
90     * Handles launching the secure camera properly even when other applications may be using the
91     * camera hardware.
92     */
93    private SecureCameraLaunchManager mSecureCameraLaunchManager;
94
95    /**
96     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
97     * the expansion for quick settings.
98     */
99    private boolean mConflictingQsExpansionGesture;
100
101    /**
102     * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
103     * intercepted yet.
104     */
105    private boolean mIntercepting;
106    private boolean mQsExpanded;
107    private boolean mQsExpandedWhenExpandingStarted;
108    private boolean mQsFullyExpanded;
109    private boolean mKeyguardShowing;
110    private boolean mDozing;
111    private int mStatusBarState;
112    private float mInitialHeightOnTouch;
113    private float mInitialTouchX;
114    private float mInitialTouchY;
115    private float mLastTouchX;
116    private float mLastTouchY;
117    private float mQsExpansionHeight;
118    private int mQsMinExpansionHeight;
119    private int mQsMaxExpansionHeight;
120    private int mQsPeekHeight;
121    private boolean mStackScrollerOverscrolling;
122    private boolean mQsExpansionFromOverscroll;
123    private float mLastOverscroll;
124    private boolean mQsExpansionEnabled = true;
125    private ValueAnimator mQsExpansionAnimator;
126    private FlingAnimationUtils mFlingAnimationUtils;
127    private int mStatusBarMinHeight;
128    private boolean mUnlockIconActive;
129    private int mNotificationsHeaderCollideDistance;
130    private int mUnlockMoveDistance;
131    private float mEmptyDragAmount;
132
133    private Interpolator mFastOutSlowInInterpolator;
134    private Interpolator mFastOutLinearInterpolator;
135    private ObjectAnimator mClockAnimator;
136    private int mClockAnimationTarget = -1;
137    private int mTopPaddingAdjustment;
138    private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
139            new KeyguardClockPositionAlgorithm();
140    private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
141            new KeyguardClockPositionAlgorithm.Result();
142    private boolean mIsExpanding;
143
144    private boolean mBlockTouches;
145    private int mNotificationScrimWaitDistance;
146    private boolean mTwoFingerQsExpand;
147    private boolean mTwoFingerQsExpandPossible;
148
149    /**
150     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
151     * need to take this into account in our panel height calculation.
152     */
153    private int mScrollYOverride = -1;
154    private boolean mQsAnimatorExpand;
155    private boolean mIsLaunchTransitionFinished;
156    private boolean mIsLaunchTransitionRunning;
157    private Runnable mLaunchAnimationEndRunnable;
158    private boolean mOnlyAffordanceInThisMotion;
159    private boolean mKeyguardStatusViewAnimating;
160    private boolean mHeaderAnimatingIn;
161    private ObjectAnimator mQsContainerAnimator;
162
163    private boolean mShadeEmpty;
164
165    private boolean mQsScrimEnabled = true;
166    private boolean mLastAnnouncementWasQuickSettings;
167    private boolean mQsTouchAboveFalsingThreshold;
168    private int mQsFalsingThreshold;
169
170    public NotificationPanelView(Context context, AttributeSet attrs) {
171        super(context, attrs);
172    }
173
174    public void setStatusBar(PhoneStatusBar bar) {
175        mStatusBar = bar;
176    }
177
178    @Override
179    protected void onFinishInflate() {
180        super.onFinishInflate();
181        mHeader = (StatusBarHeaderView) findViewById(R.id.header);
182        mHeader.setOnClickListener(this);
183        mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
184        mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
185        mQsContainer = findViewById(R.id.quick_settings_container);
186        mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
187        mClockView = (TextView) findViewById(R.id.clock_view);
188        mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
189        mScrollView.setListener(this);
190        mScrollView.setFocusable(false);
191        mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
192        mNotificationContainerParent = findViewById(R.id.notification_container_parent);
193        mNotificationStackScroller = (NotificationStackScrollLayout)
194                findViewById(R.id.notification_stack_scroller);
195        mNotificationStackScroller.setOnHeightChangedListener(this);
196        mNotificationStackScroller.setOverscrollTopChangedListener(this);
197        mNotificationStackScroller.setScrollView(mScrollView);
198        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
199                android.R.interpolator.fast_out_slow_in);
200        mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
201                android.R.interpolator.fast_out_linear_in);
202        mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
203        mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
204        mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
205        mSecureCameraLaunchManager =
206                new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
207
208        // recompute internal state when qspanel height changes
209        mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
210            @Override
211            public void onLayoutChange(View v, int left, int top, int right,
212                    int bottom, int oldLeft, int oldTop, int oldRight,
213                    int oldBottom) {
214                final int height = bottom - top;
215                final int oldHeight = oldBottom - oldTop;
216                if (height != oldHeight) {
217                    onScrollChanged();
218                }
219            }
220        });
221    }
222
223    @Override
224    protected void loadDimens() {
225        super.loadDimens();
226        mNotificationTopPadding = getResources().getDimensionPixelSize(
227                R.dimen.notifications_top_padding);
228        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
229        mStatusBarMinHeight = getResources().getDimensionPixelSize(
230                com.android.internal.R.dimen.status_bar_height);
231        mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
232        mNotificationsHeaderCollideDistance =
233                getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
234        mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
235        mClockPositionAlgorithm.loadDimens(getResources());
236        mNotificationScrimWaitDistance =
237                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
238        mQsFalsingThreshold = getResources().getDimensionPixelSize(
239                R.dimen.qs_falsing_threshold);
240    }
241
242    public void updateResources() {
243        int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
244        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
245        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
246        if (lp.width != panelWidth) {
247            lp.width = panelWidth;
248            lp.gravity = panelGravity;
249            mHeader.setLayoutParams(lp);
250            mHeader.post(mUpdateHeader);
251        }
252
253        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
254        if (lp.width != panelWidth) {
255            lp.width = panelWidth;
256            lp.gravity = panelGravity;
257            mNotificationStackScroller.setLayoutParams(lp);
258        }
259
260        lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
261        if (lp.width != panelWidth) {
262            lp.width = panelWidth;
263            lp.gravity = panelGravity;
264            mScrollView.setLayoutParams(lp);
265        }
266    }
267
268    @Override
269    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
270        super.onLayout(changed, left, top, right, bottom);
271
272        // Update Clock Pivot
273        mKeyguardStatusView.setPivotX(getWidth() / 2);
274        mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
275
276        // Calculate quick setting heights.
277        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
278        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
279        positionClockAndNotifications();
280        if (mQsExpanded) {
281            if (mQsFullyExpanded) {
282                mQsExpansionHeight = mQsMaxExpansionHeight;
283                requestScrollerTopPaddingUpdate(false /* animate */);
284            }
285        } else {
286            setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
287            mNotificationStackScroller.setStackHeight(getExpandedHeight());
288            updateHeader();
289        }
290        mNotificationStackScroller.updateIsSmallScreen(
291                mHeader.getCollapsedHeight() + mQsPeekHeight);
292    }
293
294    @Override
295    public void onAttachedToWindow() {
296        mSecureCameraLaunchManager.create();
297    }
298
299    @Override
300    public void onDetachedFromWindow() {
301        mSecureCameraLaunchManager.destroy();
302    }
303
304    /**
305     * Positions the clock and notifications dynamically depending on how many notifications are
306     * showing.
307     */
308    private void positionClockAndNotifications() {
309        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
310        int stackScrollerPadding;
311        if (mStatusBarState != StatusBarState.KEYGUARD) {
312            int bottom = mHeader.getCollapsedHeight();
313            stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
314                    ? bottom + mQsPeekHeight + mNotificationTopPadding
315                    : mKeyguardStatusBar.getHeight() + mNotificationTopPadding;
316            mTopPaddingAdjustment = 0;
317        } else {
318            mClockPositionAlgorithm.setup(
319                    mStatusBar.getMaxKeyguardNotifications(),
320                    getMaxPanelHeight(),
321                    getExpandedHeight(),
322                    mNotificationStackScroller.getNotGoneChildCount(),
323                    getHeight(),
324                    mKeyguardStatusView.getHeight(),
325                    mEmptyDragAmount);
326            mClockPositionAlgorithm.run(mClockPositionResult);
327            if (animate || mClockAnimator != null) {
328                startClockAnimation(mClockPositionResult.clockY);
329            } else {
330                mKeyguardStatusView.setY(mClockPositionResult.clockY);
331            }
332            updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
333            stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
334            mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
335        }
336        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
337        requestScrollerTopPaddingUpdate(animate);
338    }
339
340    private void startClockAnimation(int y) {
341        if (mClockAnimationTarget == y) {
342            return;
343        }
344        mClockAnimationTarget = y;
345        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
346            @Override
347            public boolean onPreDraw() {
348                getViewTreeObserver().removeOnPreDrawListener(this);
349                if (mClockAnimator != null) {
350                    mClockAnimator.removeAllListeners();
351                    mClockAnimator.cancel();
352                }
353                mClockAnimator = ObjectAnimator
354                        .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
355                mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
356                mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
357                mClockAnimator.addListener(new AnimatorListenerAdapter() {
358                    @Override
359                    public void onAnimationEnd(Animator animation) {
360                        mClockAnimator = null;
361                        mClockAnimationTarget = -1;
362                    }
363                });
364                mClockAnimator.start();
365                return true;
366            }
367        });
368    }
369
370    private void updateClock(float alpha, float scale) {
371        if (!mKeyguardStatusViewAnimating) {
372            mKeyguardStatusView.setAlpha(alpha);
373        }
374        mKeyguardStatusView.setScaleX(scale);
375        mKeyguardStatusView.setScaleY(scale);
376    }
377
378    public void animateToFullShade(long delay) {
379        mAnimateNextTopPaddingChange = true;
380        mNotificationStackScroller.goToFullShade(delay);
381        requestLayout();
382    }
383
384    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
385        mQsExpansionEnabled = qsExpansionEnabled;
386        mHeader.setClickable(qsExpansionEnabled);
387    }
388
389    @Override
390    public void resetViews() {
391        mIsLaunchTransitionFinished = false;
392        mBlockTouches = false;
393        mUnlockIconActive = false;
394        mAfforanceHelper.reset(true);
395        closeQs();
396        mStatusBar.dismissPopups();
397        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
398                true /* cancelAnimators */);
399    }
400
401    public void closeQs() {
402        cancelAnimation();
403        setQsExpansion(mQsMinExpansionHeight);
404    }
405
406    public void animateCloseQs() {
407        if (mQsExpansionAnimator != null) {
408            if (!mQsAnimatorExpand) {
409                return;
410            }
411            float height = mQsExpansionHeight;
412            mQsExpansionAnimator.cancel();
413            setQsExpansion(height);
414        }
415        flingSettings(0 /* vel */, false);
416    }
417
418    public void openQs() {
419        cancelAnimation();
420        if (mQsExpansionEnabled) {
421            setQsExpansion(mQsMaxExpansionHeight);
422        }
423    }
424
425    @Override
426    public void fling(float vel, boolean expand) {
427        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
428        if (gr != null) {
429            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
430        }
431        super.fling(vel, expand);
432    }
433
434    @Override
435    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
436        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
437            event.getText().add(getKeyguardOrLockScreenString());
438            mLastAnnouncementWasQuickSettings = false;
439            return true;
440        }
441
442        return super.dispatchPopulateAccessibilityEvent(event);
443    }
444
445    @Override
446    public boolean onInterceptTouchEvent(MotionEvent event) {
447        if (mBlockTouches) {
448            return false;
449        }
450        resetDownStates(event);
451        int pointerIndex = event.findPointerIndex(mTrackingPointer);
452        if (pointerIndex < 0) {
453            pointerIndex = 0;
454            mTrackingPointer = event.getPointerId(pointerIndex);
455        }
456        final float x = event.getX(pointerIndex);
457        final float y = event.getY(pointerIndex);
458
459        switch (event.getActionMasked()) {
460            case MotionEvent.ACTION_DOWN:
461                mIntercepting = true;
462                mInitialTouchY = y;
463                mInitialTouchX = x;
464                initVelocityTracker();
465                trackMovement(event);
466                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
467                    getParent().requestDisallowInterceptTouchEvent(true);
468                }
469                if (mQsExpansionAnimator != null) {
470                    onQsExpansionStarted();
471                    mInitialHeightOnTouch = mQsExpansionHeight;
472                    mQsTracking = true;
473                    mIntercepting = false;
474                    mNotificationStackScroller.removeLongPressCallback();
475                }
476                break;
477            case MotionEvent.ACTION_POINTER_UP:
478                final int upPointer = event.getPointerId(event.getActionIndex());
479                if (mTrackingPointer == upPointer) {
480                    // gesture is ongoing, find a new pointer to track
481                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
482                    mTrackingPointer = event.getPointerId(newIndex);
483                    mInitialTouchX = event.getX(newIndex);
484                    mInitialTouchY = event.getY(newIndex);
485                }
486                break;
487
488            case MotionEvent.ACTION_MOVE:
489                final float h = y - mInitialTouchY;
490                trackMovement(event);
491                if (mQsTracking) {
492
493                    // Already tracking because onOverscrolled was called. We need to update here
494                    // so we don't stop for a frame until the next touch event gets handled in
495                    // onTouchEvent.
496                    setQsExpansion(h + mInitialHeightOnTouch);
497                    trackMovement(event);
498                    mIntercepting = false;
499                    return true;
500                }
501                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
502                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
503                    onQsExpansionStarted();
504                    mInitialHeightOnTouch = mQsExpansionHeight;
505                    mInitialTouchY = y;
506                    mInitialTouchX = x;
507                    mQsTracking = true;
508                    mIntercepting = false;
509                    mNotificationStackScroller.removeLongPressCallback();
510                    return true;
511                }
512                break;
513
514            case MotionEvent.ACTION_CANCEL:
515            case MotionEvent.ACTION_UP:
516                trackMovement(event);
517                if (mQsTracking) {
518                    flingQsWithCurrentVelocity();
519                    mQsTracking = false;
520                }
521                mIntercepting = false;
522                break;
523        }
524
525        // Allow closing the whole panel when in SHADE state.
526        if (mStatusBarState == StatusBarState.SHADE) {
527            return super.onInterceptTouchEvent(event);
528        } else {
529            return !mQsExpanded && super.onInterceptTouchEvent(event);
530        }
531    }
532
533    private void resetDownStates(MotionEvent event) {
534        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
535            mOnlyAffordanceInThisMotion = false;
536            mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
537        }
538    }
539
540    @Override
541    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
542
543        // Block request when interacting with the scroll view so we can still intercept the
544        // scrolling when QS is expanded.
545        if (mScrollView.isHandlingTouchEvent()) {
546            return;
547        }
548        super.requestDisallowInterceptTouchEvent(disallowIntercept);
549    }
550
551    private void flingQsWithCurrentVelocity() {
552        float vel = getCurrentVelocity();
553        flingSettings(vel, flingExpandsQs(vel));
554    }
555
556    private boolean flingExpandsQs(float vel) {
557        if (isBelowFalsingThreshold()) {
558            return false;
559        }
560        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
561            return getQsExpansionFraction() > 0.5f;
562        } else {
563            return vel > 0;
564        }
565    }
566
567    private boolean isBelowFalsingThreshold() {
568        return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD;
569    }
570
571    private float getQsExpansionFraction() {
572        return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
573                / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
574    }
575
576    @Override
577    public boolean onTouchEvent(MotionEvent event) {
578        if (mBlockTouches) {
579            return false;
580        }
581        resetDownStates(event);
582        if ((!mIsExpanding || mHintAnimationRunning)
583                && !mQsExpanded
584                && mStatusBar.getBarState() != StatusBarState.SHADE) {
585            mAfforanceHelper.onTouchEvent(event);
586        }
587        if (mOnlyAffordanceInThisMotion) {
588            return true;
589        }
590        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
591                && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
592                && mQsExpansionEnabled) {
593
594            // Down in the empty area while fully expanded - go to QS.
595            mQsTracking = true;
596            mConflictingQsExpansionGesture = true;
597            onQsExpansionStarted();
598            mInitialHeightOnTouch = mQsExpansionHeight;
599            mInitialTouchY = event.getX();
600            mInitialTouchX = event.getY();
601        }
602        if (mExpandedHeight != 0) {
603            handleQsDown(event);
604        }
605        if (!mTwoFingerQsExpand && mQsTracking) {
606            onQsTouch(event);
607            if (!mConflictingQsExpansionGesture) {
608                return true;
609            }
610        }
611        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
612                || event.getActionMasked() == MotionEvent.ACTION_UP) {
613            mConflictingQsExpansionGesture = false;
614        }
615        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
616                && mQsExpansionEnabled) {
617            mTwoFingerQsExpandPossible = true;
618        }
619        if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
620                && event.getPointerCount() == 2
621                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
622            mTwoFingerQsExpand = true;
623            requestPanelHeightUpdate();
624
625            // Normally, we start listening when the panel is expanded, but here we need to start
626            // earlier so the state is already up to date when dragging down.
627            setListening(true);
628        }
629        super.onTouchEvent(event);
630        return true;
631    }
632
633    private boolean isInQsArea(float x, float y) {
634        return mStatusBarState != StatusBarState.SHADE ||
635                (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
636                        (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
637                                || y <= mQsContainer.getY() + mQsContainer.getHeight());
638    }
639
640    private void handleQsDown(MotionEvent event) {
641        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
642                && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
643            mQsTracking = true;
644            onQsExpansionStarted();
645            mInitialHeightOnTouch = mQsExpansionHeight;
646            mInitialTouchY = event.getX();
647            mInitialTouchX = event.getY();
648
649            // If we interrupt an expansion gesture here, make sure to update the state correctly.
650            if (mIsExpanding) {
651                onExpandingFinished();
652            }
653        }
654    }
655
656    @Override
657    protected boolean flingExpands(float vel, float vectorVel) {
658        boolean expands = super.flingExpands(vel, vectorVel);
659
660        // If we are already running a QS expansion, make sure that we keep the panel open.
661        if (mQsExpansionAnimator != null) {
662            expands = true;
663        }
664        return expands;
665    }
666
667    @Override
668    protected boolean hasConflictingGestures() {
669        return mStatusBar.getBarState() != StatusBarState.SHADE;
670    }
671
672    private void onQsTouch(MotionEvent event) {
673        int pointerIndex = event.findPointerIndex(mTrackingPointer);
674        if (pointerIndex < 0) {
675            pointerIndex = 0;
676            mTrackingPointer = event.getPointerId(pointerIndex);
677        }
678        final float y = event.getY(pointerIndex);
679        final float x = event.getX(pointerIndex);
680
681        switch (event.getActionMasked()) {
682            case MotionEvent.ACTION_DOWN:
683                mQsTracking = true;
684                mInitialTouchY = y;
685                mInitialTouchX = x;
686                onQsExpansionStarted();
687                mInitialHeightOnTouch = mQsExpansionHeight;
688                initVelocityTracker();
689                trackMovement(event);
690                break;
691
692            case MotionEvent.ACTION_POINTER_UP:
693                final int upPointer = event.getPointerId(event.getActionIndex());
694                if (mTrackingPointer == upPointer) {
695                    // gesture is ongoing, find a new pointer to track
696                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
697                    final float newY = event.getY(newIndex);
698                    final float newX = event.getX(newIndex);
699                    mTrackingPointer = event.getPointerId(newIndex);
700                    mInitialHeightOnTouch = mQsExpansionHeight;
701                    mInitialTouchY = newY;
702                    mInitialTouchX = newX;
703                }
704                break;
705
706            case MotionEvent.ACTION_MOVE:
707                final float h = y - mInitialTouchY;
708                setQsExpansion(h + mInitialHeightOnTouch);
709                if (h >= getFalsingThreshold()) {
710                    mQsTouchAboveFalsingThreshold = true;
711                }
712                trackMovement(event);
713                break;
714
715            case MotionEvent.ACTION_UP:
716            case MotionEvent.ACTION_CANCEL:
717                mQsTracking = false;
718                mTrackingPointer = -1;
719                trackMovement(event);
720                float fraction = getQsExpansionFraction();
721                if ((fraction != 0f || y >= mInitialTouchY)
722                        && (fraction != 1f || y <= mInitialTouchY)) {
723                    flingQsWithCurrentVelocity();
724                } else {
725                    mScrollYOverride = -1;
726                }
727                if (mVelocityTracker != null) {
728                    mVelocityTracker.recycle();
729                    mVelocityTracker = null;
730                }
731                break;
732        }
733    }
734
735    private int getFalsingThreshold() {
736        float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
737        return (int) (mQsFalsingThreshold * factor);
738    }
739
740    @Override
741    public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
742        if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
743                -1 /* yDiff: Not relevant here */)) {
744            onQsExpansionStarted(amount);
745            mInitialHeightOnTouch = mQsExpansionHeight;
746            mInitialTouchY = mLastTouchY;
747            mInitialTouchX = mLastTouchX;
748            mQsTracking = true;
749        }
750    }
751
752    @Override
753    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
754        cancelAnimation();
755        if (!mQsExpansionEnabled) {
756            amount = 0f;
757        }
758        float rounded = amount >= 1f ? amount : 0f;
759        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
760        mQsExpansionFromOverscroll = rounded != 0f;
761        mLastOverscroll = rounded;
762        updateQsState();
763        setQsExpansion(mQsMinExpansionHeight + rounded);
764    }
765
766    @Override
767    public void flingTopOverscroll(float velocity, boolean open) {
768        mLastOverscroll = 0f;
769        setQsExpansion(mQsExpansionHeight);
770        flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
771                new Runnable() {
772            @Override
773            public void run() {
774                mStackScrollerOverscrolling = false;
775                mQsExpansionFromOverscroll = false;
776                updateQsState();
777            }
778        });
779    }
780
781    private void onQsExpansionStarted() {
782        onQsExpansionStarted(0);
783    }
784
785    private void onQsExpansionStarted(int overscrollAmount) {
786        cancelAnimation();
787
788        // Reset scroll position and apply that position to the expanded height.
789        float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
790        if (mScrollView.getScrollY() != 0) {
791            mScrollYOverride = mScrollView.getScrollY();
792        }
793        mScrollView.scrollTo(0, 0);
794        setQsExpansion(height);
795    }
796
797    private void setQsExpanded(boolean expanded) {
798        boolean changed = mQsExpanded != expanded;
799        if (changed) {
800            mQsExpanded = expanded;
801            updateQsState();
802            requestPanelHeightUpdate();
803            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
804            mStatusBar.setQsExpanded(expanded);
805        }
806    }
807
808    public void setBarState(int statusBarState, boolean keyguardFadingAway,
809            boolean goingToFullShade) {
810        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
811                || statusBarState == StatusBarState.SHADE_LOCKED;
812        if (!mKeyguardShowing && keyguardShowing) {
813            setQsTranslation(mQsExpansionHeight);
814            mHeader.setTranslationY(0f);
815        }
816        setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
817        setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
818        if (goingToFullShade) {
819            animateKeyguardStatusBarOut();
820        } else {
821            mKeyguardStatusBar.setAlpha(1f);
822            mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
823        }
824        mStatusBarState = statusBarState;
825        mKeyguardShowing = keyguardShowing;
826        updateQsState();
827        if (goingToFullShade) {
828            animateHeaderSlidingIn();
829        }
830    }
831
832    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
833        @Override
834        public void run() {
835            mKeyguardStatusViewAnimating = false;
836            mKeyguardStatusView.setVisibility(View.GONE);
837        }
838    };
839
840    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
841        @Override
842        public void run() {
843            mKeyguardStatusViewAnimating = false;
844        }
845    };
846
847    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
848            = new AnimatorListenerAdapter() {
849        @Override
850        public void onAnimationEnd(Animator animation) {
851            mHeaderAnimatingIn = false;
852            mQsContainerAnimator = null;
853            mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
854        }
855    };
856
857    private final OnLayoutChangeListener mQsContainerAnimatorUpdater
858            = new OnLayoutChangeListener() {
859        @Override
860        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
861                int oldTop, int oldRight, int oldBottom) {
862            int oldHeight = oldBottom - oldTop;
863            int height = bottom - top;
864            if (height != oldHeight && mQsContainerAnimator != null) {
865                PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
866                float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
867                float newStartValue = -height - top;
868                values[0].setFloatValues(newStartValue, newEndValue);
869                mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
870            }
871        }
872    };
873
874    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
875            = new ViewTreeObserver.OnPreDrawListener() {
876        @Override
877        public boolean onPreDraw() {
878            getViewTreeObserver().removeOnPreDrawListener(this);
879            mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
880            mHeader.animate()
881                    .translationY(0f)
882                    .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay())
883                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
884                    .setInterpolator(mFastOutSlowInInterpolator)
885                    .start();
886            mQsContainer.setY(-mQsContainer.getHeight());
887            mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
888                    mQsContainer.getTranslationY(),
889                    mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
890                            - mQsContainer.getTop());
891            mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay());
892            mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
893            mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
894            mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
895            mQsContainerAnimator.start();
896            mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
897            return true;
898        }
899    };
900
901    private void animateHeaderSlidingIn() {
902        mHeaderAnimatingIn = true;
903        getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
904
905    }
906
907    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
908        @Override
909        public void run() {
910            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
911        }
912    };
913
914    private void animateKeyguardStatusBarOut() {
915        mKeyguardStatusBar.animate()
916                .alpha(0f)
917                .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
918                .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
919                .setInterpolator(PhoneStatusBar.ALPHA_OUT)
920                .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable)
921                .start();
922    }
923
924    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
925        @Override
926        public void run() {
927            mKeyguardBottomArea.setVisibility(View.GONE);
928        }
929    };
930
931    private void setKeyguardBottomAreaVisibility(int statusBarState,
932            boolean goingToFullShade) {
933        if (goingToFullShade) {
934            mKeyguardBottomArea.animate().cancel();
935            mKeyguardBottomArea.animate()
936                    .alpha(0f)
937                    .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
938                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
939                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
940                    .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
941                    .start();
942        } else if (statusBarState == StatusBarState.KEYGUARD
943                || statusBarState == StatusBarState.SHADE_LOCKED) {
944            mKeyguardBottomArea.animate().cancel();
945            mKeyguardBottomArea.setVisibility(View.VISIBLE);
946            mKeyguardBottomArea.setAlpha(1f);
947        } else {
948            mKeyguardBottomArea.animate().cancel();
949            mKeyguardBottomArea.setVisibility(View.GONE);
950            mKeyguardBottomArea.setAlpha(1f);
951        }
952    }
953
954    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
955            boolean goingToFullShade) {
956        if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
957                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
958            mKeyguardStatusView.animate().cancel();
959            mKeyguardStatusViewAnimating = true;
960            mKeyguardStatusView.animate()
961                    .alpha(0f)
962                    .setStartDelay(0)
963                    .setDuration(160)
964                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
965                    .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
966            if (keyguardFadingAway) {
967                mKeyguardStatusView.animate()
968                        .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
969                        .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
970                        .start();
971            }
972        } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
973                && statusBarState == StatusBarState.KEYGUARD) {
974            mKeyguardStatusView.animate().cancel();
975            mKeyguardStatusView.setVisibility(View.VISIBLE);
976            mKeyguardStatusViewAnimating = true;
977            mKeyguardStatusView.setAlpha(0f);
978            mKeyguardStatusView.animate()
979                    .alpha(1f)
980                    .setStartDelay(0)
981                    .setDuration(320)
982                    .setInterpolator(PhoneStatusBar.ALPHA_IN)
983                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
984        } else if (statusBarState == StatusBarState.KEYGUARD) {
985            mKeyguardStatusView.animate().cancel();
986            mKeyguardStatusView.setVisibility(View.VISIBLE);
987            mKeyguardStatusView.setAlpha(1f);
988        } else {
989            mKeyguardStatusView.animate().cancel();
990            mKeyguardStatusView.setVisibility(View.GONE);
991            mKeyguardStatusView.setAlpha(1f);
992        }
993    }
994
995    private void updateQsState() {
996        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
997        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE);
998        mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling));
999        mNotificationStackScroller.setScrollingEnabled(
1000                mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
1001                        || mQsExpansionFromOverscroll));
1002        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
1003        mQsContainer.setVisibility(
1004                mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
1005        mScrollView.setTouchEnabled(mQsExpanded);
1006        updateEmptyShadeView();
1007        mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
1008                && !mStackScrollerOverscrolling && mQsScrimEnabled
1009                        ? View.VISIBLE
1010                        : View.INVISIBLE);
1011        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
1012            mKeyguardUserSwitcher.hide(true /* animate */);
1013        }
1014    }
1015
1016    private void setQsExpansion(float height) {
1017        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
1018        mQsFullyExpanded = height == mQsMaxExpansionHeight;
1019        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
1020            setQsExpanded(true);
1021        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
1022            setQsExpanded(false);
1023            if (mLastAnnouncementWasQuickSettings && !mTracking) {
1024                announceForAccessibility(getKeyguardOrLockScreenString());
1025                mLastAnnouncementWasQuickSettings = false;
1026            }
1027        }
1028        mQsExpansionHeight = height;
1029        mHeader.setExpansion(getHeaderExpansionFraction());
1030        setQsTranslation(height);
1031        requestScrollerTopPaddingUpdate(false /* animate */);
1032        updateNotificationScrim(height);
1033        if (mKeyguardShowing) {
1034            updateHeaderKeyguard();
1035        }
1036        if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
1037                && !mStackScrollerOverscrolling && mQsScrimEnabled) {
1038            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
1039        }
1040
1041        // Upon initialisation when we are not layouted yet we don't want to announce that we are
1042        // fully expanded, hence the != 0.0f check.
1043        if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
1044            announceForAccessibility(getContext().getString(
1045                    R.string.accessibility_desc_quick_settings));
1046            mLastAnnouncementWasQuickSettings = true;
1047        }
1048    }
1049
1050    private String getKeyguardOrLockScreenString() {
1051        if (mStatusBarState == StatusBarState.KEYGUARD) {
1052            return getContext().getString(R.string.accessibility_desc_lock_screen);
1053        } else {
1054            return getContext().getString(R.string.accessibility_desc_notification_shade);
1055        }
1056    }
1057
1058    private void updateNotificationScrim(float height) {
1059        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
1060        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
1061        progress = Math.max(0.0f, Math.min(progress, 1.0f));
1062    }
1063
1064    private float getHeaderExpansionFraction() {
1065        if (!mKeyguardShowing) {
1066            return getQsExpansionFraction();
1067        } else {
1068            return 1f;
1069        }
1070    }
1071
1072    private void setQsTranslation(float height) {
1073        if (!mHeaderAnimatingIn) {
1074            mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
1075        }
1076        if (mKeyguardShowing) {
1077            mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
1078        }
1079    }
1080
1081    private float calculateQsTopPadding() {
1082        // We can only do the smoother transition on Keyguard when we also are not collapsing from a
1083        // scrolled quick settings.
1084        if (mKeyguardShowing && mScrollYOverride == -1) {
1085            return interpolate(getQsExpansionFraction(),
1086                    mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
1087                    mQsMaxExpansionHeight);
1088        } else {
1089            return mQsExpansionHeight;
1090        }
1091    }
1092
1093    private void requestScrollerTopPaddingUpdate(boolean animate) {
1094        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
1095                mScrollView.getScrollY(),
1096                mAnimateNextTopPaddingChange || animate);
1097        mAnimateNextTopPaddingChange = false;
1098    }
1099
1100    private void trackMovement(MotionEvent event) {
1101        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
1102        mLastTouchX = event.getX();
1103        mLastTouchY = event.getY();
1104    }
1105
1106    private void initVelocityTracker() {
1107        if (mVelocityTracker != null) {
1108            mVelocityTracker.recycle();
1109        }
1110        mVelocityTracker = VelocityTracker.obtain();
1111    }
1112
1113    private float getCurrentVelocity() {
1114        if (mVelocityTracker == null) {
1115            return 0;
1116        }
1117        mVelocityTracker.computeCurrentVelocity(1000);
1118        return mVelocityTracker.getYVelocity();
1119    }
1120
1121    private void cancelAnimation() {
1122        if (mQsExpansionAnimator != null) {
1123            mQsExpansionAnimator.cancel();
1124        }
1125    }
1126
1127    private void flingSettings(float vel, boolean expand) {
1128        flingSettings(vel, expand, null);
1129    }
1130
1131    private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
1132        float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
1133        if (target == mQsExpansionHeight) {
1134            mScrollYOverride = -1;
1135            if (onFinishRunnable != null) {
1136                onFinishRunnable.run();
1137            }
1138            return;
1139        }
1140        boolean belowFalsingThreshold = isBelowFalsingThreshold();
1141        if (belowFalsingThreshold) {
1142            vel = 0;
1143        }
1144        mScrollView.setBlockFlinging(true);
1145        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
1146        mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
1147        if (belowFalsingThreshold) {
1148            animator.setDuration(350);
1149        }
1150        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1151            @Override
1152            public void onAnimationUpdate(ValueAnimator animation) {
1153                setQsExpansion((Float) animation.getAnimatedValue());
1154            }
1155        });
1156        animator.addListener(new AnimatorListenerAdapter() {
1157            @Override
1158            public void onAnimationEnd(Animator animation) {
1159                mScrollView.setBlockFlinging(false);
1160                mScrollYOverride = -1;
1161                mQsExpansionAnimator = null;
1162                if (onFinishRunnable != null) {
1163                    onFinishRunnable.run();
1164                }
1165            }
1166        });
1167        animator.start();
1168        mQsExpansionAnimator = animator;
1169        mQsAnimatorExpand = expand;
1170    }
1171
1172    /**
1173     * @return Whether we should intercept a gesture to open Quick Settings.
1174     */
1175    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
1176        if (!mQsExpansionEnabled) {
1177            return false;
1178        }
1179        View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
1180        boolean onHeader = x >= header.getLeft() && x <= header.getRight()
1181                && y >= header.getTop() && y <= header.getBottom();
1182        if (mQsExpanded) {
1183            return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
1184        } else {
1185            return onHeader;
1186        }
1187    }
1188
1189    @Override
1190    protected boolean isScrolledToBottom() {
1191        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1192            return true;
1193        }
1194        if (!isInSettings()) {
1195            return mNotificationStackScroller.isScrolledToBottom();
1196        } else {
1197            return mScrollView.isScrolledToBottom();
1198        }
1199    }
1200
1201    @Override
1202    protected int getMaxPanelHeight() {
1203        int min = mStatusBarMinHeight;
1204        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
1205                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
1206            int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
1207                    * HEADER_RUBBERBAND_FACTOR);
1208            min = Math.max(min, minHeight);
1209        }
1210        int maxHeight;
1211        if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
1212            maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade());
1213        } else {
1214            maxHeight = calculatePanelHeightShade();
1215        }
1216        maxHeight = Math.max(maxHeight, min);
1217        return maxHeight;
1218    }
1219
1220    private boolean isInSettings() {
1221        return mQsExpanded;
1222    }
1223
1224    @Override
1225    protected void onHeightUpdated(float expandedHeight) {
1226        if (!mQsExpanded) {
1227            positionClockAndNotifications();
1228        }
1229        if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
1230                && !mQsExpansionFromOverscroll) {
1231            float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
1232                    + mNotificationStackScroller.getMinStackHeight()
1233                    + mNotificationStackScroller.getNotificationTopPadding();
1234            float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
1235            float t = (expandedHeight - panelHeightQsCollapsed)
1236                    / (panelHeightQsExpanded - panelHeightQsCollapsed);
1237
1238            setQsExpansion(mQsMinExpansionHeight
1239                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
1240        }
1241        mNotificationStackScroller.setStackHeight(expandedHeight);
1242        updateHeader();
1243        updateUnlockIcon();
1244        updateNotificationTranslucency();
1245    }
1246
1247    /**
1248     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
1249     *         collapsing QS / the panel when QS was scrolled
1250     */
1251    private int getTempQsMaxExpansion() {
1252        int qsTempMaxExpansion = mQsMaxExpansionHeight;
1253        if (mScrollYOverride != -1) {
1254            qsTempMaxExpansion -= mScrollYOverride;
1255        }
1256        return qsTempMaxExpansion;
1257    }
1258
1259    private int calculatePanelHeightShade() {
1260        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
1261        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
1262                - mTopPaddingAdjustment;
1263        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
1264        return maxHeight;
1265    }
1266
1267    private int calculatePanelHeightQsExpanded() {
1268        float notificationHeight = mNotificationStackScroller.getHeight()
1269                - mNotificationStackScroller.getEmptyBottomMargin()
1270                - mNotificationStackScroller.getTopPadding();
1271        float totalHeight = mQsMaxExpansionHeight + notificationHeight
1272                + mNotificationStackScroller.getNotificationTopPadding();
1273        if (totalHeight > mNotificationStackScroller.getHeight()) {
1274            float fullyCollapsedHeight = mQsMaxExpansionHeight
1275                    + mNotificationStackScroller.getMinStackHeight()
1276                    + mNotificationStackScroller.getNotificationTopPadding()
1277                    - getScrollViewScrollY();
1278            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
1279        }
1280        return (int) totalHeight;
1281    }
1282
1283    private int getScrollViewScrollY() {
1284        if (mScrollYOverride != -1) {
1285            return mScrollYOverride;
1286        } else {
1287            return mScrollView.getScrollY();
1288        }
1289    }
1290    private void updateNotificationTranslucency() {
1291        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
1292                / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
1293                        - mNotificationStackScroller.getCollapseSecondCardPadding());
1294        alpha = Math.max(0, Math.min(alpha, 1));
1295        alpha = (float) Math.pow(alpha, 0.75);
1296        if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
1297            mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
1298        } else if (alpha == 1f
1299                && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
1300            mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
1301        }
1302        mNotificationStackScroller.setAlpha(alpha);
1303    }
1304
1305    @Override
1306    protected float getOverExpansionAmount() {
1307        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
1308    }
1309
1310    @Override
1311    protected float getOverExpansionPixels() {
1312        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
1313    }
1314
1315    private void updateUnlockIcon() {
1316        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1317                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1318            boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
1319            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1320            if (active && !mUnlockIconActive && mTracking) {
1321                lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
1322                lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
1323                        mFastOutLinearInterpolator);
1324            } else if (!active && mUnlockIconActive && mTracking) {
1325                lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
1326                        150, mFastOutLinearInterpolator, null);
1327                lockIcon.setImageScale(1.0f, true, 150,
1328                        mFastOutLinearInterpolator);
1329            }
1330            mUnlockIconActive = active;
1331        }
1332    }
1333
1334    /**
1335     * Hides the header when notifications are colliding with it.
1336     */
1337    private void updateHeader() {
1338        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1339                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1340            updateHeaderKeyguard();
1341        } else {
1342            updateHeaderShade();
1343        }
1344
1345    }
1346
1347    private void updateHeaderShade() {
1348        if (!mHeaderAnimatingIn) {
1349            mHeader.setTranslationY(getHeaderTranslation());
1350        }
1351        setQsTranslation(mQsExpansionHeight);
1352    }
1353
1354    private float getHeaderTranslation() {
1355        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1356                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1357            return 0;
1358        }
1359        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1360            if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
1361                return 0;
1362            } else {
1363                return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
1364            }
1365        }
1366        return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
1367    }
1368
1369    private void updateHeaderKeyguard() {
1370        float alphaNotifications;
1371        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1372
1373            // When on Keyguard, we hide the header as soon as the top card of the notification
1374            // stack scroller is close enough (collision distance) to the bottom of the header.
1375            alphaNotifications = getNotificationsTopY()
1376                    /
1377                    (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
1378        } else {
1379
1380            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
1381            // soon as we start translating the stack.
1382            alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
1383        }
1384        alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1);
1385        alphaNotifications = (float) Math.pow(alphaNotifications, 0.75);
1386        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
1387        mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion));
1388        mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications));
1389        setQsTranslation(mQsExpansionHeight);
1390    }
1391
1392    private float getNotificationsTopY() {
1393        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1394            return getExpandedHeight();
1395        }
1396        return mNotificationStackScroller.getNotificationsTopY();
1397    }
1398
1399    @Override
1400    protected void onExpandingStarted() {
1401        super.onExpandingStarted();
1402        mNotificationStackScroller.onExpansionStarted();
1403        mIsExpanding = true;
1404        mQsExpandedWhenExpandingStarted = mQsExpanded;
1405        if (mQsExpanded) {
1406            onQsExpansionStarted();
1407        }
1408    }
1409
1410    @Override
1411    protected void onExpandingFinished() {
1412        super.onExpandingFinished();
1413        mNotificationStackScroller.onExpansionStopped();
1414        mIsExpanding = false;
1415        mScrollYOverride = -1;
1416        if (mExpandedHeight == 0f) {
1417            setListening(false);
1418        } else {
1419            setListening(true);
1420        }
1421        mTwoFingerQsExpand = false;
1422        mTwoFingerQsExpandPossible = false;
1423    }
1424
1425    private void setListening(boolean listening) {
1426        mHeader.setListening(listening);
1427        mKeyguardStatusBar.setListening(listening);
1428        mQsPanel.setListening(listening);
1429    }
1430
1431    @Override
1432    public void instantExpand() {
1433        super.instantExpand();
1434        setListening(true);
1435    }
1436
1437    @Override
1438    protected void setOverExpansion(float overExpansion, boolean isPixels) {
1439        if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) {
1440            return;
1441        }
1442        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1443            mNotificationStackScroller.setOnHeightChangedListener(null);
1444            if (isPixels) {
1445                mNotificationStackScroller.setOverScrolledPixels(
1446                        overExpansion, true /* onTop */, false /* animate */);
1447            } else {
1448                mNotificationStackScroller.setOverScrollAmount(
1449                        overExpansion, true /* onTop */, false /* animate */);
1450            }
1451            mNotificationStackScroller.setOnHeightChangedListener(this);
1452        }
1453    }
1454
1455    @Override
1456    protected void onTrackingStarted() {
1457        super.onTrackingStarted();
1458        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1459                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1460            mAfforanceHelper.animateHideLeftRightIcon();
1461        } else if (mQsExpanded) {
1462            mTwoFingerQsExpand = true;
1463        }
1464    }
1465
1466    @Override
1467    protected void onTrackingStopped(boolean expand) {
1468        super.onTrackingStopped(expand);
1469        if (expand) {
1470            mNotificationStackScroller.setOverScrolledPixels(
1471                    0.0f, true /* onTop */, true /* animate */);
1472        }
1473        if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1474                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1475            if (!mHintAnimationRunning) {
1476                mAfforanceHelper.reset(true);
1477            }
1478        }
1479        if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1480                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1481            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1482            lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
1483            lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
1484        }
1485    }
1486
1487    @Override
1488    public void onHeightChanged(ExpandableView view) {
1489
1490        // Block update if we are in quick settings and just the top padding changed
1491        // (i.e. view == null).
1492        if (view == null && mQsExpanded) {
1493            return;
1494        }
1495        requestPanelHeightUpdate();
1496    }
1497
1498    @Override
1499    public void onReset(ExpandableView view) {
1500    }
1501
1502    @Override
1503    public void onScrollChanged() {
1504        if (mQsExpanded) {
1505            requestScrollerTopPaddingUpdate(false /* animate */);
1506            requestPanelHeightUpdate();
1507        }
1508    }
1509
1510    @Override
1511    protected void onConfigurationChanged(Configuration newConfig) {
1512        super.onConfigurationChanged(newConfig);
1513        mAfforanceHelper.onConfigurationChanged();
1514    }
1515
1516    @Override
1517    public void onClick(View v) {
1518        if (v == mHeader) {
1519            onQsExpansionStarted();
1520            if (mQsExpanded) {
1521                flingSettings(0 /* vel */, false /* expand */);
1522            } else if (mQsExpansionEnabled) {
1523                flingSettings(0 /* vel */, true /* expand */);
1524            }
1525        }
1526    }
1527
1528    @Override
1529    public void onAnimationToSideStarted(boolean rightPage) {
1530        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1531        mIsLaunchTransitionRunning = true;
1532        mLaunchAnimationEndRunnable = null;
1533        if (start) {
1534            mKeyguardBottomArea.launchPhone();
1535        } else {
1536            mSecureCameraLaunchManager.startSecureCameraLaunch();
1537        }
1538        mBlockTouches = true;
1539    }
1540
1541    @Override
1542    public void onAnimationToSideEnded() {
1543        mIsLaunchTransitionRunning = false;
1544        mIsLaunchTransitionFinished = true;
1545        if (mLaunchAnimationEndRunnable != null) {
1546            mLaunchAnimationEndRunnable.run();
1547            mLaunchAnimationEndRunnable = null;
1548        }
1549    }
1550
1551    @Override
1552    protected void onEdgeClicked(boolean right) {
1553        if ((right && getRightIcon().getVisibility() != View.VISIBLE)
1554                || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) {
1555            return;
1556        }
1557        mHintAnimationRunning = true;
1558        mAfforanceHelper.startHintAnimation(right, new Runnable() {
1559            @Override
1560            public void run() {
1561                mHintAnimationRunning = false;
1562                mStatusBar.onHintFinished();
1563            }
1564        });
1565        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
1566        if (start) {
1567            mStatusBar.onPhoneHintStarted();
1568        } else {
1569            mStatusBar.onCameraHintStarted();
1570        }
1571    }
1572
1573    @Override
1574    protected void startUnlockHintAnimation() {
1575        super.startUnlockHintAnimation();
1576        startHighlightIconAnimation(getCenterIcon());
1577    }
1578
1579    /**
1580     * Starts the highlight (making it fully opaque) animation on an icon.
1581     */
1582    private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1583        icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1584                mFastOutSlowInInterpolator, new Runnable() {
1585                    @Override
1586                    public void run() {
1587                        icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
1588                                true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1589                                mFastOutSlowInInterpolator, null);
1590                    }
1591                });
1592    }
1593
1594    @Override
1595    public float getPageWidth() {
1596        return getWidth();
1597    }
1598
1599    @Override
1600    public void onSwipingStarted() {
1601        mSecureCameraLaunchManager.onSwipingStarted();
1602        requestDisallowInterceptTouchEvent(true);
1603        mOnlyAffordanceInThisMotion = true;
1604    }
1605
1606    @Override
1607    public KeyguardAffordanceView getLeftIcon() {
1608        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1609                ? mKeyguardBottomArea.getCameraView()
1610                : mKeyguardBottomArea.getPhoneView();
1611    }
1612
1613    @Override
1614    public KeyguardAffordanceView getCenterIcon() {
1615        return mKeyguardBottomArea.getLockIcon();
1616    }
1617
1618    @Override
1619    public KeyguardAffordanceView getRightIcon() {
1620        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1621                ? mKeyguardBottomArea.getPhoneView()
1622                : mKeyguardBottomArea.getCameraView();
1623    }
1624
1625    @Override
1626    public View getLeftPreview() {
1627        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1628                ? mKeyguardBottomArea.getCameraPreview()
1629                : mKeyguardBottomArea.getPhonePreview();
1630    }
1631
1632    @Override
1633    public View getRightPreview() {
1634        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1635                ? mKeyguardBottomArea.getPhonePreview()
1636                : mKeyguardBottomArea.getCameraPreview();
1637    }
1638
1639    @Override
1640    public float getAffordanceFalsingFactor() {
1641        return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
1642    }
1643
1644    @Override
1645    protected float getPeekHeight() {
1646        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1647            return mNotificationStackScroller.getPeekHeight();
1648        } else {
1649            return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
1650        }
1651    }
1652
1653    @Override
1654    protected float getCannedFlingDurationFactor() {
1655        if (mQsExpanded) {
1656            return 0.7f;
1657        } else {
1658            return 0.6f;
1659        }
1660    }
1661
1662    @Override
1663    protected boolean fullyExpandedClearAllVisible() {
1664        return mNotificationStackScroller.isDismissViewNotGone()
1665                && mNotificationStackScroller.isScrolledToBottom() && !mTwoFingerQsExpand;
1666    }
1667
1668    @Override
1669    protected boolean isClearAllVisible() {
1670        return mNotificationStackScroller.isDismissViewVisible();
1671    }
1672
1673    @Override
1674    protected int getClearAllHeight() {
1675        return mNotificationStackScroller.getDismissViewHeight();
1676    }
1677
1678    @Override
1679    protected boolean isTrackingBlocked() {
1680        return mConflictingQsExpansionGesture && mQsExpanded;
1681    }
1682
1683    public void notifyVisibleChildrenChanged() {
1684        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
1685            mReserveNotificationSpace.setVisibility(View.VISIBLE);
1686        } else {
1687            mReserveNotificationSpace.setVisibility(View.GONE);
1688        }
1689    }
1690
1691    public boolean isQsExpanded() {
1692        return mQsExpanded;
1693    }
1694
1695    public boolean isQsDetailShowing() {
1696        return mQsPanel.isShowingDetail();
1697    }
1698
1699    public void closeQsDetail() {
1700        mQsPanel.closeDetail();
1701    }
1702
1703    @Override
1704    public boolean shouldDelayChildPressedState() {
1705        return true;
1706    }
1707
1708    public boolean isLaunchTransitionFinished() {
1709        return mIsLaunchTransitionFinished;
1710    }
1711
1712    public boolean isLaunchTransitionRunning() {
1713        return mIsLaunchTransitionRunning;
1714    }
1715
1716    public void setLaunchTransitionEndRunnable(Runnable r) {
1717        mLaunchAnimationEndRunnable = r;
1718    }
1719
1720    public void setEmptyDragAmount(float amount) {
1721        float factor = 0.8f;
1722        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1723            factor = 0.4f;
1724        } else if (!mStatusBar.hasActiveNotifications()) {
1725            factor = 0.4f;
1726        }
1727        mEmptyDragAmount = amount * factor;
1728        positionClockAndNotifications();
1729    }
1730
1731    private static float interpolate(float t, float start, float end) {
1732        return (1 - t) * start + t * end;
1733    }
1734
1735    private void updateKeyguardStatusBarVisibility() {
1736        mKeyguardStatusBar.setVisibility(mKeyguardShowing && !mDozing ? VISIBLE : INVISIBLE);
1737    }
1738
1739    public void setDozing(boolean dozing) {
1740        if (dozing == mDozing) return;
1741        mDozing = dozing;
1742        if (mDozing) {
1743            setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0xff, false /*animate*/);
1744        } else {
1745            setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0, true /*animate*/);
1746        }
1747        updateKeyguardStatusBarVisibility();
1748    }
1749
1750    public boolean isDozing() {
1751        return mDozing;
1752    }
1753
1754    private static void setBackgroundColorAlpha(final View target, int rgb, int targetAlpha,
1755            boolean animate) {
1756        int currentAlpha = getBackgroundAlpha(target);
1757        if (currentAlpha == targetAlpha) {
1758            return;
1759        }
1760        final int r = Color.red(rgb);
1761        final int g = Color.green(rgb);
1762        final int b = Color.blue(rgb);
1763        Object runningAnim = target.getTag(TAG_KEY_ANIM);
1764        if (runningAnim instanceof ValueAnimator) {
1765            ((ValueAnimator) runningAnim).cancel();
1766        }
1767        if (!animate) {
1768            target.setBackgroundColor(Color.argb(targetAlpha, r, g, b));
1769            return;
1770        }
1771        ValueAnimator anim = ValueAnimator.ofInt(currentAlpha, targetAlpha);
1772        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1773            @Override
1774            public void onAnimationUpdate(ValueAnimator animation) {
1775                int value = (int) animation.getAnimatedValue();
1776                target.setBackgroundColor(Color.argb(value, r, g, b));
1777            }
1778        });
1779        anim.setDuration(DOZE_BACKGROUND_ANIM_DURATION);
1780        anim.addListener(new AnimatorListenerAdapter() {
1781            @Override
1782            public void onAnimationEnd(Animator animation) {
1783                target.setTag(TAG_KEY_ANIM, null);
1784            }
1785        });
1786        anim.start();
1787        target.setTag(TAG_KEY_ANIM, anim);
1788    }
1789
1790    private static int getBackgroundAlpha(View view) {
1791        if (view.getBackground() instanceof ColorDrawable) {
1792            ColorDrawable drawable = (ColorDrawable) view.getBackground();
1793            return Color.alpha(drawable.getColor());
1794        } else {
1795            return 0;
1796        }
1797    }
1798
1799    public void setShadeEmpty(boolean shadeEmpty) {
1800        mShadeEmpty = shadeEmpty;
1801        updateEmptyShadeView();
1802    }
1803
1804    private void updateEmptyShadeView() {
1805
1806        // Hide "No notifications" in QS.
1807        mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
1808    }
1809
1810    public void setQsScrimEnabled(boolean qsScrimEnabled) {
1811        boolean changed = mQsScrimEnabled != qsScrimEnabled;
1812        mQsScrimEnabled = qsScrimEnabled;
1813        if (changed) {
1814            updateQsState();
1815        }
1816    }
1817
1818    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
1819        mKeyguardUserSwitcher = keyguardUserSwitcher;
1820    }
1821
1822    private final Runnable mUpdateHeader = new Runnable() {
1823        @Override
1824        public void run() {
1825            mHeader.updateEverything();
1826        }
1827    };
1828
1829    public void onScreenTurnedOn() {
1830        mKeyguardStatusView.refreshTime();
1831    }
1832}
1833