NotificationPanelView.java revision 90e0df291f0be8f84cad3b1a8834a60ddf7d352e
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.ValueAnimator;
23import android.content.Context;
24import android.content.res.Configuration;
25import android.util.AttributeSet;
26import android.view.MotionEvent;
27import android.view.VelocityTracker;
28import android.view.View;
29import android.view.ViewGroup;
30import android.view.ViewTreeObserver;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.animation.AnimationUtils;
33import android.view.animation.Interpolator;
34import android.widget.LinearLayout;
35import android.widget.TextView;
36
37import com.android.systemui.R;
38import com.android.systemui.qs.QSPanel;
39import com.android.systemui.statusbar.ExpandableView;
40import com.android.systemui.statusbar.FlingAnimationUtils;
41import com.android.systemui.statusbar.GestureRecorder;
42import com.android.systemui.statusbar.KeyguardAffordanceView;
43import com.android.systemui.statusbar.MirrorView;
44import com.android.systemui.statusbar.StatusBarState;
45import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
46import com.android.systemui.statusbar.stack.StackStateAnimator;
47
48public class NotificationPanelView extends PanelView implements
49        ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
50        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
51        KeyguardAffordanceHelper.Callback {
52
53    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
54    // changed.
55    private static final int CAP_HEIGHT = 1456;
56    private static final int FONT_HEIGHT = 2163;
57
58    private static final float HEADER_RUBBERBAND_FACTOR = 2.15f;
59    private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
60
61    private KeyguardAffordanceHelper mAfforanceHelper;
62    private StatusBarHeaderView mHeader;
63    private View mQsContainer;
64    private QSPanel mQsPanel;
65    private View mKeyguardStatusView;
66    private ObservableScrollView mScrollView;
67    private TextView mClockView;
68    private View mReserveNotificationSpace;
69    private MirrorView mSystemIconsCopy;
70
71    private NotificationStackScrollLayout mNotificationStackScroller;
72    private int mNotificationTopPadding;
73    private boolean mAnimateNextTopPaddingChange;
74
75    private int mTrackingPointer;
76    private VelocityTracker mVelocityTracker;
77    private boolean mQsTracking;
78
79    /**
80     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
81     * the expansion for quick settings.
82     */
83    private boolean mConflictingQsExpansionGesture;
84
85    /**
86     * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
87     * intercepted yet.
88     */
89    private boolean mIntercepting;
90    private boolean mQsExpanded;
91    private boolean mQsExpandedWhenExpandingStarted;
92    private boolean mQsFullyExpanded;
93    private boolean mKeyguardShowing;
94    private int mStatusBarState;
95    private float mInitialHeightOnTouch;
96    private float mInitialTouchX;
97    private float mInitialTouchY;
98    private float mLastTouchX;
99    private float mLastTouchY;
100    private float mQsExpansionHeight;
101    private int mQsMinExpansionHeight;
102    private int mQsMaxExpansionHeight;
103    private int mQsPeekHeight;
104    private boolean mStackScrollerOverscrolling;
105    private boolean mQsExpansionFromOverscroll;
106    private boolean mQsExpansionEnabled = true;
107    private ValueAnimator mQsExpansionAnimator;
108    private FlingAnimationUtils mFlingAnimationUtils;
109    private int mStatusBarMinHeight;
110    private boolean mUnlockIconActive;
111    private int mNotificationsHeaderCollideDistance;
112    private int mUnlockMoveDistance;
113    private float mEmptyDragAmount;
114
115    private Interpolator mFastOutSlowInInterpolator;
116    private Interpolator mFastOutLinearInterpolator;
117    private ObjectAnimator mClockAnimator;
118    private int mClockAnimationTarget = -1;
119    private int mTopPaddingAdjustment;
120    private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
121            new KeyguardClockPositionAlgorithm();
122    private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
123            new KeyguardClockPositionAlgorithm.Result();
124    private boolean mIsExpanding;
125
126    private boolean mBlockTouches;
127    private int mNotificationScrimWaitDistance;
128    private boolean mTwoFingerQsExpand;
129    private boolean mTwoFingerQsExpandPossible;
130
131    /**
132     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
133     * need to take this into account in our panel height calculation.
134     */
135    private int mScrollYOverride = -1;
136    private boolean mQsAnimatorExpand;
137    private boolean mIsLaunchTransitionFinished;
138    private boolean mIsLaunchTransitionRunning;
139    private Runnable mLaunchAnimationEndRunnable;
140    private boolean mOnlyAffordanceInThisMotion;
141
142    public NotificationPanelView(Context context, AttributeSet attrs) {
143        super(context, attrs);
144        mSystemIconsCopy = new MirrorView(context);
145    }
146
147    public void setStatusBar(PhoneStatusBar bar) {
148        mStatusBar = bar;
149    }
150
151    @Override
152    protected void onFinishInflate() {
153        super.onFinishInflate();
154        mHeader = (StatusBarHeaderView) findViewById(R.id.header);
155        mHeader.setOnClickListener(this);
156        mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
157        mQsContainer = findViewById(R.id.quick_settings_container);
158        mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
159        mClockView = (TextView) findViewById(R.id.clock_view);
160        mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
161        mScrollView.setListener(this);
162        mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
163        mNotificationStackScroller = (NotificationStackScrollLayout)
164                findViewById(R.id.notification_stack_scroller);
165        mNotificationStackScroller.setOnHeightChangedListener(this);
166        mNotificationStackScroller.setOverscrollTopChangedListener(this);
167        mNotificationStackScroller.setScrollView(mScrollView);
168        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
169                android.R.interpolator.fast_out_slow_in);
170        mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
171                android.R.interpolator.fast_out_linear_in);
172        mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
173        mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
174    }
175
176    @Override
177    protected void loadDimens() {
178        super.loadDimens();
179        mNotificationTopPadding = getResources().getDimensionPixelSize(
180                R.dimen.notifications_top_padding);
181        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
182        mStatusBarMinHeight = getResources().getDimensionPixelSize(
183                com.android.internal.R.dimen.status_bar_height);
184        mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
185        mNotificationsHeaderCollideDistance =
186                getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
187        mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
188        mClockPositionAlgorithm.loadDimens(getResources());
189        mNotificationScrimWaitDistance =
190                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
191    }
192
193    @Override
194    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
195        super.onLayout(changed, left, top, right, bottom);
196
197        // Update Clock Pivot
198        mKeyguardStatusView.setPivotX(getWidth() / 2);
199        mKeyguardStatusView.setPivotY(
200                (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
201
202        // Calculate quick setting heights.
203        mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
204        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
205        if (mQsExpanded) {
206            if (mQsFullyExpanded) {
207                mQsExpansionHeight = mQsMaxExpansionHeight;
208                requestScrollerTopPaddingUpdate(false /* animate */);
209            }
210        } else {
211            if (!mStackScrollerOverscrolling) {
212                setQsExpansion(mQsMinExpansionHeight);
213            }
214            positionClockAndNotifications();
215            mNotificationStackScroller.setStackHeight(getExpandedHeight());
216        }
217    }
218
219    /**
220     * Positions the clock and notifications dynamically depending on how many notifications are
221     * showing.
222     */
223    private void positionClockAndNotifications() {
224        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
225        int stackScrollerPadding;
226        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
227            int bottom = mHeader.getCollapsedHeight();
228            stackScrollerPadding = bottom + mQsPeekHeight
229                    + mNotificationTopPadding;
230            mTopPaddingAdjustment = 0;
231        } else {
232            mClockPositionAlgorithm.setup(
233                    mStatusBar.getMaxKeyguardNotifications(),
234                    getMaxPanelHeight(),
235                    getExpandedHeight(),
236                    mNotificationStackScroller.getNotGoneChildCount(),
237                    getHeight(),
238                    mKeyguardStatusView.getHeight(),
239                    mEmptyDragAmount);
240            mClockPositionAlgorithm.run(mClockPositionResult);
241            if (animate || mClockAnimator != null) {
242                startClockAnimation(mClockPositionResult.clockY);
243            } else {
244                mKeyguardStatusView.setY(mClockPositionResult.clockY);
245            }
246            updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
247            stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
248            mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
249        }
250        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
251        requestScrollerTopPaddingUpdate(animate);
252    }
253
254    private void startClockAnimation(int y) {
255        if (mClockAnimationTarget == y) {
256            return;
257        }
258        mClockAnimationTarget = y;
259        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
260            @Override
261            public boolean onPreDraw() {
262                getViewTreeObserver().removeOnPreDrawListener(this);
263                if (mClockAnimator != null) {
264                    mClockAnimator.removeAllListeners();
265                    mClockAnimator.cancel();
266                }
267                mClockAnimator = ObjectAnimator
268                        .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
269                mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
270                mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
271                mClockAnimator.addListener(new AnimatorListenerAdapter() {
272                    @Override
273                    public void onAnimationEnd(Animator animation) {
274                        mClockAnimator = null;
275                        mClockAnimationTarget = -1;
276                    }
277                });
278                mClockAnimator.start();
279                return true;
280            }
281        });
282    }
283
284    private void updateClock(float alpha, float scale) {
285        mKeyguardStatusView.setAlpha(alpha);
286        mKeyguardStatusView.setScaleX(scale);
287        mKeyguardStatusView.setScaleY(scale);
288    }
289
290    public void animateToFullShade() {
291        mAnimateNextTopPaddingChange = true;
292        mNotificationStackScroller.goToFullShade();
293        requestLayout();
294    }
295
296    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
297        mQsExpansionEnabled = qsExpansionEnabled;
298    }
299
300    @Override
301    public void resetViews() {
302        mIsLaunchTransitionFinished = false;
303        mBlockTouches = false;
304        mUnlockIconActive = false;
305        mAfforanceHelper.reset(true);
306        closeQs();
307        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
308                true /* cancelAnimators */);
309    }
310
311    public void closeQs() {
312        cancelAnimation();
313        setQsExpansion(mQsMinExpansionHeight);
314    }
315
316    public void animateCloseQs() {
317        if (mQsExpansionAnimator != null) {
318            if (!mQsAnimatorExpand) {
319                return;
320            }
321            float height = mQsExpansionHeight;
322            mQsExpansionAnimator.cancel();
323            setQsExpansion(height);
324        }
325        flingSettings(0 /* vel */, false);
326    }
327
328    public void openQs() {
329        cancelAnimation();
330        if (mQsExpansionEnabled) {
331            setQsExpansion(mQsMaxExpansionHeight);
332        }
333    }
334
335    @Override
336    public void fling(float vel, boolean always) {
337        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
338        if (gr != null) {
339            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
340        }
341        super.fling(vel, always);
342    }
343
344    @Override
345    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
346        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
347            event.getText()
348                    .add(getContext().getString(R.string.accessibility_desc_notification_shade));
349            return true;
350        }
351
352        return super.dispatchPopulateAccessibilityEvent(event);
353    }
354
355    @Override
356    public boolean onInterceptTouchEvent(MotionEvent event) {
357        if (mBlockTouches) {
358            return false;
359        }
360        resetDownStates(event);
361        int pointerIndex = event.findPointerIndex(mTrackingPointer);
362        if (pointerIndex < 0) {
363            pointerIndex = 0;
364            mTrackingPointer = event.getPointerId(pointerIndex);
365        }
366        final float x = event.getX(pointerIndex);
367        final float y = event.getY(pointerIndex);
368
369        switch (event.getActionMasked()) {
370            case MotionEvent.ACTION_DOWN:
371                mIntercepting = true;
372                mInitialTouchY = y;
373                mInitialTouchX = x;
374                initVelocityTracker();
375                trackMovement(event);
376                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
377                    getParent().requestDisallowInterceptTouchEvent(true);
378                }
379                if (mQsExpansionAnimator != null) {
380                    onQsExpansionStarted();
381                    mInitialHeightOnTouch = mQsExpansionHeight;
382                    mQsTracking = true;
383                    mIntercepting = false;
384                    mNotificationStackScroller.removeLongPressCallback();
385                }
386                break;
387            case MotionEvent.ACTION_POINTER_UP:
388                final int upPointer = event.getPointerId(event.getActionIndex());
389                if (mTrackingPointer == upPointer) {
390                    // gesture is ongoing, find a new pointer to track
391                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
392                    mTrackingPointer = event.getPointerId(newIndex);
393                    mInitialTouchX = event.getX(newIndex);
394                    mInitialTouchY = event.getY(newIndex);
395                }
396                break;
397
398            case MotionEvent.ACTION_MOVE:
399                final float h = y - mInitialTouchY;
400                trackMovement(event);
401                if (mQsTracking) {
402
403                    // Already tracking because onOverscrolled was called. We need to update here
404                    // so we don't stop for a frame until the next touch event gets handled in
405                    // onTouchEvent.
406                    setQsExpansion(h + mInitialHeightOnTouch);
407                    trackMovement(event);
408                    mIntercepting = false;
409                    return true;
410                }
411                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
412                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
413                    onQsExpansionStarted();
414                    mInitialHeightOnTouch = mQsExpansionHeight;
415                    mInitialTouchY = y;
416                    mInitialTouchX = x;
417                    mQsTracking = true;
418                    mIntercepting = false;
419                    mNotificationStackScroller.removeLongPressCallback();
420                    return true;
421                }
422                break;
423
424            case MotionEvent.ACTION_CANCEL:
425            case MotionEvent.ACTION_UP:
426                trackMovement(event);
427                if (mQsTracking) {
428                    flingQsWithCurrentVelocity();
429                    mQsTracking = false;
430                }
431                mIntercepting = false;
432                break;
433        }
434        return !mQsExpanded && super.onInterceptTouchEvent(event);
435    }
436
437    private void resetDownStates(MotionEvent event) {
438        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
439            mOnlyAffordanceInThisMotion = false;
440        }
441    }
442
443    @Override
444    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
445
446        // Block request when interacting with the scroll view so we can still intercept the
447        // scrolling when QS is expanded.
448        if (mScrollView.isHandlingTouchEvent()) {
449            return;
450        }
451        super.requestDisallowInterceptTouchEvent(disallowIntercept);
452    }
453
454    private void flingQsWithCurrentVelocity() {
455        float vel = getCurrentVelocity();
456        flingSettings(vel, flingExpandsQs(vel));
457    }
458
459    private boolean flingExpandsQs(float vel) {
460        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
461            return getQsExpansionFraction() > 0.5f;
462        } else {
463            return vel > 0;
464        }
465    }
466
467    private float getQsExpansionFraction() {
468        return (mQsExpansionHeight - mQsMinExpansionHeight)
469                / (getTempQsMaxExpansion() - mQsMinExpansionHeight);
470    }
471
472    @Override
473    public boolean onTouchEvent(MotionEvent event) {
474        if (mBlockTouches) {
475            return false;
476        }
477        resetDownStates(event);
478        if ((!mIsExpanding || mHintAnimationRunning)
479                && !mQsExpanded
480                && mStatusBar.getBarState() != StatusBarState.SHADE) {
481            mAfforanceHelper.onTouchEvent(event);
482        }
483        if (mOnlyAffordanceInThisMotion) {
484            return true;
485        }
486        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
487                && mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
488
489            // Down in the empty area while fully expanded - go to QS.
490            mQsTracking = true;
491            mConflictingQsExpansionGesture = true;
492            onQsExpansionStarted();
493            mInitialHeightOnTouch = mQsExpansionHeight;
494            mInitialTouchY = event.getX();
495            mInitialTouchX = event.getY();
496        }
497        if (mExpandedHeight != 0) {
498            handleQsDown(event);
499        }
500        if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) {
501            onQsTouch(event);
502            if (!mConflictingQsExpansionGesture) {
503                return true;
504            }
505        }
506        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
507                || event.getActionMasked() == MotionEvent.ACTION_UP) {
508            mConflictingQsExpansionGesture = false;
509        }
510        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) {
511            mTwoFingerQsExpandPossible = true;
512        }
513        if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
514                && event.getPointerCount() == 2
515                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
516            mTwoFingerQsExpand = true;
517            requestPanelHeightUpdate();
518        }
519        super.onTouchEvent(event);
520        return true;
521    }
522
523    private void handleQsDown(MotionEvent event) {
524        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
525                && shouldQuickSettingsIntercept(event.getX(), event.getY(), 0)) {
526            mQsTracking = true;
527            onQsExpansionStarted();
528            mInitialHeightOnTouch = mQsExpansionHeight;
529            mInitialTouchY = event.getX();
530            mInitialTouchX = event.getY();
531        }
532    }
533
534    @Override
535    protected boolean flingExpands(float vel, float vectorVel) {
536        boolean expands = super.flingExpands(vel, vectorVel);
537
538        // If we are already running a QS expansion, make sure that we keep the panel open.
539        if (mQsExpansionAnimator != null) {
540            expands = true;
541        }
542        return expands;
543    }
544
545    @Override
546    protected boolean hasConflictingGestures() {
547        return mStatusBar.getBarState() != StatusBarState.SHADE;
548    }
549
550    private void onQsTouch(MotionEvent event) {
551        int pointerIndex = event.findPointerIndex(mTrackingPointer);
552        if (pointerIndex < 0) {
553            pointerIndex = 0;
554            mTrackingPointer = event.getPointerId(pointerIndex);
555        }
556        final float y = event.getY(pointerIndex);
557        final float x = event.getX(pointerIndex);
558
559        switch (event.getActionMasked()) {
560            case MotionEvent.ACTION_DOWN:
561                mQsTracking = true;
562                mInitialTouchY = y;
563                mInitialTouchX = x;
564                onQsExpansionStarted();
565                mInitialHeightOnTouch = mQsExpansionHeight;
566                initVelocityTracker();
567                trackMovement(event);
568                break;
569
570            case MotionEvent.ACTION_POINTER_UP:
571                final int upPointer = event.getPointerId(event.getActionIndex());
572                if (mTrackingPointer == upPointer) {
573                    // gesture is ongoing, find a new pointer to track
574                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
575                    final float newY = event.getY(newIndex);
576                    final float newX = event.getX(newIndex);
577                    mTrackingPointer = event.getPointerId(newIndex);
578                    mInitialHeightOnTouch = mQsExpansionHeight;
579                    mInitialTouchY = newY;
580                    mInitialTouchX = newX;
581                }
582                break;
583
584            case MotionEvent.ACTION_MOVE:
585                final float h = y - mInitialTouchY;
586                setQsExpansion(h + mInitialHeightOnTouch);
587                trackMovement(event);
588                break;
589
590            case MotionEvent.ACTION_UP:
591            case MotionEvent.ACTION_CANCEL:
592                mQsTracking = false;
593                mTrackingPointer = -1;
594                trackMovement(event);
595                float fraction = getQsExpansionFraction();
596                if ((fraction != 0f || y >= mInitialTouchY)
597                        && (fraction != 1f || y <= mInitialTouchY)) {
598                    flingQsWithCurrentVelocity();
599                } else {
600                    mScrollYOverride = -1;
601                }
602                if (mVelocityTracker != null) {
603                    mVelocityTracker.recycle();
604                    mVelocityTracker = null;
605                }
606                break;
607        }
608    }
609
610    @Override
611    public void onOverscrolled(int amount) {
612        if (mIntercepting) {
613            onQsExpansionStarted(amount);
614            mInitialHeightOnTouch = mQsExpansionHeight;
615            mInitialTouchY = mLastTouchY;
616            mInitialTouchX = mLastTouchX;
617            mQsTracking = true;
618        }
619    }
620
621    @Override
622    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
623        cancelAnimation();
624        float rounded = amount >= 1f ? amount : 0f;
625        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
626        mQsExpansionFromOverscroll = rounded != 0f;
627        updateQsState();
628        setQsExpansion(mQsMinExpansionHeight + rounded);
629    }
630
631    @Override
632    public void flingTopOverscroll(float velocity, boolean open) {
633        setQsExpansion(mQsExpansionHeight);
634        flingSettings(velocity, open, new Runnable() {
635            @Override
636            public void run() {
637                mStackScrollerOverscrolling = false;
638                mQsExpansionFromOverscroll = false;
639                updateQsState();
640            }
641        });
642    }
643
644    private void onQsExpansionStarted() {
645        onQsExpansionStarted(0);
646    }
647
648    private void onQsExpansionStarted(int overscrollAmount) {
649        cancelAnimation();
650
651        // Reset scroll position and apply that position to the expanded height.
652        float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
653        if (mScrollView.getScrollY() != 0) {
654            mScrollYOverride = mScrollView.getScrollY();
655        }
656        mScrollView.scrollTo(0, 0);
657        setQsExpansion(height);
658    }
659
660    private void setQsExpanded(boolean expanded) {
661        boolean changed = mQsExpanded != expanded;
662        if (changed) {
663            mQsExpanded = expanded;
664            updateQsState();
665            requestPanelHeightUpdate();
666            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
667            mStatusBar.setQsExpanded(expanded);
668        }
669    }
670
671    public void setBarState(int statusBarState) {
672        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
673                || statusBarState == StatusBarState.SHADE_LOCKED;
674        if (!mKeyguardShowing && keyguardShowing) {
675            setQsTranslation(mQsExpansionHeight);
676            mHeader.setTranslationY(0f);
677        }
678        mStatusBarState = statusBarState;
679        mKeyguardShowing = keyguardShowing;
680        updateQsState();
681    }
682
683    private void updateQsState() {
684        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
685        mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling);
686        mNotificationStackScroller.setScrollingEnabled(mStatusBarState != StatusBarState.KEYGUARD
687                && (!mQsExpanded || mQsExpansionFromOverscroll));
688        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
689        mQsContainer.setVisibility(
690                mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
691        mScrollView.setTouchEnabled(mQsExpanded);
692    }
693
694    private void setQsExpansion(float height) {
695        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
696        mQsFullyExpanded = height == mQsMaxExpansionHeight;
697        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
698            setQsExpanded(true);
699        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
700            setQsExpanded(false);
701        }
702        mQsExpansionHeight = height;
703        mHeader.setExpansion(getQsExpansionFraction());
704        setQsTranslation(height);
705        requestScrollerTopPaddingUpdate(false /* animate */);
706        updateNotificationScrim(height);
707    }
708
709    private void updateNotificationScrim(float height) {
710        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
711        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
712        progress = Math.max(0.0f, Math.min(progress, 1.0f));
713        mNotificationStackScroller.setScrimAlpha(progress);
714    }
715
716    private void setQsTranslation(float height) {
717        mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
718    }
719
720    private void requestScrollerTopPaddingUpdate(boolean animate) {
721        mNotificationStackScroller.updateTopPadding(mQsExpansionHeight,
722                mScrollView.getScrollY(),
723                mAnimateNextTopPaddingChange || animate);
724        mAnimateNextTopPaddingChange = false;
725    }
726
727    private void trackMovement(MotionEvent event) {
728        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
729        mLastTouchX = event.getX();
730        mLastTouchY = event.getY();
731    }
732
733    private void initVelocityTracker() {
734        if (mVelocityTracker != null) {
735            mVelocityTracker.recycle();
736        }
737        mVelocityTracker = VelocityTracker.obtain();
738    }
739
740    private float getCurrentVelocity() {
741        if (mVelocityTracker == null) {
742            return 0;
743        }
744        mVelocityTracker.computeCurrentVelocity(1000);
745        return mVelocityTracker.getYVelocity();
746    }
747
748    private void cancelAnimation() {
749        if (mQsExpansionAnimator != null) {
750            mQsExpansionAnimator.cancel();
751        }
752    }
753
754    private void flingSettings(float vel, boolean expand) {
755        flingSettings(vel, expand, null);
756    }
757
758    private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
759        float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
760        if (target == mQsExpansionHeight) {
761            mScrollYOverride = -1;
762            if (onFinishRunnable != null) {
763                onFinishRunnable.run();
764            }
765            return;
766        }
767        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
768        mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
769        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
770            @Override
771            public void onAnimationUpdate(ValueAnimator animation) {
772                setQsExpansion((Float) animation.getAnimatedValue());
773            }
774        });
775        animator.addListener(new AnimatorListenerAdapter() {
776            @Override
777            public void onAnimationEnd(Animator animation) {
778                mScrollYOverride = -1;
779                mQsExpansionAnimator = null;
780                if (onFinishRunnable != null) {
781                    onFinishRunnable.run();
782                }
783            }
784        });
785        animator.start();
786        mQsExpansionAnimator = animator;
787        mQsAnimatorExpand = expand;
788    }
789
790    /**
791     * @return Whether we should intercept a gesture to open Quick Settings.
792     */
793    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
794        if (!mQsExpansionEnabled) {
795            return false;
796        }
797        boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
798                && y >= mHeader.getTop() && y <= mHeader.getBottom();
799        if (mQsExpanded) {
800            return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
801        } else {
802            return onHeader;
803        }
804    }
805
806    @Override
807    public void setVisibility(int visibility) {
808        int oldVisibility = getVisibility();
809        super.setVisibility(visibility);
810        if (visibility != oldVisibility) {
811            reparentStatusIcons(visibility == VISIBLE);
812        }
813    }
814
815    /**
816     * When the notification panel gets expanded, we need to move the status icons in the header
817     * card.
818     */
819    private void reparentStatusIcons(boolean toHeader) {
820        if (mStatusBar == null) {
821            return;
822        }
823        LinearLayout systemIcons = mStatusBar.getSystemIcons();
824        ViewGroup parent = ((ViewGroup) systemIcons.getParent());
825        if (toHeader) {
826            int index = parent.indexOfChild(systemIcons);
827            parent.removeView(systemIcons);
828            mSystemIconsCopy.setMirroredView(
829                    systemIcons, systemIcons.getWidth(), systemIcons.getHeight());
830            parent.addView(mSystemIconsCopy, index);
831            mHeader.attachSystemIcons(systemIcons);
832        } else {
833            ViewGroup newParent = mStatusBar.getSystemIconArea();
834            int index = newParent.indexOfChild(mSystemIconsCopy);
835            parent.removeView(systemIcons);
836            mHeader.onSystemIconsDetached();
837            mSystemIconsCopy.setMirroredView(null, 0, 0);
838            newParent.removeView(mSystemIconsCopy);
839            newParent.addView(systemIcons, index);
840        }
841    }
842
843    @Override
844    protected boolean isScrolledToBottom() {
845        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
846            return true;
847        }
848        if (!isInSettings()) {
849            return mNotificationStackScroller.isScrolledToBottom();
850        }
851        return super.isScrolledToBottom();
852    }
853
854    @Override
855    protected int getMaxPanelHeight() {
856        int min = mStatusBarMinHeight;
857        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
858                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
859            int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
860                    * HEADER_RUBBERBAND_FACTOR);
861            min = Math.max(min, minHeight);
862        }
863        int maxHeight;
864        if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
865            maxHeight = (int) calculatePanelHeightQsExpanded();
866        } else {
867            int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
868            maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
869                    - mTopPaddingAdjustment;
870            maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
871        }
872        maxHeight = Math.max(maxHeight, min);
873        return maxHeight;
874    }
875
876    private boolean isInSettings() {
877        return mQsExpanded;
878    }
879
880    @Override
881    protected void onHeightUpdated(float expandedHeight) {
882        if (!mQsExpanded) {
883            positionClockAndNotifications();
884        }
885        if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
886                && !mQsExpansionFromOverscroll) {
887            float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
888                    + mNotificationStackScroller.getMinStackHeight()
889                    + mNotificationStackScroller.getNotificationTopPadding();
890            float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
891            float t = (expandedHeight - panelHeightQsCollapsed)
892                    / (panelHeightQsExpanded - panelHeightQsCollapsed);
893
894            setQsExpansion(mQsMinExpansionHeight
895                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
896        }
897        mNotificationStackScroller.setStackHeight(expandedHeight);
898        updateHeader();
899        updateUnlockIcon();
900        updateNotificationTranslucency();
901    }
902
903    /**
904     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
905     *         collapsing QS / the panel when QS was scrolled
906     */
907    private int getTempQsMaxExpansion() {
908        int qsTempMaxExpansion = mQsMaxExpansionHeight;
909        if (mScrollYOverride != -1) {
910            qsTempMaxExpansion -= mScrollYOverride;
911        }
912        return qsTempMaxExpansion;
913    }
914
915    private float calculatePanelHeightQsExpanded() {
916        float notificationHeight = mNotificationStackScroller.getHeight()
917                - mNotificationStackScroller.getEmptyBottomMargin()
918                - mNotificationStackScroller.getTopPadding();
919        float totalHeight = mQsMaxExpansionHeight + notificationHeight
920                + mNotificationStackScroller.getNotificationTopPadding();
921        if (totalHeight > mNotificationStackScroller.getHeight()) {
922            float fullyCollapsedHeight = mQsMaxExpansionHeight
923                    + mNotificationStackScroller.getMinStackHeight()
924                    + mNotificationStackScroller.getNotificationTopPadding()
925                    - getScrollViewScrollY();
926            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
927        }
928        return totalHeight;
929    }
930
931    private int getScrollViewScrollY() {
932        if (mScrollYOverride != -1) {
933            return mScrollYOverride;
934        } else {
935            return mScrollView.getScrollY();
936        }
937    }
938    private void updateNotificationTranslucency() {
939        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
940                / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
941                        + mNotificationStackScroller.getCollapseSecondCardPadding());
942        alpha = Math.max(0, Math.min(alpha, 1));
943        alpha = (float) Math.pow(alpha, 0.75);
944        if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
945            mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
946        } else if (alpha == 1f
947                && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
948            mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
949        }
950        mNotificationStackScroller.setAlpha(alpha);
951    }
952
953    @Override
954    protected float getOverExpansionAmount() {
955        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
956    }
957
958    @Override
959    protected float getOverExpansionPixels() {
960        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
961    }
962
963    private void updateUnlockIcon() {
964        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
965                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
966            boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
967            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
968            if (active && !mUnlockIconActive && mTracking) {
969                lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
970                lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
971                        mFastOutLinearInterpolator);
972            } else if (!active && mUnlockIconActive && mTracking) {
973                lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
974                        150, mFastOutLinearInterpolator, null);
975                lockIcon.setImageScale(1.0f, true, 150,
976                        mFastOutLinearInterpolator);
977            }
978            mUnlockIconActive = active;
979        }
980    }
981
982    /**
983     * Hides the header when notifications are colliding with it.
984     */
985    private void updateHeader() {
986        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
987                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
988            updateHeaderKeyguard();
989        } else {
990            updateHeaderShade();
991        }
992
993    }
994
995    private void updateHeaderShade() {
996        mHeader.setAlpha(1f);
997        mHeader.setTranslationY(getHeaderTranslation());
998        setQsTranslation(mQsExpansionHeight);
999    }
1000
1001    private float getHeaderTranslation() {
1002        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1003                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1004            return 0;
1005        }
1006        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1007            if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
1008                return 0;
1009            } else {
1010                return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
1011            }
1012        }
1013        return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
1014    }
1015
1016    private void updateHeaderKeyguard() {
1017        mHeader.setTranslationY(0f);
1018        float alpha;
1019        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1020
1021            // When on Keyguard, we hide the header as soon as the top card of the notification
1022            // stack scroller is close enough (collision distance) to the bottom of the header.
1023            alpha = getNotificationsTopY()
1024                    /
1025                    (mQsMinExpansionHeight + mNotificationsHeaderCollideDistance);
1026
1027        } else {
1028
1029            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
1030            // soon as we start translating the stack.
1031            alpha = getNotificationsTopY() / mQsMinExpansionHeight;
1032        }
1033        alpha = Math.max(0, Math.min(alpha, 1));
1034        alpha = (float) Math.pow(alpha, 0.75);
1035        mHeader.setAlpha(alpha);
1036        mKeyguardBottomArea.setAlpha(alpha);
1037        setQsTranslation(mQsExpansionHeight);
1038    }
1039
1040    private float getNotificationsTopY() {
1041        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1042            return getExpandedHeight();
1043        }
1044        return mNotificationStackScroller.getNotificationsTopY();
1045    }
1046
1047    @Override
1048    protected void onExpandingStarted() {
1049        super.onExpandingStarted();
1050        mNotificationStackScroller.onExpansionStarted();
1051        mIsExpanding = true;
1052        mQsExpandedWhenExpandingStarted = mQsExpanded;
1053        if (mQsExpanded) {
1054            onQsExpansionStarted();
1055        }
1056    }
1057
1058    @Override
1059    protected void onExpandingFinished() {
1060        super.onExpandingFinished();
1061        mNotificationStackScroller.onExpansionStopped();
1062        mIsExpanding = false;
1063        mScrollYOverride = -1;
1064        if (mExpandedHeight == 0f) {
1065            mHeader.setListening(false);
1066            mQsPanel.setListening(false);
1067        } else {
1068            mHeader.setListening(true);
1069            mQsPanel.setListening(true);
1070        }
1071        mTwoFingerQsExpand = false;
1072        mTwoFingerQsExpandPossible = false;
1073    }
1074
1075    @Override
1076    public void instantExpand() {
1077        super.instantExpand();
1078        mHeader.setListening(true);
1079        mQsPanel.setListening(true);
1080    }
1081
1082    @Override
1083    protected void setOverExpansion(float overExpansion, boolean isPixels) {
1084        if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) {
1085            return;
1086        }
1087        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1088            mNotificationStackScroller.setOnHeightChangedListener(null);
1089            if (isPixels) {
1090                mNotificationStackScroller.setOverScrolledPixels(
1091                        overExpansion, true /* onTop */, false /* animate */);
1092            } else {
1093                mNotificationStackScroller.setOverScrollAmount(
1094                        overExpansion, true /* onTop */, false /* animate */);
1095            }
1096            mNotificationStackScroller.setOnHeightChangedListener(this);
1097        }
1098    }
1099
1100    @Override
1101    protected void onTrackingStarted() {
1102        super.onTrackingStarted();
1103        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1104                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1105            mAfforanceHelper.animateHideLeftRightIcon();
1106        }
1107    }
1108
1109    @Override
1110    protected void onTrackingStopped(boolean expand) {
1111        super.onTrackingStopped(expand);
1112        if (expand) {
1113            mNotificationStackScroller.setOverScrolledPixels(
1114                    0.0f, true /* onTop */, true /* animate */);
1115        }
1116        if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1117                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1118            if (!mHintAnimationRunning) {
1119                mAfforanceHelper.reset(true);
1120            }
1121        }
1122        if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1123                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1124            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1125            lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
1126            lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
1127        }
1128    }
1129
1130    @Override
1131    public void onHeightChanged(ExpandableView view) {
1132
1133        // Block update if we are in quick settings and just the top padding changed
1134        // (i.e. view == null).
1135        if (view == null && mQsExpanded) {
1136            return;
1137        }
1138        requestPanelHeightUpdate();
1139    }
1140
1141    @Override
1142    public void onScrollChanged() {
1143        if (mQsExpanded) {
1144            requestScrollerTopPaddingUpdate(false /* animate */);
1145            requestPanelHeightUpdate();
1146        }
1147    }
1148
1149    @Override
1150    protected void onConfigurationChanged(Configuration newConfig) {
1151        super.onConfigurationChanged(newConfig);
1152        mAfforanceHelper.onConfigurationChanged();
1153    }
1154
1155    @Override
1156    public void onClick(View v) {
1157        if (v == mHeader) {
1158            onQsExpansionStarted();
1159            if (mQsExpanded) {
1160                flingSettings(0 /* vel */, false /* expand */);
1161            } else if (mQsExpansionEnabled) {
1162                flingSettings(0 /* vel */, true /* expand */);
1163            }
1164        }
1165    }
1166
1167    @Override
1168    public void onAnimationToSideStarted(boolean rightPage) {
1169        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1170        mIsLaunchTransitionRunning = true;
1171        mLaunchAnimationEndRunnable = null;
1172        if (start) {
1173            mKeyguardBottomArea.launchPhone();
1174        } else {
1175            mKeyguardBottomArea.launchCamera();
1176        }
1177        mBlockTouches = true;
1178    }
1179
1180    @Override
1181    public void onAnimationToSideEnded() {
1182        mIsLaunchTransitionRunning = false;
1183        mIsLaunchTransitionFinished = true;
1184        if (mLaunchAnimationEndRunnable != null) {
1185            mLaunchAnimationEndRunnable.run();
1186            mLaunchAnimationEndRunnable = null;
1187        }
1188    }
1189
1190    @Override
1191    protected void onEdgeClicked(boolean right) {
1192        if ((right && getRightIcon().getVisibility() != View.VISIBLE)
1193                || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) {
1194            return;
1195        }
1196        mHintAnimationRunning = true;
1197        mAfforanceHelper.startHintAnimation(right, new Runnable() {
1198            @Override
1199            public void run() {
1200                mHintAnimationRunning = false;
1201                mStatusBar.onHintFinished();
1202            }
1203        });
1204        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
1205        if (start) {
1206            mStatusBar.onPhoneHintStarted();
1207        } else {
1208            mStatusBar.onCameraHintStarted();
1209        }
1210    }
1211
1212    @Override
1213    protected void startUnlockHintAnimation() {
1214        super.startUnlockHintAnimation();
1215        startHighlightIconAnimation(getCenterIcon());
1216    }
1217
1218    /**
1219     * Starts the highlight (making it fully opaque) animation on an icon.
1220     */
1221    private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1222        icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1223                mFastOutSlowInInterpolator, new Runnable() {
1224                    @Override
1225                    public void run() {
1226                        icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
1227                                true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1228                                mFastOutSlowInInterpolator, null);
1229                    }
1230                });
1231    }
1232
1233    @Override
1234    public float getPageWidth() {
1235        return getWidth();
1236    }
1237
1238    @Override
1239    public void onSwipingStarted() {
1240        requestDisallowInterceptTouchEvent(true);
1241        mOnlyAffordanceInThisMotion = true;
1242    }
1243
1244    @Override
1245    public KeyguardAffordanceView getLeftIcon() {
1246        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1247                ? mKeyguardBottomArea.getCameraView()
1248                : mKeyguardBottomArea.getPhoneView();
1249    }
1250
1251    @Override
1252    public KeyguardAffordanceView getCenterIcon() {
1253        return mKeyguardBottomArea.getLockIcon();
1254    }
1255
1256    @Override
1257    public KeyguardAffordanceView getRightIcon() {
1258        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1259                ? mKeyguardBottomArea.getPhoneView()
1260                : mKeyguardBottomArea.getCameraView();
1261    }
1262
1263    @Override
1264    public View getLeftPreview() {
1265        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1266                ? mKeyguardBottomArea.getCameraPreview()
1267                : mKeyguardBottomArea.getPhonePreview();
1268    }
1269
1270    @Override
1271    public View getRightPreview() {
1272        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1273                ? mKeyguardBottomArea.getPhonePreview()
1274                : mKeyguardBottomArea.getCameraPreview();
1275    }
1276
1277    @Override
1278    protected float getPeekHeight() {
1279        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1280            return mNotificationStackScroller.getPeekHeight();
1281        } else {
1282            return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
1283        }
1284    }
1285
1286    @Override
1287    protected float getCannedFlingDurationFactor() {
1288        if (mQsExpanded) {
1289            return 0.7f;
1290        } else {
1291            return 0.6f;
1292        }
1293    }
1294
1295    @Override
1296    protected boolean fullyExpandedClearAllVisible() {
1297        return mNotificationStackScroller.isDismissViewNotGone()
1298                && mNotificationStackScroller.isScrolledToBottom();
1299    }
1300
1301    @Override
1302    protected boolean isClearAllVisible() {
1303        return mNotificationStackScroller.isDismissViewVisible();
1304    }
1305
1306    @Override
1307    protected int getClearAllHeight() {
1308        return mNotificationStackScroller.getDismissViewHeight();
1309    }
1310
1311    @Override
1312    protected boolean isTrackingBlocked() {
1313        return mConflictingQsExpansionGesture && mQsExpanded;
1314    }
1315
1316    public void notifyVisibleChildrenChanged() {
1317        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
1318            mReserveNotificationSpace.setVisibility(View.VISIBLE);
1319        } else {
1320            mReserveNotificationSpace.setVisibility(View.GONE);
1321        }
1322    }
1323
1324    public boolean isQsExpanded() {
1325        return mQsExpanded;
1326    }
1327
1328    @Override
1329    public boolean shouldDelayChildPressedState() {
1330        return true;
1331    }
1332
1333    public boolean isLaunchTransitionFinished() {
1334        return mIsLaunchTransitionFinished;
1335    }
1336
1337    public boolean isLaunchTransitionRunning() {
1338        return mIsLaunchTransitionRunning;
1339    }
1340
1341    public void setLaunchTransitionEndRunnable(Runnable r) {
1342        mLaunchAnimationEndRunnable = r;
1343    }
1344
1345    public void setEmptyDragAmount(float amount) {
1346        float factor = 1f;
1347        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1348            factor = 0.6f;
1349        } else if (!mStatusBar.hasNotifications()) {
1350            factor = 0.4f;
1351        }
1352        mEmptyDragAmount = amount * factor;
1353        positionClockAndNotifications();
1354    }
1355}
1356