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