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