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