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