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