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