NotificationPanelView.java revision cf5d3c98d1da0b1cfd4976a6962e7244490bdc8d
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 >= mQsFalsingThreshold) {
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    @Override
736    public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
737        if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
738                -1 /* yDiff: Not relevant here */)) {
739            onQsExpansionStarted(amount);
740            mInitialHeightOnTouch = mQsExpansionHeight;
741            mInitialTouchY = mLastTouchY;
742            mInitialTouchX = mLastTouchX;
743            mQsTracking = true;
744        }
745    }
746
747    @Override
748    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
749        cancelAnimation();
750        if (!mQsExpansionEnabled) {
751            amount = 0f;
752        }
753        float rounded = amount >= 1f ? amount : 0f;
754        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
755        mQsExpansionFromOverscroll = rounded != 0f;
756        mLastOverscroll = rounded;
757        updateQsState();
758        setQsExpansion(mQsMinExpansionHeight + rounded);
759    }
760
761    @Override
762    public void flingTopOverscroll(float velocity, boolean open) {
763        mLastOverscroll = 0f;
764        setQsExpansion(mQsExpansionHeight);
765        flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
766                new Runnable() {
767            @Override
768            public void run() {
769                mStackScrollerOverscrolling = false;
770                mQsExpansionFromOverscroll = false;
771                updateQsState();
772            }
773        });
774    }
775
776    private void onQsExpansionStarted() {
777        onQsExpansionStarted(0);
778    }
779
780    private void onQsExpansionStarted(int overscrollAmount) {
781        cancelAnimation();
782
783        // Reset scroll position and apply that position to the expanded height.
784        float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
785        if (mScrollView.getScrollY() != 0) {
786            mScrollYOverride = mScrollView.getScrollY();
787        }
788        mScrollView.scrollTo(0, 0);
789        setQsExpansion(height);
790    }
791
792    private void setQsExpanded(boolean expanded) {
793        boolean changed = mQsExpanded != expanded;
794        if (changed) {
795            mQsExpanded = expanded;
796            updateQsState();
797            requestPanelHeightUpdate();
798            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
799            mStatusBar.setQsExpanded(expanded);
800        }
801    }
802
803    public void setBarState(int statusBarState, boolean keyguardFadingAway,
804            boolean goingToFullShade) {
805        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
806                || statusBarState == StatusBarState.SHADE_LOCKED;
807        if (!mKeyguardShowing && keyguardShowing) {
808            setQsTranslation(mQsExpansionHeight);
809            mHeader.setTranslationY(0f);
810        }
811        setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
812        setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
813        if (goingToFullShade) {
814            animateKeyguardStatusBarOut();
815        } else {
816            mKeyguardStatusBar.setAlpha(1f);
817            mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
818        }
819        mStatusBarState = statusBarState;
820        mKeyguardShowing = keyguardShowing;
821        updateQsState();
822        if (goingToFullShade) {
823            animateHeaderSlidingIn();
824        }
825    }
826
827    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
828        @Override
829        public void run() {
830            mKeyguardStatusViewAnimating = false;
831            mKeyguardStatusView.setVisibility(View.GONE);
832        }
833    };
834
835    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
836        @Override
837        public void run() {
838            mKeyguardStatusViewAnimating = false;
839        }
840    };
841
842    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
843            = new AnimatorListenerAdapter() {
844        @Override
845        public void onAnimationEnd(Animator animation) {
846            mHeaderAnimatingIn = false;
847            mQsContainerAnimator = null;
848            mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
849        }
850    };
851
852    private final OnLayoutChangeListener mQsContainerAnimatorUpdater
853            = new OnLayoutChangeListener() {
854        @Override
855        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
856                int oldTop, int oldRight, int oldBottom) {
857            int oldHeight = oldBottom - oldTop;
858            int height = bottom - top;
859            if (height != oldHeight && mQsContainerAnimator != null) {
860                PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
861                float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
862                float newStartValue = -height - top;
863                values[0].setFloatValues(newStartValue, newEndValue);
864                mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
865            }
866        }
867    };
868
869    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
870            = new ViewTreeObserver.OnPreDrawListener() {
871        @Override
872        public boolean onPreDraw() {
873            getViewTreeObserver().removeOnPreDrawListener(this);
874            mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
875            mHeader.animate()
876                    .translationY(0f)
877                    .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay())
878                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
879                    .setInterpolator(mFastOutSlowInInterpolator)
880                    .start();
881            mQsContainer.setY(-mQsContainer.getHeight());
882            mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
883                    mQsContainer.getTranslationY(),
884                    mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
885                            - mQsContainer.getTop());
886            mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay());
887            mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
888            mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
889            mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
890            mQsContainerAnimator.start();
891            mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
892            return true;
893        }
894    };
895
896    private void animateHeaderSlidingIn() {
897        mHeaderAnimatingIn = true;
898        getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
899
900    }
901
902    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
903        @Override
904        public void run() {
905            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
906        }
907    };
908
909    private void animateKeyguardStatusBarOut() {
910        mKeyguardStatusBar.animate()
911                .alpha(0f)
912                .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
913                .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
914                .setInterpolator(PhoneStatusBar.ALPHA_OUT)
915                .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable)
916                .start();
917    }
918
919    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
920        @Override
921        public void run() {
922            mKeyguardBottomArea.setVisibility(View.GONE);
923        }
924    };
925
926    private void setKeyguardBottomAreaVisibility(int statusBarState,
927            boolean goingToFullShade) {
928        if (goingToFullShade) {
929            mKeyguardBottomArea.animate().cancel();
930            mKeyguardBottomArea.animate()
931                    .alpha(0f)
932                    .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
933                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
934                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
935                    .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
936                    .start();
937        } else if (statusBarState == StatusBarState.KEYGUARD
938                || statusBarState == StatusBarState.SHADE_LOCKED) {
939            mKeyguardBottomArea.animate().cancel();
940            mKeyguardBottomArea.setVisibility(View.VISIBLE);
941            mKeyguardBottomArea.setAlpha(1f);
942        } else {
943            mKeyguardBottomArea.animate().cancel();
944            mKeyguardBottomArea.setVisibility(View.GONE);
945            mKeyguardBottomArea.setAlpha(1f);
946        }
947    }
948
949    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
950            boolean goingToFullShade) {
951        if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
952                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
953            mKeyguardStatusView.animate().cancel();
954            mKeyguardStatusViewAnimating = true;
955            mKeyguardStatusView.animate()
956                    .alpha(0f)
957                    .setStartDelay(0)
958                    .setDuration(160)
959                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
960                    .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
961            if (keyguardFadingAway) {
962                mKeyguardStatusView.animate()
963                        .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
964                        .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
965                        .start();
966            }
967        } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
968                && statusBarState == StatusBarState.KEYGUARD) {
969            mKeyguardStatusView.animate().cancel();
970            mKeyguardStatusView.setVisibility(View.VISIBLE);
971            mKeyguardStatusViewAnimating = true;
972            mKeyguardStatusView.setAlpha(0f);
973            mKeyguardStatusView.animate()
974                    .alpha(1f)
975                    .setStartDelay(0)
976                    .setDuration(320)
977                    .setInterpolator(PhoneStatusBar.ALPHA_IN)
978                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
979        } else if (statusBarState == StatusBarState.KEYGUARD) {
980            mKeyguardStatusView.animate().cancel();
981            mKeyguardStatusView.setVisibility(View.VISIBLE);
982            mKeyguardStatusView.setAlpha(1f);
983        } else {
984            mKeyguardStatusView.animate().cancel();
985            mKeyguardStatusView.setVisibility(View.GONE);
986            mKeyguardStatusView.setAlpha(1f);
987        }
988    }
989
990    private void updateQsState() {
991        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
992        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE);
993        mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling));
994        mNotificationStackScroller.setScrollingEnabled(
995                mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
996                        || mQsExpansionFromOverscroll));
997        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
998        mQsContainer.setVisibility(
999                mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
1000        mScrollView.setTouchEnabled(mQsExpanded);
1001        updateEmptyShadeView();
1002        mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
1003                && !mStackScrollerOverscrolling && mQsScrimEnabled
1004                        ? View.VISIBLE
1005                        : View.INVISIBLE);
1006        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
1007            mKeyguardUserSwitcher.hide(true /* animate */);
1008        }
1009    }
1010
1011    private void setQsExpansion(float height) {
1012        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
1013        mQsFullyExpanded = height == mQsMaxExpansionHeight;
1014        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
1015            setQsExpanded(true);
1016        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
1017            setQsExpanded(false);
1018            if (mLastAnnouncementWasQuickSettings && !mTracking) {
1019                announceForAccessibility(getKeyguardOrLockScreenString());
1020                mLastAnnouncementWasQuickSettings = false;
1021            }
1022        }
1023        mQsExpansionHeight = height;
1024        mHeader.setExpansion(getHeaderExpansionFraction());
1025        setQsTranslation(height);
1026        requestScrollerTopPaddingUpdate(false /* animate */);
1027        updateNotificationScrim(height);
1028        if (mKeyguardShowing) {
1029            updateHeaderKeyguard();
1030        }
1031        if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
1032                && !mStackScrollerOverscrolling && mQsScrimEnabled) {
1033            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
1034        }
1035
1036        // Upon initialisation when we are not layouted yet we don't want to announce that we are
1037        // fully expanded, hence the != 0.0f check.
1038        if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
1039            announceForAccessibility(getContext().getString(
1040                    R.string.accessibility_desc_quick_settings));
1041            mLastAnnouncementWasQuickSettings = true;
1042        }
1043    }
1044
1045    private String getKeyguardOrLockScreenString() {
1046        if (mStatusBarState == StatusBarState.KEYGUARD) {
1047            return getContext().getString(R.string.accessibility_desc_lock_screen);
1048        } else {
1049            return getContext().getString(R.string.accessibility_desc_notification_shade);
1050        }
1051    }
1052
1053    private void updateNotificationScrim(float height) {
1054        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
1055        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
1056        progress = Math.max(0.0f, Math.min(progress, 1.0f));
1057    }
1058
1059    private float getHeaderExpansionFraction() {
1060        if (!mKeyguardShowing) {
1061            return getQsExpansionFraction();
1062        } else {
1063            return 1f;
1064        }
1065    }
1066
1067    private void setQsTranslation(float height) {
1068        if (!mHeaderAnimatingIn) {
1069            mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
1070        }
1071        if (mKeyguardShowing) {
1072            mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
1073        }
1074    }
1075
1076    private float calculateQsTopPadding() {
1077        // We can only do the smoother transition on Keyguard when we also are not collapsing from a
1078        // scrolled quick settings.
1079        if (mKeyguardShowing && mScrollYOverride == -1) {
1080            return interpolate(getQsExpansionFraction(),
1081                    mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
1082                    mQsMaxExpansionHeight);
1083        } else {
1084            return mQsExpansionHeight;
1085        }
1086    }
1087
1088    private void requestScrollerTopPaddingUpdate(boolean animate) {
1089        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
1090                mScrollView.getScrollY(),
1091                mAnimateNextTopPaddingChange || animate);
1092        mAnimateNextTopPaddingChange = false;
1093    }
1094
1095    private void trackMovement(MotionEvent event) {
1096        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
1097        mLastTouchX = event.getX();
1098        mLastTouchY = event.getY();
1099    }
1100
1101    private void initVelocityTracker() {
1102        if (mVelocityTracker != null) {
1103            mVelocityTracker.recycle();
1104        }
1105        mVelocityTracker = VelocityTracker.obtain();
1106    }
1107
1108    private float getCurrentVelocity() {
1109        if (mVelocityTracker == null) {
1110            return 0;
1111        }
1112        mVelocityTracker.computeCurrentVelocity(1000);
1113        return mVelocityTracker.getYVelocity();
1114    }
1115
1116    private void cancelAnimation() {
1117        if (mQsExpansionAnimator != null) {
1118            mQsExpansionAnimator.cancel();
1119        }
1120    }
1121
1122    private void flingSettings(float vel, boolean expand) {
1123        flingSettings(vel, expand, null);
1124    }
1125
1126    private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
1127        float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
1128        if (target == mQsExpansionHeight) {
1129            mScrollYOverride = -1;
1130            if (onFinishRunnable != null) {
1131                onFinishRunnable.run();
1132            }
1133            return;
1134        }
1135        boolean belowFalsingThreshold = isBelowFalsingThreshold();
1136        if (belowFalsingThreshold) {
1137            vel = 0;
1138        }
1139        mScrollView.setBlockFlinging(true);
1140        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
1141        mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
1142        if (belowFalsingThreshold) {
1143            animator.setDuration(350);
1144        }
1145        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1146            @Override
1147            public void onAnimationUpdate(ValueAnimator animation) {
1148                setQsExpansion((Float) animation.getAnimatedValue());
1149            }
1150        });
1151        animator.addListener(new AnimatorListenerAdapter() {
1152            @Override
1153            public void onAnimationEnd(Animator animation) {
1154                mScrollView.setBlockFlinging(false);
1155                mScrollYOverride = -1;
1156                mQsExpansionAnimator = null;
1157                if (onFinishRunnable != null) {
1158                    onFinishRunnable.run();
1159                }
1160            }
1161        });
1162        animator.start();
1163        mQsExpansionAnimator = animator;
1164        mQsAnimatorExpand = expand;
1165    }
1166
1167    /**
1168     * @return Whether we should intercept a gesture to open Quick Settings.
1169     */
1170    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
1171        if (!mQsExpansionEnabled) {
1172            return false;
1173        }
1174        View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
1175        boolean onHeader = x >= header.getLeft() && x <= header.getRight()
1176                && y >= header.getTop() && y <= header.getBottom();
1177        if (mQsExpanded) {
1178            return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
1179        } else {
1180            return onHeader;
1181        }
1182    }
1183
1184    @Override
1185    protected boolean isScrolledToBottom() {
1186        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1187            return true;
1188        }
1189        if (!isInSettings()) {
1190            return mNotificationStackScroller.isScrolledToBottom();
1191        } else {
1192            return mScrollView.isScrolledToBottom();
1193        }
1194    }
1195
1196    @Override
1197    protected int getMaxPanelHeight() {
1198        int min = mStatusBarMinHeight;
1199        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
1200                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
1201            int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
1202                    * HEADER_RUBBERBAND_FACTOR);
1203            min = Math.max(min, minHeight);
1204        }
1205        int maxHeight;
1206        if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
1207            maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade());
1208        } else {
1209            maxHeight = calculatePanelHeightShade();
1210        }
1211        maxHeight = Math.max(maxHeight, min);
1212        return maxHeight;
1213    }
1214
1215    private boolean isInSettings() {
1216        return mQsExpanded;
1217    }
1218
1219    @Override
1220    protected void onHeightUpdated(float expandedHeight) {
1221        if (!mQsExpanded) {
1222            positionClockAndNotifications();
1223        }
1224        if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
1225                && !mQsExpansionFromOverscroll) {
1226            float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
1227                    + mNotificationStackScroller.getMinStackHeight()
1228                    + mNotificationStackScroller.getNotificationTopPadding();
1229            float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
1230            float t = (expandedHeight - panelHeightQsCollapsed)
1231                    / (panelHeightQsExpanded - panelHeightQsCollapsed);
1232
1233            setQsExpansion(mQsMinExpansionHeight
1234                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
1235        }
1236        mNotificationStackScroller.setStackHeight(expandedHeight);
1237        updateHeader();
1238        updateUnlockIcon();
1239        updateNotificationTranslucency();
1240    }
1241
1242    /**
1243     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
1244     *         collapsing QS / the panel when QS was scrolled
1245     */
1246    private int getTempQsMaxExpansion() {
1247        int qsTempMaxExpansion = mQsMaxExpansionHeight;
1248        if (mScrollYOverride != -1) {
1249            qsTempMaxExpansion -= mScrollYOverride;
1250        }
1251        return qsTempMaxExpansion;
1252    }
1253
1254    private int calculatePanelHeightShade() {
1255        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
1256        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
1257                - mTopPaddingAdjustment;
1258        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
1259        return maxHeight;
1260    }
1261
1262    private int calculatePanelHeightQsExpanded() {
1263        float notificationHeight = mNotificationStackScroller.getHeight()
1264                - mNotificationStackScroller.getEmptyBottomMargin()
1265                - mNotificationStackScroller.getTopPadding();
1266        float totalHeight = mQsMaxExpansionHeight + notificationHeight
1267                + mNotificationStackScroller.getNotificationTopPadding();
1268        if (totalHeight > mNotificationStackScroller.getHeight()) {
1269            float fullyCollapsedHeight = mQsMaxExpansionHeight
1270                    + mNotificationStackScroller.getMinStackHeight()
1271                    + mNotificationStackScroller.getNotificationTopPadding()
1272                    - getScrollViewScrollY();
1273            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
1274        }
1275        return (int) totalHeight;
1276    }
1277
1278    private int getScrollViewScrollY() {
1279        if (mScrollYOverride != -1) {
1280            return mScrollYOverride;
1281        } else {
1282            return mScrollView.getScrollY();
1283        }
1284    }
1285    private void updateNotificationTranslucency() {
1286        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
1287                / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
1288                        - mNotificationStackScroller.getCollapseSecondCardPadding());
1289        alpha = Math.max(0, Math.min(alpha, 1));
1290        alpha = (float) Math.pow(alpha, 0.75);
1291        if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
1292            mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
1293        } else if (alpha == 1f
1294                && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
1295            mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
1296        }
1297        mNotificationStackScroller.setAlpha(alpha);
1298    }
1299
1300    @Override
1301    protected float getOverExpansionAmount() {
1302        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
1303    }
1304
1305    @Override
1306    protected float getOverExpansionPixels() {
1307        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
1308    }
1309
1310    private void updateUnlockIcon() {
1311        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1312                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1313            boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
1314            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1315            if (active && !mUnlockIconActive && mTracking) {
1316                lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
1317                lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
1318                        mFastOutLinearInterpolator);
1319            } else if (!active && mUnlockIconActive && mTracking) {
1320                lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
1321                        150, mFastOutLinearInterpolator, null);
1322                lockIcon.setImageScale(1.0f, true, 150,
1323                        mFastOutLinearInterpolator);
1324            }
1325            mUnlockIconActive = active;
1326        }
1327    }
1328
1329    /**
1330     * Hides the header when notifications are colliding with it.
1331     */
1332    private void updateHeader() {
1333        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1334                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1335            updateHeaderKeyguard();
1336        } else {
1337            updateHeaderShade();
1338        }
1339
1340    }
1341
1342    private void updateHeaderShade() {
1343        if (!mHeaderAnimatingIn) {
1344            mHeader.setTranslationY(getHeaderTranslation());
1345        }
1346        setQsTranslation(mQsExpansionHeight);
1347    }
1348
1349    private float getHeaderTranslation() {
1350        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1351                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1352            return 0;
1353        }
1354        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1355            if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
1356                return 0;
1357            } else {
1358                return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
1359            }
1360        }
1361        return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
1362    }
1363
1364    private void updateHeaderKeyguard() {
1365        float alphaNotifications;
1366        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1367
1368            // When on Keyguard, we hide the header as soon as the top card of the notification
1369            // stack scroller is close enough (collision distance) to the bottom of the header.
1370            alphaNotifications = getNotificationsTopY()
1371                    /
1372                    (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
1373        } else {
1374
1375            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
1376            // soon as we start translating the stack.
1377            alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
1378        }
1379        alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1);
1380        alphaNotifications = (float) Math.pow(alphaNotifications, 0.75);
1381        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
1382        mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion));
1383        mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications));
1384        setQsTranslation(mQsExpansionHeight);
1385    }
1386
1387    private float getNotificationsTopY() {
1388        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1389            return getExpandedHeight();
1390        }
1391        return mNotificationStackScroller.getNotificationsTopY();
1392    }
1393
1394    @Override
1395    protected void onExpandingStarted() {
1396        super.onExpandingStarted();
1397        mNotificationStackScroller.onExpansionStarted();
1398        mIsExpanding = true;
1399        mQsExpandedWhenExpandingStarted = mQsExpanded;
1400        if (mQsExpanded) {
1401            onQsExpansionStarted();
1402        }
1403    }
1404
1405    @Override
1406    protected void onExpandingFinished() {
1407        super.onExpandingFinished();
1408        mNotificationStackScroller.onExpansionStopped();
1409        mIsExpanding = false;
1410        mScrollYOverride = -1;
1411        if (mExpandedHeight == 0f) {
1412            setListening(false);
1413        } else {
1414            setListening(true);
1415        }
1416        mTwoFingerQsExpand = false;
1417        mTwoFingerQsExpandPossible = false;
1418    }
1419
1420    private void setListening(boolean listening) {
1421        mHeader.setListening(listening);
1422        mKeyguardStatusBar.setListening(listening);
1423        mQsPanel.setListening(listening);
1424    }
1425
1426    @Override
1427    public void instantExpand() {
1428        super.instantExpand();
1429        setListening(true);
1430    }
1431
1432    @Override
1433    protected void setOverExpansion(float overExpansion, boolean isPixels) {
1434        if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) {
1435            return;
1436        }
1437        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1438            mNotificationStackScroller.setOnHeightChangedListener(null);
1439            if (isPixels) {
1440                mNotificationStackScroller.setOverScrolledPixels(
1441                        overExpansion, true /* onTop */, false /* animate */);
1442            } else {
1443                mNotificationStackScroller.setOverScrollAmount(
1444                        overExpansion, true /* onTop */, false /* animate */);
1445            }
1446            mNotificationStackScroller.setOnHeightChangedListener(this);
1447        }
1448    }
1449
1450    @Override
1451    protected void onTrackingStarted() {
1452        super.onTrackingStarted();
1453        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1454                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1455            mAfforanceHelper.animateHideLeftRightIcon();
1456        }
1457        if (mQsExpanded) {
1458            mTwoFingerQsExpand = true;
1459        }
1460    }
1461
1462    @Override
1463    protected void onTrackingStopped(boolean expand) {
1464        super.onTrackingStopped(expand);
1465        if (expand) {
1466            mNotificationStackScroller.setOverScrolledPixels(
1467                    0.0f, true /* onTop */, true /* animate */);
1468        }
1469        if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1470                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1471            if (!mHintAnimationRunning) {
1472                mAfforanceHelper.reset(true);
1473            }
1474        }
1475        if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1476                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1477            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1478            lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
1479            lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
1480        }
1481    }
1482
1483    @Override
1484    public void onHeightChanged(ExpandableView view) {
1485
1486        // Block update if we are in quick settings and just the top padding changed
1487        // (i.e. view == null).
1488        if (view == null && mQsExpanded) {
1489            return;
1490        }
1491        requestPanelHeightUpdate();
1492    }
1493
1494    @Override
1495    public void onReset(ExpandableView view) {
1496    }
1497
1498    @Override
1499    public void onScrollChanged() {
1500        if (mQsExpanded) {
1501            requestScrollerTopPaddingUpdate(false /* animate */);
1502            requestPanelHeightUpdate();
1503        }
1504    }
1505
1506    @Override
1507    protected void onConfigurationChanged(Configuration newConfig) {
1508        super.onConfigurationChanged(newConfig);
1509        mAfforanceHelper.onConfigurationChanged();
1510    }
1511
1512    @Override
1513    public void onClick(View v) {
1514        if (v == mHeader) {
1515            onQsExpansionStarted();
1516            if (mQsExpanded) {
1517                flingSettings(0 /* vel */, false /* expand */);
1518            } else if (mQsExpansionEnabled) {
1519                flingSettings(0 /* vel */, true /* expand */);
1520            }
1521        }
1522    }
1523
1524    @Override
1525    public void onAnimationToSideStarted(boolean rightPage) {
1526        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1527        mIsLaunchTransitionRunning = true;
1528        mLaunchAnimationEndRunnable = null;
1529        if (start) {
1530            mKeyguardBottomArea.launchPhone();
1531        } else {
1532            mSecureCameraLaunchManager.startSecureCameraLaunch();
1533        }
1534        mBlockTouches = true;
1535    }
1536
1537    @Override
1538    public void onAnimationToSideEnded() {
1539        mIsLaunchTransitionRunning = false;
1540        mIsLaunchTransitionFinished = true;
1541        if (mLaunchAnimationEndRunnable != null) {
1542            mLaunchAnimationEndRunnable.run();
1543            mLaunchAnimationEndRunnable = null;
1544        }
1545    }
1546
1547    @Override
1548    protected void onEdgeClicked(boolean right) {
1549        if ((right && getRightIcon().getVisibility() != View.VISIBLE)
1550                || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) {
1551            return;
1552        }
1553        mHintAnimationRunning = true;
1554        mAfforanceHelper.startHintAnimation(right, new Runnable() {
1555            @Override
1556            public void run() {
1557                mHintAnimationRunning = false;
1558                mStatusBar.onHintFinished();
1559            }
1560        });
1561        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
1562        if (start) {
1563            mStatusBar.onPhoneHintStarted();
1564        } else {
1565            mStatusBar.onCameraHintStarted();
1566        }
1567    }
1568
1569    @Override
1570    protected void startUnlockHintAnimation() {
1571        super.startUnlockHintAnimation();
1572        startHighlightIconAnimation(getCenterIcon());
1573    }
1574
1575    /**
1576     * Starts the highlight (making it fully opaque) animation on an icon.
1577     */
1578    private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1579        icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1580                mFastOutSlowInInterpolator, new Runnable() {
1581                    @Override
1582                    public void run() {
1583                        icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
1584                                true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1585                                mFastOutSlowInInterpolator, null);
1586                    }
1587                });
1588    }
1589
1590    @Override
1591    public float getPageWidth() {
1592        return getWidth();
1593    }
1594
1595    @Override
1596    public void onSwipingStarted() {
1597        mSecureCameraLaunchManager.onSwipingStarted();
1598        requestDisallowInterceptTouchEvent(true);
1599        mOnlyAffordanceInThisMotion = true;
1600    }
1601
1602    @Override
1603    public KeyguardAffordanceView getLeftIcon() {
1604        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1605                ? mKeyguardBottomArea.getCameraView()
1606                : mKeyguardBottomArea.getPhoneView();
1607    }
1608
1609    @Override
1610    public KeyguardAffordanceView getCenterIcon() {
1611        return mKeyguardBottomArea.getLockIcon();
1612    }
1613
1614    @Override
1615    public KeyguardAffordanceView getRightIcon() {
1616        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1617                ? mKeyguardBottomArea.getPhoneView()
1618                : mKeyguardBottomArea.getCameraView();
1619    }
1620
1621    @Override
1622    public View getLeftPreview() {
1623        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1624                ? mKeyguardBottomArea.getCameraPreview()
1625                : mKeyguardBottomArea.getPhonePreview();
1626    }
1627
1628    @Override
1629    public View getRightPreview() {
1630        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1631                ? mKeyguardBottomArea.getPhonePreview()
1632                : mKeyguardBottomArea.getCameraPreview();
1633    }
1634
1635    @Override
1636    protected float getPeekHeight() {
1637        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1638            return mNotificationStackScroller.getPeekHeight();
1639        } else {
1640            return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
1641        }
1642    }
1643
1644    @Override
1645    protected float getCannedFlingDurationFactor() {
1646        if (mQsExpanded) {
1647            return 0.7f;
1648        } else {
1649            return 0.6f;
1650        }
1651    }
1652
1653    @Override
1654    protected boolean fullyExpandedClearAllVisible() {
1655        return mNotificationStackScroller.isDismissViewNotGone()
1656                && mNotificationStackScroller.isScrolledToBottom() && !mTwoFingerQsExpand;
1657    }
1658
1659    @Override
1660    protected boolean isClearAllVisible() {
1661        return mNotificationStackScroller.isDismissViewVisible();
1662    }
1663
1664    @Override
1665    protected int getClearAllHeight() {
1666        return mNotificationStackScroller.getDismissViewHeight();
1667    }
1668
1669    @Override
1670    protected boolean isTrackingBlocked() {
1671        return mConflictingQsExpansionGesture && mQsExpanded;
1672    }
1673
1674    public void notifyVisibleChildrenChanged() {
1675        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
1676            mReserveNotificationSpace.setVisibility(View.VISIBLE);
1677        } else {
1678            mReserveNotificationSpace.setVisibility(View.GONE);
1679        }
1680    }
1681
1682    public boolean isQsExpanded() {
1683        return mQsExpanded;
1684    }
1685
1686    public boolean isQsDetailShowing() {
1687        return mQsPanel.isShowingDetail();
1688    }
1689
1690    public void closeQsDetail() {
1691        mQsPanel.closeDetail();
1692    }
1693
1694    @Override
1695    public boolean shouldDelayChildPressedState() {
1696        return true;
1697    }
1698
1699    public boolean isLaunchTransitionFinished() {
1700        return mIsLaunchTransitionFinished;
1701    }
1702
1703    public boolean isLaunchTransitionRunning() {
1704        return mIsLaunchTransitionRunning;
1705    }
1706
1707    public void setLaunchTransitionEndRunnable(Runnable r) {
1708        mLaunchAnimationEndRunnable = r;
1709    }
1710
1711    public void setEmptyDragAmount(float amount) {
1712        float factor = 0.8f;
1713        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1714            factor = 0.4f;
1715        } else if (!mStatusBar.hasActiveNotifications()) {
1716            factor = 0.4f;
1717        }
1718        mEmptyDragAmount = amount * factor;
1719        positionClockAndNotifications();
1720    }
1721
1722    private static float interpolate(float t, float start, float end) {
1723        return (1 - t) * start + t * end;
1724    }
1725
1726    private void updateKeyguardStatusBarVisibility() {
1727        mKeyguardStatusBar.setVisibility(mKeyguardShowing && !mDozing ? VISIBLE : INVISIBLE);
1728    }
1729
1730    public void setDozing(boolean dozing) {
1731        if (dozing == mDozing) return;
1732        mDozing = dozing;
1733        if (mDozing) {
1734            setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0xff, false /*animate*/);
1735        } else {
1736            setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0, true /*animate*/);
1737        }
1738        updateKeyguardStatusBarVisibility();
1739    }
1740
1741    public boolean isDozing() {
1742        return mDozing;
1743    }
1744
1745    private static void setBackgroundColorAlpha(final View target, int rgb, int targetAlpha,
1746            boolean animate) {
1747        int currentAlpha = getBackgroundAlpha(target);
1748        if (currentAlpha == targetAlpha) {
1749            return;
1750        }
1751        final int r = Color.red(rgb);
1752        final int g = Color.green(rgb);
1753        final int b = Color.blue(rgb);
1754        Object runningAnim = target.getTag(TAG_KEY_ANIM);
1755        if (runningAnim instanceof ValueAnimator) {
1756            ((ValueAnimator) runningAnim).cancel();
1757        }
1758        if (!animate) {
1759            target.setBackgroundColor(Color.argb(targetAlpha, r, g, b));
1760            return;
1761        }
1762        ValueAnimator anim = ValueAnimator.ofInt(currentAlpha, targetAlpha);
1763        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1764            @Override
1765            public void onAnimationUpdate(ValueAnimator animation) {
1766                int value = (int) animation.getAnimatedValue();
1767                target.setBackgroundColor(Color.argb(value, r, g, b));
1768            }
1769        });
1770        anim.setDuration(DOZE_BACKGROUND_ANIM_DURATION);
1771        anim.addListener(new AnimatorListenerAdapter() {
1772            @Override
1773            public void onAnimationEnd(Animator animation) {
1774                target.setTag(TAG_KEY_ANIM, null);
1775            }
1776        });
1777        anim.start();
1778        target.setTag(TAG_KEY_ANIM, anim);
1779    }
1780
1781    private static int getBackgroundAlpha(View view) {
1782        if (view.getBackground() instanceof ColorDrawable) {
1783            ColorDrawable drawable = (ColorDrawable) view.getBackground();
1784            return Color.alpha(drawable.getColor());
1785        } else {
1786            return 0;
1787        }
1788    }
1789
1790    public void setShadeEmpty(boolean shadeEmpty) {
1791        mShadeEmpty = shadeEmpty;
1792        updateEmptyShadeView();
1793    }
1794
1795    private void updateEmptyShadeView() {
1796
1797        // Hide "No notifications" in QS.
1798        mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
1799    }
1800
1801    public void setQsScrimEnabled(boolean qsScrimEnabled) {
1802        boolean changed = mQsScrimEnabled != qsScrimEnabled;
1803        mQsScrimEnabled = qsScrimEnabled;
1804        if (changed) {
1805            updateQsState();
1806        }
1807    }
1808
1809    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
1810        mKeyguardUserSwitcher = keyguardUserSwitcher;
1811    }
1812
1813    private final Runnable mUpdateHeader = new Runnable() {
1814        @Override
1815        public void run() {
1816            mHeader.updateEverything();
1817        }
1818    };
1819
1820    public void onScreenTurnedOn() {
1821        mKeyguardStatusView.refreshTime();
1822    }
1823}
1824