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