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