1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.ValueAnimator;
24import android.content.Context;
25import android.content.res.Configuration;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.Paint;
29import android.graphics.Rect;
30import android.util.AttributeSet;
31import android.util.MathUtils;
32import android.view.MotionEvent;
33import android.view.VelocityTracker;
34import android.view.View;
35import android.view.ViewTreeObserver;
36import android.view.WindowInsets;
37import android.view.accessibility.AccessibilityEvent;
38import android.view.animation.AnimationUtils;
39import android.view.animation.Interpolator;
40import android.view.animation.PathInterpolator;
41import android.widget.FrameLayout;
42import android.widget.TextView;
43
44import com.android.internal.logging.MetricsLogger;
45import com.android.keyguard.KeyguardStatusView;
46import com.android.systemui.DejankUtils;
47import com.android.systemui.EventLogConstants;
48import com.android.systemui.EventLogTags;
49import com.android.systemui.R;
50import com.android.systemui.qs.QSContainer;
51import com.android.systemui.qs.QSPanel;
52import com.android.systemui.statusbar.ExpandableNotificationRow;
53import com.android.systemui.statusbar.ExpandableView;
54import com.android.systemui.statusbar.FlingAnimationUtils;
55import com.android.systemui.statusbar.GestureRecorder;
56import com.android.systemui.statusbar.KeyguardAffordanceView;
57import com.android.systemui.statusbar.NotificationData;
58import com.android.systemui.statusbar.StatusBarState;
59import com.android.systemui.statusbar.policy.HeadsUpManager;
60import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
61import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
62import com.android.systemui.statusbar.stack.StackStateAnimator;
63
64public class NotificationPanelView extends PanelView implements
65        ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
66        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
67        KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
68        HeadsUpManager.OnHeadsUpChangedListener {
69
70    private static final boolean DEBUG = false;
71
72    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
73    // changed.
74    private static final int CAP_HEIGHT = 1456;
75    private static final int FONT_HEIGHT = 2163;
76
77    private static final float HEADER_RUBBERBAND_FACTOR = 2.05f;
78    private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
79
80    private static final String COUNTER_PANEL_OPEN = "panel_open";
81    private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
82    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
83
84    private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
85
86    public static final long DOZE_ANIMATION_DURATION = 700;
87
88    private KeyguardAffordanceHelper mAfforanceHelper;
89    private StatusBarHeaderView mHeader;
90    private KeyguardUserSwitcher mKeyguardUserSwitcher;
91    private KeyguardStatusBarView mKeyguardStatusBar;
92    private QSContainer mQsContainer;
93    private QSPanel mQsPanel;
94    private KeyguardStatusView mKeyguardStatusView;
95    private ObservableScrollView mScrollView;
96    private TextView mClockView;
97    private View mReserveNotificationSpace;
98    private View mQsNavbarScrim;
99    private NotificationsQuickSettingsContainer mNotificationContainerParent;
100    private NotificationStackScrollLayout mNotificationStackScroller;
101    private int mNotificationTopPadding;
102    private boolean mAnimateNextTopPaddingChange;
103
104    private int mTrackingPointer;
105    private VelocityTracker mVelocityTracker;
106    private boolean mQsTracking;
107
108    /**
109     * Handles launching the secure camera properly even when other applications may be using the
110     * camera hardware.
111     */
112    private SecureCameraLaunchManager mSecureCameraLaunchManager;
113
114    /**
115     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
116     * the expansion for quick settings.
117     */
118    private boolean mConflictingQsExpansionGesture;
119
120    /**
121     * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
122     * intercepted yet.
123     */
124    private boolean mIntercepting;
125    private boolean mPanelExpanded;
126    private boolean mQsExpanded;
127    private boolean mQsExpandedWhenExpandingStarted;
128    private boolean mQsFullyExpanded;
129    private boolean mKeyguardShowing;
130    private boolean mDozing;
131    private boolean mDozingOnDown;
132    private int mStatusBarState;
133    private float mInitialHeightOnTouch;
134    private float mInitialTouchX;
135    private float mInitialTouchY;
136    private float mLastTouchX;
137    private float mLastTouchY;
138    private float mQsExpansionHeight;
139    private int mQsMinExpansionHeight;
140    private int mQsMaxExpansionHeight;
141    private int mQsPeekHeight;
142    private boolean mStackScrollerOverscrolling;
143    private boolean mQsExpansionFromOverscroll;
144    private float mLastOverscroll;
145    private boolean mQsExpansionEnabled = true;
146    private ValueAnimator mQsExpansionAnimator;
147    private FlingAnimationUtils mFlingAnimationUtils;
148    private int mStatusBarMinHeight;
149    private boolean mUnlockIconActive;
150    private int mNotificationsHeaderCollideDistance;
151    private int mUnlockMoveDistance;
152    private float mEmptyDragAmount;
153
154    private Interpolator mFastOutSlowInInterpolator;
155    private Interpolator mFastOutLinearInterpolator;
156    private Interpolator mDozeAnimationInterpolator;
157    private ObjectAnimator mClockAnimator;
158    private int mClockAnimationTarget = -1;
159    private int mTopPaddingAdjustment;
160    private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
161            new KeyguardClockPositionAlgorithm();
162    private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
163            new KeyguardClockPositionAlgorithm.Result();
164    private boolean mIsExpanding;
165
166    private boolean mBlockTouches;
167    private int mNotificationScrimWaitDistance;
168    // Used for two finger gesture as well as accessibility shortcut to QS.
169    private boolean mQsExpandImmediate;
170    private boolean mTwoFingerQsExpandPossible;
171
172    /**
173     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
174     * need to take this into account in our panel height calculation.
175     */
176    private int mScrollYOverride = -1;
177    private boolean mQsAnimatorExpand;
178    private boolean mIsLaunchTransitionFinished;
179    private boolean mIsLaunchTransitionRunning;
180    private Runnable mLaunchAnimationEndRunnable;
181    private boolean mOnlyAffordanceInThisMotion;
182    private boolean mKeyguardStatusViewAnimating;
183    private boolean mHeaderAnimating;
184    private ObjectAnimator mQsContainerAnimator;
185    private ValueAnimator mQsSizeChangeAnimator;
186
187    private boolean mShadeEmpty;
188
189    private boolean mQsScrimEnabled = true;
190    private boolean mLastAnnouncementWasQuickSettings;
191    private boolean mQsTouchAboveFalsingThreshold;
192    private int mQsFalsingThreshold;
193
194    private float mKeyguardStatusBarAnimateAlpha = 1f;
195    private int mOldLayoutDirection;
196    private HeadsUpTouchHelper mHeadsUpTouchHelper;
197    private boolean mIsExpansionFromHeadsUp;
198    private boolean mListenForHeadsUp;
199    private int mNavigationBarBottomHeight;
200    private boolean mExpandingFromHeadsUp;
201    private boolean mCollapsedOnDown;
202    private int mPositionMinSideMargin;
203    private int mLastOrientation = -1;
204    private boolean mClosingWithAlphaFadeOut;
205    private boolean mHeadsUpAnimatingAway;
206
207    private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
208        @Override
209        public void run() {
210            mHeadsUpAnimatingAway = false;
211            notifyBarPanelExpansionChanged();
212        }
213    };
214
215    /** Interpolator to be used for animations that respond directly to a touch */
216    private final Interpolator mTouchResponseInterpolator =
217            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
218
219    public NotificationPanelView(Context context, AttributeSet attrs) {
220        super(context, attrs);
221        setWillNotDraw(!DEBUG);
222    }
223
224    public void setStatusBar(PhoneStatusBar bar) {
225        mStatusBar = bar;
226    }
227
228    @Override
229    protected void onFinishInflate() {
230        super.onFinishInflate();
231        mHeader = (StatusBarHeaderView) findViewById(R.id.header);
232        mHeader.setOnClickListener(this);
233        mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
234        mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
235        mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
236        mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
237        mClockView = (TextView) findViewById(R.id.clock_view);
238        mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
239        mScrollView.setListener(this);
240        mScrollView.setFocusable(false);
241        mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
242        mNotificationContainerParent = (NotificationsQuickSettingsContainer)
243                findViewById(R.id.notification_container_parent);
244        mNotificationStackScroller = (NotificationStackScrollLayout)
245                findViewById(R.id.notification_stack_scroller);
246        mNotificationStackScroller.setOnHeightChangedListener(this);
247        mNotificationStackScroller.setOverscrollTopChangedListener(this);
248        mNotificationStackScroller.setOnEmptySpaceClickListener(this);
249        mNotificationStackScroller.setScrollView(mScrollView);
250        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
251                android.R.interpolator.fast_out_slow_in);
252        mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
253                android.R.interpolator.fast_out_linear_in);
254        mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(),
255                android.R.interpolator.linear_out_slow_in);
256        mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
257        mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
258        mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
259        mSecureCameraLaunchManager =
260                new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
261        mLastOrientation = getResources().getConfiguration().orientation;
262
263        // recompute internal state when qspanel height changes
264        mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
265            @Override
266            public void onLayoutChange(View v, int left, int top, int right, int bottom,
267                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
268                final int height = bottom - top;
269                final int oldHeight = oldBottom - oldTop;
270                if (height != oldHeight) {
271                    onScrollChanged();
272                }
273            }
274        });
275    }
276
277    @Override
278    protected void loadDimens() {
279        super.loadDimens();
280        mNotificationTopPadding = getResources().getDimensionPixelSize(
281                R.dimen.notifications_top_padding);
282        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
283        mStatusBarMinHeight = getResources().getDimensionPixelSize(
284                com.android.internal.R.dimen.status_bar_height);
285        mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
286        mNotificationsHeaderCollideDistance =
287                getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
288        mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
289        mClockPositionAlgorithm.loadDimens(getResources());
290        mNotificationScrimWaitDistance =
291                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
292        mQsFalsingThreshold = getResources().getDimensionPixelSize(
293                R.dimen.qs_falsing_threshold);
294        mPositionMinSideMargin = getResources().getDimensionPixelSize(
295                R.dimen.notification_panel_min_side_margin);
296    }
297
298    public void updateResources() {
299        int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
300        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
301        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
302        if (lp.width != panelWidth) {
303            lp.width = panelWidth;
304            lp.gravity = panelGravity;
305            mHeader.setLayoutParams(lp);
306            mHeader.post(mUpdateHeader);
307        }
308
309        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
310        if (lp.width != panelWidth) {
311            lp.width = panelWidth;
312            lp.gravity = panelGravity;
313            mNotificationStackScroller.setLayoutParams(lp);
314        }
315
316        lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
317        if (lp.width != panelWidth) {
318            lp.width = panelWidth;
319            lp.gravity = panelGravity;
320            mScrollView.setLayoutParams(lp);
321        }
322    }
323
324    @Override
325    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
326        super.onLayout(changed, left, top, right, bottom);
327
328        // Update Clock Pivot
329        mKeyguardStatusView.setPivotX(getWidth() / 2);
330        mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
331
332        // Calculate quick setting heights.
333        int oldMaxHeight = mQsMaxExpansionHeight;
334        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
335        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
336        positionClockAndNotifications();
337        if (mQsExpanded && mQsFullyExpanded) {
338            mQsExpansionHeight = mQsMaxExpansionHeight;
339            requestScrollerTopPaddingUpdate(false /* animate */);
340            requestPanelHeightUpdate();
341
342            // Size has changed, start an animation.
343            if (mQsMaxExpansionHeight != oldMaxHeight) {
344                startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
345            }
346        } else if (!mQsExpanded) {
347            setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
348        }
349        updateStackHeight(getExpandedHeight());
350        updateHeader();
351        mNotificationStackScroller.updateIsSmallScreen(
352                mHeader.getCollapsedHeight() + mQsPeekHeight);
353
354        // If we are running a size change animation, the animation takes care of the height of
355        // the container. However, if we are not animating, we always need to make the QS container
356        // the desired height so when closing the QS detail, it stays smaller after the size change
357        // animation is finished but the detail view is still being animated away (this animation
358        // takes longer than the size change animation).
359        if (mQsSizeChangeAnimator == null) {
360            mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
361        }
362        updateMaxHeadsUpTranslation();
363    }
364
365    @Override
366    public void onAttachedToWindow() {
367        mSecureCameraLaunchManager.create();
368    }
369
370    @Override
371    public void onDetachedFromWindow() {
372        mSecureCameraLaunchManager.destroy();
373    }
374
375    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
376        if (mQsSizeChangeAnimator != null) {
377            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
378            mQsSizeChangeAnimator.cancel();
379        }
380        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
381        mQsSizeChangeAnimator.setDuration(300);
382        mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator);
383        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
384            @Override
385            public void onAnimationUpdate(ValueAnimator animation) {
386                requestScrollerTopPaddingUpdate(false /* animate */);
387                requestPanelHeightUpdate();
388                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
389                mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
390            }
391        });
392        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
393            @Override
394            public void onAnimationEnd(Animator animation) {
395                mQsSizeChangeAnimator = null;
396            }
397        });
398        mQsSizeChangeAnimator.start();
399    }
400
401    /**
402     * Positions the clock and notifications dynamically depending on how many notifications are
403     * showing.
404     */
405    private void positionClockAndNotifications() {
406        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
407        int stackScrollerPadding;
408        if (mStatusBarState != StatusBarState.KEYGUARD) {
409            int bottom = mHeader.getCollapsedHeight();
410            stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
411                    ? bottom + mQsPeekHeight + mNotificationTopPadding
412                    : mKeyguardStatusBar.getHeight() + mNotificationTopPadding;
413            mTopPaddingAdjustment = 0;
414        } else {
415            mClockPositionAlgorithm.setup(
416                    mStatusBar.getMaxKeyguardNotifications(),
417                    getMaxPanelHeight(),
418                    getExpandedHeight(),
419                    mNotificationStackScroller.getNotGoneChildCount(),
420                    getHeight(),
421                    mKeyguardStatusView.getHeight(),
422                    mEmptyDragAmount);
423            mClockPositionAlgorithm.run(mClockPositionResult);
424            if (animate || mClockAnimator != null) {
425                startClockAnimation(mClockPositionResult.clockY);
426            } else {
427                mKeyguardStatusView.setY(mClockPositionResult.clockY);
428            }
429            updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
430            stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
431            mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
432        }
433        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
434        requestScrollerTopPaddingUpdate(animate);
435    }
436
437    private void startClockAnimation(int y) {
438        if (mClockAnimationTarget == y) {
439            return;
440        }
441        mClockAnimationTarget = y;
442        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
443            @Override
444            public boolean onPreDraw() {
445                getViewTreeObserver().removeOnPreDrawListener(this);
446                if (mClockAnimator != null) {
447                    mClockAnimator.removeAllListeners();
448                    mClockAnimator.cancel();
449                }
450                mClockAnimator = ObjectAnimator
451                        .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
452                mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
453                mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
454                mClockAnimator.addListener(new AnimatorListenerAdapter() {
455                    @Override
456                    public void onAnimationEnd(Animator animation) {
457                        mClockAnimator = null;
458                        mClockAnimationTarget = -1;
459                    }
460                });
461                mClockAnimator.start();
462                return true;
463            }
464        });
465    }
466
467    private void updateClock(float alpha, float scale) {
468        if (!mKeyguardStatusViewAnimating) {
469            mKeyguardStatusView.setAlpha(alpha);
470        }
471        mKeyguardStatusView.setScaleX(scale);
472        mKeyguardStatusView.setScaleY(scale);
473    }
474
475    public void animateToFullShade(long delay) {
476        mAnimateNextTopPaddingChange = true;
477        mNotificationStackScroller.goToFullShade(delay);
478        requestLayout();
479    }
480
481    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
482        mQsExpansionEnabled = qsExpansionEnabled;
483        mHeader.setClickable(qsExpansionEnabled);
484    }
485
486    @Override
487    public void resetViews() {
488        mIsLaunchTransitionFinished = false;
489        mBlockTouches = false;
490        mUnlockIconActive = false;
491        mAfforanceHelper.reset(true);
492        closeQs();
493        mStatusBar.dismissPopups();
494        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
495                true /* cancelAnimators */);
496        mNotificationStackScroller.resetScrollPosition();
497    }
498
499    public void closeQs() {
500        cancelQsAnimation();
501        setQsExpansion(mQsMinExpansionHeight);
502    }
503
504    public void animateCloseQs() {
505        if (mQsExpansionAnimator != null) {
506            if (!mQsAnimatorExpand) {
507                return;
508            }
509            float height = mQsExpansionHeight;
510            mQsExpansionAnimator.cancel();
511            setQsExpansion(height);
512        }
513        flingSettings(0 /* vel */, false);
514    }
515
516    public void openQs() {
517        cancelQsAnimation();
518        if (mQsExpansionEnabled) {
519            setQsExpansion(mQsMaxExpansionHeight);
520        }
521    }
522
523    public void expandWithQs() {
524        if (mQsExpansionEnabled) {
525            mQsExpandImmediate = true;
526        }
527        expand();
528    }
529
530    @Override
531    public void fling(float vel, boolean expand) {
532        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
533        if (gr != null) {
534            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
535        }
536        super.fling(vel, expand);
537    }
538
539    @Override
540    protected void flingToHeight(float vel, boolean expand, float target,
541            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
542        mHeadsUpTouchHelper.notifyFling(!expand);
543        setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f);
544        super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
545    }
546
547    @Override
548    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
549        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
550            event.getText().add(getKeyguardOrLockScreenString());
551            mLastAnnouncementWasQuickSettings = false;
552            return true;
553        }
554        return super.dispatchPopulateAccessibilityEventInternal(event);
555    }
556
557    @Override
558    public boolean onInterceptTouchEvent(MotionEvent event) {
559        if (mBlockTouches) {
560            return false;
561        }
562        initDownStates(event);
563        if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
564            mIsExpansionFromHeadsUp = true;
565            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
566            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
567            return true;
568        }
569        if (!isFullyCollapsed() && onQsIntercept(event)) {
570            return true;
571        }
572        return super.onInterceptTouchEvent(event);
573    }
574
575    private boolean onQsIntercept(MotionEvent event) {
576        int pointerIndex = event.findPointerIndex(mTrackingPointer);
577        if (pointerIndex < 0) {
578            pointerIndex = 0;
579            mTrackingPointer = event.getPointerId(pointerIndex);
580        }
581        final float x = event.getX(pointerIndex);
582        final float y = event.getY(pointerIndex);
583
584        switch (event.getActionMasked()) {
585            case MotionEvent.ACTION_DOWN:
586                mIntercepting = true;
587                mInitialTouchY = y;
588                mInitialTouchX = x;
589                initVelocityTracker();
590                trackMovement(event);
591                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
592                    getParent().requestDisallowInterceptTouchEvent(true);
593                }
594                if (mQsExpansionAnimator != null) {
595                    onQsExpansionStarted();
596                    mInitialHeightOnTouch = mQsExpansionHeight;
597                    mQsTracking = true;
598                    mIntercepting = false;
599                    mNotificationStackScroller.removeLongPressCallback();
600                }
601                break;
602            case MotionEvent.ACTION_POINTER_UP:
603                final int upPointer = event.getPointerId(event.getActionIndex());
604                if (mTrackingPointer == upPointer) {
605                    // gesture is ongoing, find a new pointer to track
606                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
607                    mTrackingPointer = event.getPointerId(newIndex);
608                    mInitialTouchX = event.getX(newIndex);
609                    mInitialTouchY = event.getY(newIndex);
610                }
611                break;
612
613            case MotionEvent.ACTION_MOVE:
614                final float h = y - mInitialTouchY;
615                trackMovement(event);
616                if (mQsTracking) {
617
618                    // Already tracking because onOverscrolled was called. We need to update here
619                    // so we don't stop for a frame until the next touch event gets handled in
620                    // onTouchEvent.
621                    setQsExpansion(h + mInitialHeightOnTouch);
622                    trackMovement(event);
623                    mIntercepting = false;
624                    return true;
625                }
626                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
627                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
628                    mQsTracking = true;
629                    onQsExpansionStarted();
630                    notifyExpandingFinished();
631                    mInitialHeightOnTouch = mQsExpansionHeight;
632                    mInitialTouchY = y;
633                    mInitialTouchX = x;
634                    mIntercepting = false;
635                    mNotificationStackScroller.removeLongPressCallback();
636                    return true;
637                }
638                break;
639
640            case MotionEvent.ACTION_CANCEL:
641            case MotionEvent.ACTION_UP:
642                trackMovement(event);
643                if (mQsTracking) {
644                    flingQsWithCurrentVelocity(y,
645                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
646                    mQsTracking = false;
647                }
648                mIntercepting = false;
649                break;
650        }
651        return false;
652    }
653
654    @Override
655    protected boolean isInContentBounds(float x, float y) {
656        float stackScrollerX = mNotificationStackScroller.getX();
657        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
658                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
659    }
660
661    private void initDownStates(MotionEvent event) {
662        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
663            mOnlyAffordanceInThisMotion = false;
664            mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
665            mDozingOnDown = isDozing();
666            mCollapsedOnDown = isFullyCollapsed();
667            mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
668        }
669    }
670
671    @Override
672    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
673
674        // Block request when interacting with the scroll view so we can still intercept the
675        // scrolling when QS is expanded.
676        if (mScrollView.isHandlingTouchEvent()) {
677            return;
678        }
679        super.requestDisallowInterceptTouchEvent(disallowIntercept);
680    }
681
682    private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
683        float vel = getCurrentVelocity();
684        final boolean expandsQs = flingExpandsQs(vel);
685        if (expandsQs) {
686            logQsSwipeDown(y);
687        }
688        flingSettings(vel, expandsQs && !isCancelMotionEvent);
689    }
690
691    private void logQsSwipeDown(float y) {
692        float vel = getCurrentVelocity();
693        final int gesture = mStatusBarState == StatusBarState.KEYGUARD
694                ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS
695                : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS;
696        EventLogTags.writeSysuiLockscreenGesture(
697                gesture,
698                (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
699                (int) (vel / mStatusBar.getDisplayDensity()));
700    }
701
702    private boolean flingExpandsQs(float vel) {
703        if (isBelowFalsingThreshold()) {
704            return false;
705        }
706        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
707            return getQsExpansionFraction() > 0.5f;
708        } else {
709            return vel > 0;
710        }
711    }
712
713    private boolean isBelowFalsingThreshold() {
714        return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD;
715    }
716
717    private float getQsExpansionFraction() {
718        return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
719                / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
720    }
721
722    @Override
723    public boolean onTouchEvent(MotionEvent event) {
724        if (mBlockTouches) {
725            return false;
726        }
727        initDownStates(event);
728        if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
729                && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
730            mIsExpansionFromHeadsUp = true;
731            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
732        }
733        if ((!mIsExpanding || mHintAnimationRunning)
734                && !mQsExpanded
735                && mStatusBar.getBarState() != StatusBarState.SHADE) {
736            mAfforanceHelper.onTouchEvent(event);
737        }
738        if (mOnlyAffordanceInThisMotion) {
739            return true;
740        }
741        mHeadsUpTouchHelper.onTouchEvent(event);
742        if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
743            return true;
744        }
745        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
746            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
747            updateVerticalPanelPosition(event.getX());
748        }
749        super.onTouchEvent(event);
750        return true;
751    }
752
753    private boolean handleQsTouch(MotionEvent event) {
754        final int action = event.getActionMasked();
755        if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
756                && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
757                && mQsExpansionEnabled) {
758
759            // Down in the empty area while fully expanded - go to QS.
760            mQsTracking = true;
761            mConflictingQsExpansionGesture = true;
762            onQsExpansionStarted();
763            mInitialHeightOnTouch = mQsExpansionHeight;
764            mInitialTouchY = event.getX();
765            mInitialTouchX = event.getY();
766        }
767        if (!isFullyCollapsed()) {
768            handleQsDown(event);
769        }
770        if (!mQsExpandImmediate && mQsTracking) {
771            onQsTouch(event);
772            if (!mConflictingQsExpansionGesture) {
773                return true;
774            }
775        }
776        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
777            mConflictingQsExpansionGesture = false;
778        }
779        if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
780                && mQsExpansionEnabled) {
781            mTwoFingerQsExpandPossible = true;
782        }
783        if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
784                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
785            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
786            mQsExpandImmediate = true;
787            requestPanelHeightUpdate();
788
789            // Normally, we start listening when the panel is expanded, but here we need to start
790            // earlier so the state is already up to date when dragging down.
791            setListening(true);
792        }
793        return false;
794    }
795
796    private boolean isInQsArea(float x, float y) {
797        return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) &&
798                (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
799                || y <= mQsContainer.getY() + mQsContainer.getHeight());
800    }
801
802    private boolean isOpenQsEvent(MotionEvent event) {
803        final int pointerCount = event.getPointerCount();
804        final int action = event.getActionMasked();
805
806        final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
807                && pointerCount == 2;
808
809        final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
810                && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
811                        || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
812
813        final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
814                && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
815                        || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
816
817        return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
818    }
819
820    private void handleQsDown(MotionEvent event) {
821        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
822                && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
823            mQsTracking = true;
824            onQsExpansionStarted();
825            mInitialHeightOnTouch = mQsExpansionHeight;
826            mInitialTouchY = event.getX();
827            mInitialTouchX = event.getY();
828
829            // If we interrupt an expansion gesture here, make sure to update the state correctly.
830            notifyExpandingFinished();
831        }
832    }
833
834    @Override
835    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
836        boolean expands = super.flingExpands(vel, vectorVel, x, y);
837
838        // If we are already running a QS expansion, make sure that we keep the panel open.
839        if (mQsExpansionAnimator != null) {
840            expands = true;
841        }
842        return expands;
843    }
844
845    @Override
846    protected boolean hasConflictingGestures() {
847        return mStatusBar.getBarState() != StatusBarState.SHADE;
848    }
849
850    @Override
851    protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
852        return !mAfforanceHelper.isOnAffordanceIcon(x, y);
853    }
854
855    private void onQsTouch(MotionEvent event) {
856        int pointerIndex = event.findPointerIndex(mTrackingPointer);
857        if (pointerIndex < 0) {
858            pointerIndex = 0;
859            mTrackingPointer = event.getPointerId(pointerIndex);
860        }
861        final float y = event.getY(pointerIndex);
862        final float x = event.getX(pointerIndex);
863        final float h = y - mInitialTouchY;
864
865        switch (event.getActionMasked()) {
866            case MotionEvent.ACTION_DOWN:
867                mQsTracking = true;
868                mInitialTouchY = y;
869                mInitialTouchX = x;
870                onQsExpansionStarted();
871                mInitialHeightOnTouch = mQsExpansionHeight;
872                initVelocityTracker();
873                trackMovement(event);
874                break;
875
876            case MotionEvent.ACTION_POINTER_UP:
877                final int upPointer = event.getPointerId(event.getActionIndex());
878                if (mTrackingPointer == upPointer) {
879                    // gesture is ongoing, find a new pointer to track
880                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
881                    final float newY = event.getY(newIndex);
882                    final float newX = event.getX(newIndex);
883                    mTrackingPointer = event.getPointerId(newIndex);
884                    mInitialHeightOnTouch = mQsExpansionHeight;
885                    mInitialTouchY = newY;
886                    mInitialTouchX = newX;
887                }
888                break;
889
890            case MotionEvent.ACTION_MOVE:
891                setQsExpansion(h + mInitialHeightOnTouch);
892                if (h >= getFalsingThreshold()) {
893                    mQsTouchAboveFalsingThreshold = true;
894                }
895                trackMovement(event);
896                break;
897
898            case MotionEvent.ACTION_UP:
899            case MotionEvent.ACTION_CANCEL:
900                mQsTracking = false;
901                mTrackingPointer = -1;
902                trackMovement(event);
903                float fraction = getQsExpansionFraction();
904                if ((fraction != 0f || y >= mInitialTouchY)
905                        && (fraction != 1f || y <= mInitialTouchY)) {
906                    flingQsWithCurrentVelocity(y,
907                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
908                } else {
909                    logQsSwipeDown(y);
910                    mScrollYOverride = -1;
911                }
912                if (mVelocityTracker != null) {
913                    mVelocityTracker.recycle();
914                    mVelocityTracker = null;
915                }
916                break;
917        }
918    }
919
920    private int getFalsingThreshold() {
921        float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
922        return (int) (mQsFalsingThreshold * factor);
923    }
924
925    @Override
926    public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
927        if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
928                -1 /* yDiff: Not relevant here */)) {
929            mQsTracking = true;
930            onQsExpansionStarted(amount);
931            mInitialHeightOnTouch = mQsExpansionHeight;
932            mInitialTouchY = mLastTouchY;
933            mInitialTouchX = mLastTouchX;
934        }
935    }
936
937    @Override
938    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
939        cancelQsAnimation();
940        if (!mQsExpansionEnabled) {
941            amount = 0f;
942        }
943        float rounded = amount >= 1f ? amount : 0f;
944        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
945        mQsExpansionFromOverscroll = rounded != 0f;
946        mLastOverscroll = rounded;
947        updateQsState();
948        setQsExpansion(mQsMinExpansionHeight + rounded);
949    }
950
951    @Override
952    public void flingTopOverscroll(float velocity, boolean open) {
953        mLastOverscroll = 0f;
954        setQsExpansion(mQsExpansionHeight);
955        flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
956                new Runnable() {
957                    @Override
958                    public void run() {
959                        mStackScrollerOverscrolling = false;
960                        mQsExpansionFromOverscroll = false;
961                        updateQsState();
962                    }
963                }, false /* isClick */);
964    }
965
966    private void onQsExpansionStarted() {
967        onQsExpansionStarted(0);
968    }
969
970    private void onQsExpansionStarted(int overscrollAmount) {
971        cancelQsAnimation();
972        cancelHeightAnimator();
973
974        // Reset scroll position and apply that position to the expanded height.
975        float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
976        if (mScrollView.getScrollY() != 0) {
977            mScrollYOverride = mScrollView.getScrollY();
978        }
979        mScrollView.scrollTo(0, 0);
980        setQsExpansion(height);
981        requestPanelHeightUpdate();
982    }
983
984    private void setQsExpanded(boolean expanded) {
985        boolean changed = mQsExpanded != expanded;
986        if (changed) {
987            mQsExpanded = expanded;
988            updateQsState();
989            requestPanelHeightUpdate();
990            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
991            mStatusBar.setQsExpanded(expanded);
992            mQsPanel.setExpanded(expanded);
993            mNotificationContainerParent.setQsExpanded(expanded);
994        }
995    }
996
997    public void setBarState(int statusBarState, boolean keyguardFadingAway,
998            boolean goingToFullShade) {
999        int oldState = mStatusBarState;
1000        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
1001        setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
1002        setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
1003
1004        mStatusBarState = statusBarState;
1005        mKeyguardShowing = keyguardShowing;
1006
1007        if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
1008                && statusBarState == StatusBarState.SHADE_LOCKED)) {
1009            animateKeyguardStatusBarOut();
1010            animateHeaderSlidingIn();
1011        } else if (oldState == StatusBarState.SHADE_LOCKED
1012                && statusBarState == StatusBarState.KEYGUARD) {
1013            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1014            animateHeaderSlidingOut();
1015        } else {
1016            mKeyguardStatusBar.setAlpha(1f);
1017            mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
1018            if (keyguardShowing && oldState != mStatusBarState) {
1019                mKeyguardBottomArea.updateLeftAffordance();
1020                mAfforanceHelper.updatePreviews();
1021            }
1022        }
1023        if (keyguardShowing) {
1024            updateDozingVisibilities(false /* animate */);
1025        }
1026        resetVerticalPanelPosition();
1027        updateQsState();
1028    }
1029
1030    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
1031        @Override
1032        public void run() {
1033            mKeyguardStatusViewAnimating = false;
1034            mKeyguardStatusView.setVisibility(View.GONE);
1035        }
1036    };
1037
1038    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
1039        @Override
1040        public void run() {
1041            mKeyguardStatusViewAnimating = false;
1042        }
1043    };
1044
1045    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
1046            = new AnimatorListenerAdapter() {
1047        @Override
1048        public void onAnimationEnd(Animator animation) {
1049            mHeaderAnimating = false;
1050            mQsContainerAnimator = null;
1051            mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
1052        }
1053    };
1054
1055    private final OnLayoutChangeListener mQsContainerAnimatorUpdater
1056            = new OnLayoutChangeListener() {
1057        @Override
1058        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
1059                int oldTop, int oldRight, int oldBottom) {
1060            int oldHeight = oldBottom - oldTop;
1061            int height = bottom - top;
1062            if (height != oldHeight && mQsContainerAnimator != null) {
1063                PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
1064                float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
1065                float newStartValue = -height - top;
1066                values[0].setFloatValues(newStartValue, newEndValue);
1067                mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
1068            }
1069        }
1070    };
1071
1072    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
1073            = new ViewTreeObserver.OnPreDrawListener() {
1074        @Override
1075        public boolean onPreDraw() {
1076            getViewTreeObserver().removeOnPreDrawListener(this);
1077            long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
1078                    ? 0
1079                    : mStatusBar.calculateGoingToFullShadeDelay();
1080            mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
1081            mHeader.animate()
1082                    .translationY(0f)
1083                    .setStartDelay(delay)
1084                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
1085                    .setInterpolator(mFastOutSlowInInterpolator)
1086                    .start();
1087            mQsContainer.setY(-mQsContainer.getHeight());
1088            mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
1089                    mQsContainer.getTranslationY(),
1090                    mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
1091                            - mQsContainer.getTop());
1092            mQsContainerAnimator.setStartDelay(delay);
1093            mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
1094            mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
1095            mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
1096            mQsContainerAnimator.start();
1097            mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
1098            return true;
1099        }
1100    };
1101
1102    private void animateHeaderSlidingIn() {
1103        // If the QS is already expanded we don't need to slide in the header as it's already
1104        // visible.
1105        if (!mQsExpanded) {
1106            mHeaderAnimating = true;
1107            getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
1108        }
1109    }
1110
1111    private void animateHeaderSlidingOut() {
1112        mHeaderAnimating = true;
1113        mHeader.animate().y(-mHeader.getHeight())
1114                .setStartDelay(0)
1115                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
1116                .setInterpolator(mFastOutSlowInInterpolator)
1117                .setListener(new AnimatorListenerAdapter() {
1118                    @Override
1119                    public void onAnimationEnd(Animator animation) {
1120                        mHeader.animate().setListener(null);
1121                        mHeaderAnimating = false;
1122                        updateQsState();
1123                    }
1124                })
1125                .start();
1126        mQsContainer.animate()
1127                .y(-mQsContainer.getHeight())
1128                .setStartDelay(0)
1129                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
1130                .setInterpolator(mFastOutSlowInInterpolator)
1131                .start();
1132    }
1133
1134    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
1135        @Override
1136        public void run() {
1137            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
1138            mKeyguardStatusBar.setAlpha(1f);
1139            mKeyguardStatusBarAnimateAlpha = 1f;
1140        }
1141    };
1142
1143    private void animateKeyguardStatusBarOut() {
1144        ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
1145        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
1146        anim.setStartDelay(mStatusBar.isKeyguardFadingAway()
1147                ? mStatusBar.getKeyguardFadingAwayDelay()
1148                : 0);
1149        anim.setDuration(mStatusBar.isKeyguardFadingAway()
1150                ? mStatusBar.getKeyguardFadingAwayDuration() / 2
1151                : StackStateAnimator.ANIMATION_DURATION_STANDARD);
1152        anim.setInterpolator(mDozeAnimationInterpolator);
1153        anim.addListener(new AnimatorListenerAdapter() {
1154            @Override
1155            public void onAnimationEnd(Animator animation) {
1156                mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
1157            }
1158        });
1159        anim.start();
1160    }
1161
1162    private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
1163            new ValueAnimator.AnimatorUpdateListener() {
1164        @Override
1165        public void onAnimationUpdate(ValueAnimator animation) {
1166            mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
1167            updateHeaderKeyguardAlpha();
1168        }
1169    };
1170
1171    private void animateKeyguardStatusBarIn(long duration) {
1172        mKeyguardStatusBar.setVisibility(View.VISIBLE);
1173        mKeyguardStatusBar.setAlpha(0f);
1174        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
1175        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
1176        anim.setDuration(duration);
1177        anim.setInterpolator(mDozeAnimationInterpolator);
1178        anim.start();
1179    }
1180
1181    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
1182        @Override
1183        public void run() {
1184            mKeyguardBottomArea.setVisibility(View.GONE);
1185        }
1186    };
1187
1188    private void setKeyguardBottomAreaVisibility(int statusBarState,
1189            boolean goingToFullShade) {
1190        if (goingToFullShade) {
1191            mKeyguardBottomArea.animate().cancel();
1192            mKeyguardBottomArea.animate()
1193                    .alpha(0f)
1194                    .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
1195                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2)
1196                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
1197                    .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
1198                    .start();
1199        } else if (statusBarState == StatusBarState.KEYGUARD
1200                || statusBarState == StatusBarState.SHADE_LOCKED) {
1201            mKeyguardBottomArea.animate().cancel();
1202            if (!mDozing) {
1203                mKeyguardBottomArea.setVisibility(View.VISIBLE);
1204            }
1205            mKeyguardBottomArea.setAlpha(1f);
1206        } else {
1207            mKeyguardBottomArea.animate().cancel();
1208            mKeyguardBottomArea.setVisibility(View.GONE);
1209            mKeyguardBottomArea.setAlpha(1f);
1210        }
1211    }
1212
1213    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
1214            boolean goingToFullShade) {
1215        if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
1216                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
1217            mKeyguardStatusView.animate().cancel();
1218            mKeyguardStatusViewAnimating = true;
1219            mKeyguardStatusView.animate()
1220                    .alpha(0f)
1221                    .setStartDelay(0)
1222                    .setDuration(160)
1223                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
1224                    .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
1225            if (keyguardFadingAway) {
1226                mKeyguardStatusView.animate()
1227                        .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
1228                        .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
1229                        .start();
1230            }
1231        } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
1232                && statusBarState == StatusBarState.KEYGUARD) {
1233            mKeyguardStatusView.animate().cancel();
1234            mKeyguardStatusView.setVisibility(View.VISIBLE);
1235            mKeyguardStatusViewAnimating = true;
1236            mKeyguardStatusView.setAlpha(0f);
1237            mKeyguardStatusView.animate()
1238                    .alpha(1f)
1239                    .setStartDelay(0)
1240                    .setDuration(320)
1241                    .setInterpolator(PhoneStatusBar.ALPHA_IN)
1242                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
1243        } else if (statusBarState == StatusBarState.KEYGUARD) {
1244            mKeyguardStatusView.animate().cancel();
1245            mKeyguardStatusViewAnimating = false;
1246            mKeyguardStatusView.setVisibility(View.VISIBLE);
1247            mKeyguardStatusView.setAlpha(1f);
1248        } else {
1249            mKeyguardStatusView.animate().cancel();
1250            mKeyguardStatusViewAnimating = false;
1251            mKeyguardStatusView.setVisibility(View.GONE);
1252            mKeyguardStatusView.setAlpha(1f);
1253        }
1254    }
1255
1256    private void updateQsState() {
1257        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
1258        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
1259                ? View.VISIBLE
1260                : View.INVISIBLE);
1261        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
1262                || (mQsExpanded && !mStackScrollerOverscrolling));
1263        mNotificationStackScroller.setScrollingEnabled(
1264                mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
1265                        || mQsExpansionFromOverscroll));
1266        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
1267        mQsContainer.setVisibility(
1268                mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
1269        mScrollView.setTouchEnabled(mQsExpanded);
1270        updateEmptyShadeView();
1271        mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
1272                && !mStackScrollerOverscrolling && mQsScrimEnabled
1273                        ? View.VISIBLE
1274                        : View.INVISIBLE);
1275        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
1276            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
1277        }
1278    }
1279
1280    private void setQsExpansion(float height) {
1281        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
1282        mQsFullyExpanded = height == mQsMaxExpansionHeight;
1283        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
1284            setQsExpanded(true);
1285        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
1286            setQsExpanded(false);
1287            if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) {
1288                announceForAccessibility(getKeyguardOrLockScreenString());
1289                mLastAnnouncementWasQuickSettings = false;
1290            }
1291        }
1292        mQsExpansionHeight = height;
1293        mHeader.setExpansion(getHeaderExpansionFraction());
1294        setQsTranslation(height);
1295        requestScrollerTopPaddingUpdate(false /* animate */);
1296        updateNotificationScrim(height);
1297        if (mKeyguardShowing) {
1298            updateHeaderKeyguard();
1299        }
1300        if (mStatusBarState == StatusBarState.SHADE_LOCKED
1301                || mStatusBarState == StatusBarState.KEYGUARD) {
1302            updateKeyguardBottomAreaAlpha();
1303        }
1304        if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
1305                && !mStackScrollerOverscrolling && mQsScrimEnabled) {
1306            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
1307        }
1308
1309        // Upon initialisation when we are not layouted yet we don't want to announce that we are
1310        // fully expanded, hence the != 0.0f check.
1311        if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
1312            announceForAccessibility(getContext().getString(
1313                    R.string.accessibility_desc_quick_settings));
1314            mLastAnnouncementWasQuickSettings = true;
1315        }
1316        if (DEBUG) {
1317            invalidate();
1318        }
1319    }
1320
1321    private String getKeyguardOrLockScreenString() {
1322        if (mStatusBarState == StatusBarState.KEYGUARD) {
1323            return getContext().getString(R.string.accessibility_desc_lock_screen);
1324        } else {
1325            return getContext().getString(R.string.accessibility_desc_notification_shade);
1326        }
1327    }
1328
1329    private void updateNotificationScrim(float height) {
1330        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
1331        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
1332        progress = Math.max(0.0f, Math.min(progress, 1.0f));
1333    }
1334
1335    private float getHeaderExpansionFraction() {
1336        if (!mKeyguardShowing) {
1337            return getQsExpansionFraction();
1338        } else {
1339            return 1f;
1340        }
1341    }
1342
1343    private void setQsTranslation(float height) {
1344        if (!mHeaderAnimating) {
1345            mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
1346        }
1347        if (mKeyguardShowing && !mHeaderAnimating) {
1348            mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
1349        }
1350    }
1351
1352    private float calculateQsTopPadding() {
1353        if (mKeyguardShowing
1354                && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
1355
1356            // Either QS pushes the notifications down when fully expanded, or QS is fully above the
1357            // notifications (mostly on tablets). maxNotifications denotes the normal top padding
1358            // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to
1359            // take the maximum and linearly interpolate with the panel expansion for a nice motion.
1360            int maxNotifications = mClockPositionResult.stackScrollerPadding
1361                    - mClockPositionResult.stackScrollerPaddingAdjustment
1362                    - mNotificationTopPadding;
1363            int maxQs = getTempQsMaxExpansion();
1364            int max = mStatusBarState == StatusBarState.KEYGUARD
1365                    ? Math.max(maxNotifications, maxQs)
1366                    : maxQs;
1367            return (int) interpolate(getExpandedFraction(),
1368                    mQsMinExpansionHeight, max);
1369        } else if (mQsSizeChangeAnimator != null) {
1370            return (int) mQsSizeChangeAnimator.getAnimatedValue();
1371        } else if (mKeyguardShowing && mScrollYOverride == -1) {
1372
1373            // We can only do the smoother transition on Keyguard when we also are not collapsing
1374            // from a scrolled quick settings.
1375            return interpolate(getQsExpansionFraction(),
1376                    mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
1377                    mQsMaxExpansionHeight);
1378        } else {
1379            return mQsExpansionHeight;
1380        }
1381    }
1382
1383    private void requestScrollerTopPaddingUpdate(boolean animate) {
1384        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
1385                mScrollView.getScrollY(),
1386                mAnimateNextTopPaddingChange || animate,
1387                mKeyguardShowing
1388                        && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
1389        mAnimateNextTopPaddingChange = false;
1390    }
1391
1392    private void trackMovement(MotionEvent event) {
1393        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
1394        mLastTouchX = event.getX();
1395        mLastTouchY = event.getY();
1396    }
1397
1398    private void initVelocityTracker() {
1399        if (mVelocityTracker != null) {
1400            mVelocityTracker.recycle();
1401        }
1402        mVelocityTracker = VelocityTracker.obtain();
1403    }
1404
1405    private float getCurrentVelocity() {
1406        if (mVelocityTracker == null) {
1407            return 0;
1408        }
1409        mVelocityTracker.computeCurrentVelocity(1000);
1410        return mVelocityTracker.getYVelocity();
1411    }
1412
1413    private void cancelQsAnimation() {
1414        if (mQsExpansionAnimator != null) {
1415            mQsExpansionAnimator.cancel();
1416        }
1417    }
1418
1419    private void flingSettings(float vel, boolean expand) {
1420        flingSettings(vel, expand, null, false /* isClick */);
1421    }
1422
1423    private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable,
1424            boolean isClick) {
1425        float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
1426        if (target == mQsExpansionHeight) {
1427            mScrollYOverride = -1;
1428            if (onFinishRunnable != null) {
1429                onFinishRunnable.run();
1430            }
1431            return;
1432        }
1433        boolean belowFalsingThreshold = isBelowFalsingThreshold();
1434        if (belowFalsingThreshold) {
1435            vel = 0;
1436        }
1437        mScrollView.setBlockFlinging(true);
1438        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
1439        if (isClick) {
1440            animator.setInterpolator(mTouchResponseInterpolator);
1441            animator.setDuration(368);
1442        } else {
1443            mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
1444        }
1445        if (belowFalsingThreshold) {
1446            animator.setDuration(350);
1447        }
1448        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1449            @Override
1450            public void onAnimationUpdate(ValueAnimator animation) {
1451                setQsExpansion((Float) animation.getAnimatedValue());
1452            }
1453        });
1454        animator.addListener(new AnimatorListenerAdapter() {
1455            @Override
1456            public void onAnimationEnd(Animator animation) {
1457                mScrollView.setBlockFlinging(false);
1458                mScrollYOverride = -1;
1459                mQsExpansionAnimator = null;
1460                if (onFinishRunnable != null) {
1461                    onFinishRunnable.run();
1462                }
1463            }
1464        });
1465        animator.start();
1466        mQsExpansionAnimator = animator;
1467        mQsAnimatorExpand = expand;
1468    }
1469
1470    /**
1471     * @return Whether we should intercept a gesture to open Quick Settings.
1472     */
1473    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
1474        if (!mQsExpansionEnabled || mCollapsedOnDown) {
1475            return false;
1476        }
1477        View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
1478        boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
1479                && y >= header.getTop() && y <= header.getBottom();
1480        if (mQsExpanded) {
1481            return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
1482        } else {
1483            return onHeader;
1484        }
1485    }
1486
1487    @Override
1488    protected boolean isScrolledToBottom() {
1489        if (!isInSettings()) {
1490            return mStatusBar.getBarState() == StatusBarState.KEYGUARD
1491                    || mNotificationStackScroller.isScrolledToBottom();
1492        } else {
1493            return mScrollView.isScrolledToBottom();
1494        }
1495    }
1496
1497    @Override
1498    protected int getMaxPanelHeight() {
1499        int min = mStatusBarMinHeight;
1500        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
1501                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
1502            int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
1503                    * HEADER_RUBBERBAND_FACTOR);
1504            min = Math.max(min, minHeight);
1505        }
1506        int maxHeight;
1507        if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
1508            maxHeight = calculatePanelHeightQsExpanded();
1509        } else {
1510            maxHeight = calculatePanelHeightShade();
1511        }
1512        maxHeight = Math.max(maxHeight, min);
1513        return maxHeight;
1514    }
1515
1516    private boolean isInSettings() {
1517        return mQsExpanded;
1518    }
1519
1520    @Override
1521    protected void onHeightUpdated(float expandedHeight) {
1522        if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
1523            positionClockAndNotifications();
1524        }
1525        if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
1526                && !mQsExpansionFromOverscroll) {
1527            float t;
1528            if (mKeyguardShowing) {
1529
1530                // On Keyguard, interpolate the QS expansion linearly to the panel expansion
1531                t = expandedHeight / getMaxPanelHeight();
1532            } else {
1533
1534                // In Shade, interpolate linearly such that QS is closed whenever panel height is
1535                // minimum QS expansion + minStackHeight
1536                float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
1537                        + mNotificationStackScroller.getMinStackHeight();
1538                float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
1539                t = (expandedHeight - panelHeightQsCollapsed)
1540                        / (panelHeightQsExpanded - panelHeightQsCollapsed);
1541            }
1542            setQsExpansion(mQsMinExpansionHeight
1543                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
1544        }
1545        updateStackHeight(expandedHeight);
1546        updateHeader();
1547        updateUnlockIcon();
1548        updateNotificationTranslucency();
1549        updatePanelExpanded();
1550        mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed());
1551        if (DEBUG) {
1552            invalidate();
1553        }
1554    }
1555
1556    private void updatePanelExpanded() {
1557        boolean isExpanded = !isFullyCollapsed();
1558        if (mPanelExpanded != isExpanded) {
1559            mHeadsUpManager.setIsExpanded(isExpanded);
1560            mStatusBar.setPanelExpanded(isExpanded);
1561            mPanelExpanded = isExpanded;
1562        }
1563    }
1564
1565    /**
1566     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
1567     *         collapsing QS / the panel when QS was scrolled
1568     */
1569    private int getTempQsMaxExpansion() {
1570        int qsTempMaxExpansion = mQsMaxExpansionHeight;
1571        if (mScrollYOverride != -1) {
1572            qsTempMaxExpansion -= mScrollYOverride;
1573        }
1574        return qsTempMaxExpansion;
1575    }
1576
1577    private int calculatePanelHeightShade() {
1578        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
1579        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
1580                - mTopPaddingAdjustment;
1581        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
1582        return maxHeight;
1583    }
1584
1585    private int calculatePanelHeightQsExpanded() {
1586        float notificationHeight = mNotificationStackScroller.getHeight()
1587                - mNotificationStackScroller.getEmptyBottomMargin()
1588                - mNotificationStackScroller.getTopPadding();
1589
1590        // When only empty shade view is visible in QS collapsed state, simulate that we would have
1591        // it in expanded QS state as well so we don't run into troubles when fading the view in/out
1592        // and expanding/collapsing the whole panel from/to quick settings.
1593        if (mNotificationStackScroller.getNotGoneChildCount() == 0
1594                && mShadeEmpty) {
1595            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight()
1596                    + mNotificationStackScroller.getBottomStackPeekSize()
1597                    + mNotificationStackScroller.getCollapseSecondCardPadding();
1598        }
1599        int maxQsHeight = mQsMaxExpansionHeight;
1600
1601        // If an animation is changing the size of the QS panel, take the animated value.
1602        if (mQsSizeChangeAnimator != null) {
1603            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
1604        }
1605        float totalHeight = Math.max(
1606                maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(),
1607                mStatusBarState == StatusBarState.KEYGUARD
1608                        ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
1609                        : 0)
1610                + notificationHeight;
1611        if (totalHeight > mNotificationStackScroller.getHeight()) {
1612            float fullyCollapsedHeight = maxQsHeight
1613                    + mNotificationStackScroller.getMinStackHeight()
1614                    + mNotificationStackScroller.getNotificationTopPadding()
1615                    - getScrollViewScrollY();
1616            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
1617        }
1618        return (int) totalHeight;
1619    }
1620
1621    private int getScrollViewScrollY() {
1622        if (mScrollYOverride != -1 && !mQsTracking) {
1623            return mScrollYOverride;
1624        } else {
1625            return mScrollView.getScrollY();
1626        }
1627    }
1628    private void updateNotificationTranslucency() {
1629        float alpha = 1f;
1630        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
1631            alpha = getFadeoutAlpha();
1632        }
1633        mNotificationStackScroller.setAlpha(alpha);
1634    }
1635
1636    private float getFadeoutAlpha() {
1637        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
1638                / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
1639                - mNotificationStackScroller.getCollapseSecondCardPadding());
1640        alpha = Math.max(0, Math.min(alpha, 1));
1641        alpha = (float) Math.pow(alpha, 0.75);
1642        return alpha;
1643    }
1644
1645    @Override
1646    protected float getOverExpansionAmount() {
1647        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
1648    }
1649
1650    @Override
1651    protected float getOverExpansionPixels() {
1652        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
1653    }
1654
1655    private void updateUnlockIcon() {
1656        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1657                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1658            boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
1659            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1660            if (active && !mUnlockIconActive && mTracking) {
1661                lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
1662                lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
1663                        mFastOutLinearInterpolator);
1664            } else if (!active && mUnlockIconActive && mTracking) {
1665                lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */,
1666                        150, mFastOutLinearInterpolator, null);
1667                lockIcon.setImageScale(1.0f, true, 150,
1668                        mFastOutLinearInterpolator);
1669            }
1670            mUnlockIconActive = active;
1671        }
1672    }
1673
1674    /**
1675     * Hides the header when notifications are colliding with it.
1676     */
1677    private void updateHeader() {
1678        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1679            updateHeaderKeyguard();
1680        } else {
1681            updateHeaderShade();
1682        }
1683
1684    }
1685
1686    private void updateHeaderShade() {
1687        if (!mHeaderAnimating) {
1688            mHeader.setTranslationY(getHeaderTranslation());
1689        }
1690        setQsTranslation(mQsExpansionHeight);
1691    }
1692
1693    private float getHeaderTranslation() {
1694        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1695            return 0;
1696        }
1697        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1698            if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
1699                return 0;
1700            } else {
1701                return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
1702            }
1703        }
1704        float stackTranslation = mNotificationStackScroller.getStackTranslation();
1705        float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR;
1706        if (mHeadsUpManager.hasPinnedHeadsUp() || mIsExpansionFromHeadsUp) {
1707            translation = mNotificationStackScroller.getTopPadding() + stackTranslation
1708                    - mNotificationTopPadding - mQsMinExpansionHeight;
1709        }
1710        return Math.min(0, translation);
1711    }
1712
1713    /**
1714     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
1715     *         during swiping up
1716     */
1717    private float getKeyguardContentsAlpha() {
1718        float alpha;
1719        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1720
1721            // When on Keyguard, we hide the header as soon as the top card of the notification
1722            // stack scroller is close enough (collision distance) to the bottom of the header.
1723            alpha = getNotificationsTopY()
1724                    /
1725                    (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
1726        } else {
1727
1728            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
1729            // soon as we start translating the stack.
1730            alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
1731        }
1732        alpha = MathUtils.constrain(alpha, 0, 1);
1733        alpha = (float) Math.pow(alpha, 0.75);
1734        return alpha;
1735    }
1736
1737    private void updateHeaderKeyguardAlpha() {
1738        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
1739        mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
1740                * mKeyguardStatusBarAnimateAlpha);
1741        mKeyguardStatusBar.setVisibility(mKeyguardStatusBar.getAlpha() != 0f
1742                && !mDozing ? VISIBLE : INVISIBLE);
1743    }
1744
1745    private void updateHeaderKeyguard() {
1746        updateHeaderKeyguardAlpha();
1747        setQsTranslation(mQsExpansionHeight);
1748    }
1749
1750    private void updateKeyguardBottomAreaAlpha() {
1751        float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction());
1752        mKeyguardBottomArea.setAlpha(alpha);
1753        mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
1754                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
1755                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
1756    }
1757
1758    private float getNotificationsTopY() {
1759        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1760            return getExpandedHeight();
1761        }
1762        return mNotificationStackScroller.getNotificationsTopY();
1763    }
1764
1765    @Override
1766    protected void onExpandingStarted() {
1767        super.onExpandingStarted();
1768        mNotificationStackScroller.onExpansionStarted();
1769        mIsExpanding = true;
1770        mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
1771        if (mQsExpanded) {
1772            onQsExpansionStarted();
1773        }
1774    }
1775
1776    @Override
1777    protected void onExpandingFinished() {
1778        super.onExpandingFinished();
1779        mNotificationStackScroller.onExpansionStopped();
1780        mHeadsUpManager.onExpandingFinished();
1781        mIsExpanding = false;
1782        mScrollYOverride = -1;
1783        if (isFullyCollapsed()) {
1784            DejankUtils.postAfterTraversal(new Runnable() {
1785                @Override
1786                public void run() {
1787                    setListening(false);
1788                }
1789            });
1790
1791            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
1792            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
1793            // ahead with rendering and we jank.
1794            postOnAnimation(new Runnable() {
1795                @Override
1796                public void run() {
1797                    getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
1798                }
1799            });
1800        } else {
1801            setListening(true);
1802        }
1803        mQsExpandImmediate = false;
1804        mTwoFingerQsExpandPossible = false;
1805        mIsExpansionFromHeadsUp = false;
1806        mNotificationStackScroller.setTrackingHeadsUp(false);
1807        mExpandingFromHeadsUp = false;
1808        setPanelScrimMinFraction(0.0f);
1809    }
1810
1811    private void setListening(boolean listening) {
1812        mHeader.setListening(listening);
1813        mKeyguardStatusBar.setListening(listening);
1814        mQsPanel.setListening(listening);
1815    }
1816
1817    @Override
1818    public void instantExpand() {
1819        super.instantExpand();
1820        setListening(true);
1821    }
1822
1823    @Override
1824    protected void setOverExpansion(float overExpansion, boolean isPixels) {
1825        if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
1826            return;
1827        }
1828        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1829            mNotificationStackScroller.setOnHeightChangedListener(null);
1830            if (isPixels) {
1831                mNotificationStackScroller.setOverScrolledPixels(
1832                        overExpansion, true /* onTop */, false /* animate */);
1833            } else {
1834                mNotificationStackScroller.setOverScrollAmount(
1835                        overExpansion, true /* onTop */, false /* animate */);
1836            }
1837            mNotificationStackScroller.setOnHeightChangedListener(this);
1838        }
1839    }
1840
1841    @Override
1842    protected void onTrackingStarted() {
1843        super.onTrackingStarted();
1844        if (mQsFullyExpanded) {
1845            mQsExpandImmediate = true;
1846        }
1847        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1848                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1849            mAfforanceHelper.animateHideLeftRightIcon();
1850        }
1851        mNotificationStackScroller.onPanelTrackingStarted();
1852    }
1853
1854    @Override
1855    protected void onTrackingStopped(boolean expand) {
1856        super.onTrackingStopped(expand);
1857        if (expand) {
1858            mNotificationStackScroller.setOverScrolledPixels(
1859                    0.0f, true /* onTop */, true /* animate */);
1860        }
1861        mNotificationStackScroller.onPanelTrackingStopped();
1862        if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1863                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1864            if (!mHintAnimationRunning) {
1865                mAfforanceHelper.reset(true);
1866            }
1867        }
1868        if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1869                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1870            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1871            lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
1872            lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
1873        }
1874    }
1875
1876    @Override
1877    public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
1878
1879        // Block update if we are in quick settings and just the top padding changed
1880        // (i.e. view == null).
1881        if (view == null && mQsExpanded) {
1882            return;
1883        }
1884        requestPanelHeightUpdate();
1885    }
1886
1887    @Override
1888    public void onReset(ExpandableView view) {
1889    }
1890
1891    @Override
1892    public void onScrollChanged() {
1893        if (mQsExpanded) {
1894            requestScrollerTopPaddingUpdate(false /* animate */);
1895            requestPanelHeightUpdate();
1896        }
1897    }
1898
1899    @Override
1900    protected void onConfigurationChanged(Configuration newConfig) {
1901        super.onConfigurationChanged(newConfig);
1902        mAfforanceHelper.onConfigurationChanged();
1903        if (newConfig.orientation != mLastOrientation) {
1904            resetVerticalPanelPosition();
1905        }
1906        mLastOrientation = newConfig.orientation;
1907    }
1908
1909    @Override
1910    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1911        mNavigationBarBottomHeight = insets.getSystemWindowInsetBottom();
1912        updateMaxHeadsUpTranslation();
1913        return insets;
1914    }
1915
1916    private void updateMaxHeadsUpTranslation() {
1917        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
1918    }
1919
1920    @Override
1921    public void onRtlPropertiesChanged(int layoutDirection) {
1922        if (layoutDirection != mOldLayoutDirection) {
1923            mAfforanceHelper.onRtlPropertiesChanged();
1924            mOldLayoutDirection = layoutDirection;
1925        }
1926    }
1927
1928    @Override
1929    public void onClick(View v) {
1930        if (v == mHeader) {
1931            onQsExpansionStarted();
1932            if (mQsExpanded) {
1933                flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
1934            } else if (mQsExpansionEnabled) {
1935                EventLogTags.writeSysuiLockscreenGesture(
1936                        EventLogConstants.SYSUI_TAP_TO_OPEN_QS,
1937                        0, 0);
1938                flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
1939            }
1940        }
1941    }
1942
1943    @Override
1944    public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
1945        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1946        mIsLaunchTransitionRunning = true;
1947        mLaunchAnimationEndRunnable = null;
1948        float displayDensity = mStatusBar.getDisplayDensity();
1949        int lengthDp = Math.abs((int) (translation / displayDensity));
1950        int velocityDp = Math.abs((int) (vel / displayDensity));
1951        if (start) {
1952            EventLogTags.writeSysuiLockscreenGesture(
1953                    EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
1954            mKeyguardBottomArea.launchLeftAffordance();
1955        } else {
1956            EventLogTags.writeSysuiLockscreenGesture(
1957                    EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
1958            mSecureCameraLaunchManager.startSecureCameraLaunch();
1959        }
1960        mStatusBar.startLaunchTransitionTimeout();
1961        mBlockTouches = true;
1962    }
1963
1964    @Override
1965    public void onAnimationToSideEnded() {
1966        mIsLaunchTransitionRunning = false;
1967        mIsLaunchTransitionFinished = true;
1968        if (mLaunchAnimationEndRunnable != null) {
1969            mLaunchAnimationEndRunnable.run();
1970            mLaunchAnimationEndRunnable = null;
1971        }
1972    }
1973
1974    @Override
1975    protected void startUnlockHintAnimation() {
1976        super.startUnlockHintAnimation();
1977        startHighlightIconAnimation(getCenterIcon());
1978    }
1979
1980    /**
1981     * Starts the highlight (making it fully opaque) animation on an icon.
1982     */
1983    private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1984        icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1985                mFastOutSlowInInterpolator, new Runnable() {
1986                    @Override
1987                    public void run() {
1988                        icon.setImageAlpha(icon.getRestingAlpha(),
1989                                true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1990                                mFastOutSlowInInterpolator, null);
1991                    }
1992                });
1993    }
1994
1995    @Override
1996    public float getMaxTranslationDistance() {
1997        return (float) Math.hypot(getWidth(), getHeight());
1998    }
1999
2000    @Override
2001    public void onSwipingStarted(boolean rightIcon) {
2002        boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
2003                : rightIcon;
2004        if (camera) {
2005            mSecureCameraLaunchManager.onSwipingStarted();
2006            mKeyguardBottomArea.bindCameraPrewarmService();
2007        }
2008        requestDisallowInterceptTouchEvent(true);
2009        mOnlyAffordanceInThisMotion = true;
2010        mQsTracking = false;
2011    }
2012
2013    @Override
2014    public void onSwipingAborted() {
2015        mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
2016    }
2017
2018    @Override
2019    public void onIconClicked(boolean rightIcon) {
2020        if (mHintAnimationRunning) {
2021            return;
2022        }
2023        mHintAnimationRunning = true;
2024        mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
2025            @Override
2026            public void run() {
2027                mHintAnimationRunning = false;
2028                mStatusBar.onHintFinished();
2029            }
2030        });
2031        rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
2032        if (rightIcon) {
2033            mStatusBar.onCameraHintStarted();
2034        } else {
2035            if (mKeyguardBottomArea.isLeftVoiceAssist()) {
2036                mStatusBar.onVoiceAssistHintStarted();
2037            } else {
2038                mStatusBar.onPhoneHintStarted();
2039            }
2040        }
2041    }
2042
2043    @Override
2044    public KeyguardAffordanceView getLeftIcon() {
2045        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2046                ? mKeyguardBottomArea.getRightView()
2047                : mKeyguardBottomArea.getLeftView();
2048    }
2049
2050    @Override
2051    public KeyguardAffordanceView getCenterIcon() {
2052        return mKeyguardBottomArea.getLockIcon();
2053    }
2054
2055    @Override
2056    public KeyguardAffordanceView getRightIcon() {
2057        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2058                ? mKeyguardBottomArea.getLeftView()
2059                : mKeyguardBottomArea.getRightView();
2060    }
2061
2062    @Override
2063    public View getLeftPreview() {
2064        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2065                ? mKeyguardBottomArea.getRightPreview()
2066                : mKeyguardBottomArea.getLeftPreview();
2067    }
2068
2069    @Override
2070    public View getRightPreview() {
2071        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2072                ? mKeyguardBottomArea.getLeftPreview()
2073                : mKeyguardBottomArea.getRightPreview();
2074    }
2075
2076    @Override
2077    public float getAffordanceFalsingFactor() {
2078        return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
2079    }
2080
2081    @Override
2082    protected float getPeekHeight() {
2083        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
2084            return mNotificationStackScroller.getPeekHeight();
2085        } else {
2086            return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
2087        }
2088    }
2089
2090    @Override
2091    protected float getCannedFlingDurationFactor() {
2092        if (mQsExpanded) {
2093            return 0.7f;
2094        } else {
2095            return 0.6f;
2096        }
2097    }
2098
2099    @Override
2100    protected boolean fullyExpandedClearAllVisible() {
2101        return mNotificationStackScroller.isDismissViewNotGone()
2102                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
2103    }
2104
2105    @Override
2106    protected boolean isClearAllVisible() {
2107        return mNotificationStackScroller.isDismissViewVisible();
2108    }
2109
2110    @Override
2111    protected int getClearAllHeight() {
2112        return mNotificationStackScroller.getDismissViewHeight();
2113    }
2114
2115    @Override
2116    protected boolean isTrackingBlocked() {
2117        return mConflictingQsExpansionGesture && mQsExpanded;
2118    }
2119
2120    public void notifyVisibleChildrenChanged() {
2121        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
2122            mReserveNotificationSpace.setVisibility(View.VISIBLE);
2123        } else {
2124            mReserveNotificationSpace.setVisibility(View.GONE);
2125        }
2126    }
2127
2128    public boolean isQsExpanded() {
2129        return mQsExpanded;
2130    }
2131
2132    public boolean isQsDetailShowing() {
2133        return mQsPanel.isShowingDetail();
2134    }
2135
2136    public void closeQsDetail() {
2137        mQsPanel.closeDetail();
2138    }
2139
2140    @Override
2141    public boolean shouldDelayChildPressedState() {
2142        return true;
2143    }
2144
2145    public boolean isLaunchTransitionFinished() {
2146        return mIsLaunchTransitionFinished;
2147    }
2148
2149    public boolean isLaunchTransitionRunning() {
2150        return mIsLaunchTransitionRunning;
2151    }
2152
2153    public void setLaunchTransitionEndRunnable(Runnable r) {
2154        mLaunchAnimationEndRunnable = r;
2155    }
2156
2157    public void setEmptyDragAmount(float amount) {
2158        float factor = 0.8f;
2159        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
2160            factor = 0.4f;
2161        } else if (!mStatusBar.hasActiveNotifications()) {
2162            factor = 0.4f;
2163        }
2164        mEmptyDragAmount = amount * factor;
2165        positionClockAndNotifications();
2166    }
2167
2168    private static float interpolate(float t, float start, float end) {
2169        return (1 - t) * start + t * end;
2170    }
2171
2172    public void setDozing(boolean dozing, boolean animate) {
2173        if (dozing == mDozing) return;
2174        mDozing = dozing;
2175        if (mStatusBarState == StatusBarState.KEYGUARD) {
2176            updateDozingVisibilities(animate);
2177        }
2178    }
2179
2180    private void updateDozingVisibilities(boolean animate) {
2181        if (mDozing) {
2182            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
2183            mKeyguardBottomArea.setVisibility(View.INVISIBLE);
2184        } else {
2185            mKeyguardBottomArea.setVisibility(View.VISIBLE);
2186            mKeyguardStatusBar.setVisibility(View.VISIBLE);
2187            if (animate) {
2188                animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION);
2189                mKeyguardBottomArea.startFinishDozeAnimation();
2190            }
2191        }
2192    }
2193
2194    @Override
2195    public boolean isDozing() {
2196        return mDozing;
2197    }
2198
2199    public void setShadeEmpty(boolean shadeEmpty) {
2200        mShadeEmpty = shadeEmpty;
2201        updateEmptyShadeView();
2202    }
2203
2204    private void updateEmptyShadeView() {
2205
2206        // Hide "No notifications" in QS.
2207        mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
2208    }
2209
2210    public void setQsScrimEnabled(boolean qsScrimEnabled) {
2211        boolean changed = mQsScrimEnabled != qsScrimEnabled;
2212        mQsScrimEnabled = qsScrimEnabled;
2213        if (changed) {
2214            updateQsState();
2215        }
2216    }
2217
2218    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
2219        mKeyguardUserSwitcher = keyguardUserSwitcher;
2220    }
2221
2222    private final Runnable mUpdateHeader = new Runnable() {
2223        @Override
2224        public void run() {
2225            mHeader.updateEverything();
2226        }
2227    };
2228
2229    public void onScreenTurningOn() {
2230        mKeyguardStatusView.refreshTime();
2231    }
2232
2233    @Override
2234    public void onEmptySpaceClicked(float x, float y) {
2235        onEmptySpaceClick(x);
2236    }
2237
2238    protected boolean onMiddleClicked() {
2239        switch (mStatusBar.getBarState()) {
2240            case StatusBarState.KEYGUARD:
2241                if (!mDozingOnDown) {
2242                    EventLogTags.writeSysuiLockscreenGesture(
2243                            EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
2244                            0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
2245                    startUnlockHintAnimation();
2246                }
2247                return true;
2248            case StatusBarState.SHADE_LOCKED:
2249                if (!mQsExpanded) {
2250                    mStatusBar.goToKeyguard();
2251                }
2252                return true;
2253            case StatusBarState.SHADE:
2254
2255                // This gets called in the middle of the touch handling, where the state is still
2256                // that we are tracking the panel. Collapse the panel after this is done.
2257                post(mPostCollapseRunnable);
2258                return false;
2259            default:
2260                return true;
2261        }
2262    }
2263
2264    @Override
2265    protected void dispatchDraw(Canvas canvas) {
2266        super.dispatchDraw(canvas);
2267        if (DEBUG) {
2268            Paint p = new Paint();
2269            p.setColor(Color.RED);
2270            p.setStrokeWidth(2);
2271            p.setStyle(Paint.Style.STROKE);
2272            canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
2273            p.setColor(Color.BLUE);
2274            canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
2275            p.setColor(Color.GREEN);
2276            canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
2277                    calculatePanelHeightQsExpanded(), p);
2278            p.setColor(Color.YELLOW);
2279            canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
2280                    calculatePanelHeightShade(), p);
2281            p.setColor(Color.MAGENTA);
2282            canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
2283                    calculateQsTopPadding(), p);
2284            p.setColor(Color.CYAN);
2285            canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
2286                    mNotificationStackScroller.getTopPadding(), p);
2287        }
2288    }
2289
2290    @Override
2291    public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
2292        if (inPinnedMode) {
2293            mHeadsUpExistenceChangedRunnable.run();
2294            updateNotificationTranslucency();
2295        } else {
2296            mHeadsUpAnimatingAway = true;
2297            mNotificationStackScroller.runAfterAnimationFinished(
2298                    mHeadsUpExistenceChangedRunnable);
2299        }
2300    }
2301
2302    @Override
2303    public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
2304        mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
2305    }
2306
2307    @Override
2308    public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
2309    }
2310
2311    @Override
2312    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
2313        mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
2314    }
2315
2316    @Override
2317    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2318        super.setHeadsUpManager(headsUpManager);
2319        mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
2320                this);
2321    }
2322
2323    public void setTrackingHeadsUp(boolean tracking) {
2324        if (tracking) {
2325            mNotificationStackScroller.setTrackingHeadsUp(true);
2326            mExpandingFromHeadsUp = true;
2327        }
2328        // otherwise we update the state when the expansion is finished
2329    }
2330
2331    @Override
2332    protected void onClosingFinished() {
2333        super.onClosingFinished();
2334        resetVerticalPanelPosition();
2335        setClosingWithAlphaFadeout(false);
2336    }
2337
2338    private void setClosingWithAlphaFadeout(boolean closing) {
2339        mClosingWithAlphaFadeOut = closing;
2340        mNotificationStackScroller.forceNoOverlappingRendering(closing);
2341    }
2342
2343    /**
2344     * Updates the vertical position of the panel so it is positioned closer to the touch
2345     * responsible for opening the panel.
2346     *
2347     * @param x the x-coordinate the touch event
2348     */
2349    private void updateVerticalPanelPosition(float x) {
2350        if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
2351            resetVerticalPanelPosition();
2352            return;
2353        }
2354        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
2355        float rightMost = getWidth() - mPositionMinSideMargin
2356                - mNotificationStackScroller.getWidth() / 2;
2357        if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
2358            x = getWidth() / 2;
2359        }
2360        x = Math.min(rightMost, Math.max(leftMost, x));
2361        setVerticalPanelTranslation(x -
2362                (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
2363     }
2364
2365    private void resetVerticalPanelPosition() {
2366        setVerticalPanelTranslation(0f);
2367    }
2368
2369    private void setVerticalPanelTranslation(float translation) {
2370        mNotificationStackScroller.setTranslationX(translation);
2371        mScrollView.setTranslationX(translation);
2372        mHeader.setTranslationX(translation);
2373    }
2374
2375    private void updateStackHeight(float stackHeight) {
2376        mNotificationStackScroller.setStackHeight(stackHeight);
2377        updateKeyguardBottomAreaAlpha();
2378    }
2379
2380    public void setPanelScrimMinFraction(float minFraction) {
2381        mBar.panelScrimMinFractionChanged(minFraction);
2382    }
2383
2384    public void clearNotificattonEffects() {
2385        mStatusBar.clearNotificationEffects();
2386    }
2387
2388    protected boolean isPanelVisibleBecauseOfHeadsUp() {
2389        return mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
2390    }
2391}
2392