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 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.app.ActivityManager;
24 import android.app.Fragment;
25 import android.app.StatusBarManager;
26 import android.content.Context;
27 import android.content.pm.ResolveInfo;
28 import android.content.res.Configuration;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.Paint;
32 import android.graphics.Rect;
33 import android.util.AttributeSet;
34 import android.util.FloatProperty;
35 import android.util.MathUtils;
36 import android.view.MotionEvent;
37 import android.view.VelocityTracker;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.ViewTreeObserver;
41 import android.view.WindowInsets;
42 import android.view.accessibility.AccessibilityEvent;
43 import android.widget.FrameLayout;
44 import android.widget.TextView;
45 
46 import com.android.internal.logging.MetricsLogger;
47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
48 import com.android.keyguard.KeyguardStatusView;
49 import com.android.systemui.DejankUtils;
50 import com.android.systemui.Interpolators;
51 import com.android.systemui.R;
52 import com.android.systemui.classifier.FalsingManager;
53 import com.android.systemui.fragments.FragmentHostManager;
54 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
55 import com.android.systemui.plugins.qs.QS;
56 import com.android.systemui.statusbar.ExpandableNotificationRow;
57 import com.android.systemui.statusbar.ExpandableView;
58 import com.android.systemui.statusbar.FlingAnimationUtils;
59 import com.android.systemui.statusbar.GestureRecorder;
60 import com.android.systemui.statusbar.KeyguardAffordanceView;
61 import com.android.systemui.statusbar.NotificationData;
62 import com.android.systemui.statusbar.StatusBarState;
63 import com.android.systemui.statusbar.notification.NotificationUtils;
64 import com.android.systemui.statusbar.policy.HeadsUpManager;
65 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
66 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
67 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
68 import com.android.systemui.statusbar.stack.StackStateAnimator;
69 
70 import java.util.List;
71 
72 public 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 
NotificationPanelView(Context context, AttributeSet attrs)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 
setStatusBar(StatusBar bar)241     public void setStatusBar(StatusBar bar) {
242         mStatusBar = bar;
243     }
244 
245     @Override
onFinishInflate()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
onAttachedToWindow()269     protected void onAttachedToWindow() {
270         super.onAttachedToWindow();
271         FragmentHostManager.get(this).addTagListener(QS.TAG, mFragmentListener);
272     }
273 
274     @Override
onDetachedFromWindow()275     protected void onDetachedFromWindow() {
276         super.onDetachedFromWindow();
277         FragmentHostManager.get(this).removeTagListener(QS.TAG, mFragmentListener);
278     }
279 
280     @Override
loadDimens()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 
updateResources()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
onLayout(boolean changed, int left, int top, int right, int bottom)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 
setIsFullWidth(boolean isFullWidth)364     private void setIsFullWidth(boolean isFullWidth) {
365         mIsFullWidth = isFullWidth;
366         mNotificationStackScroller.setIsFullWidth(isFullWidth);
367     }
368 
startQsSizeChangeAnimation(int oldHeight, final int newHeight)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      */
positionClockAndNotifications()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      */
computeMaxKeyguardNotifications(int maximum)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 
startClockAnimation(int y)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 
updateClock(float alpha, float scale)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 
animateToFullShade(long delay)519     public void animateToFullShade(long delay) {
520         mAnimateNextTopPaddingChange = true;
521         mNotificationStackScroller.goToFullShade(delay);
522         requestLayout();
523     }
524 
setQsExpansionEnabled(boolean qsExpansionEnabled)525     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
526         mQsExpansionEnabled = qsExpansionEnabled;
527         if (mQs == null) return;
528         mQs.setHeaderClickable(qsExpansionEnabled);
529     }
530 
531     @Override
resetViews()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 
closeQs()548     public void closeQs() {
549         cancelQsAnimation();
550         setQsExpansion(mQsMinExpansionHeight);
551     }
552 
animateCloseQs()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 
openQs()565     public void openQs() {
566         cancelQsAnimation();
567         if (mQsExpansionEnabled) {
568             setQsExpansion(mQsMaxExpansionHeight);
569         }
570     }
571 
expandWithQs()572     public void expandWithQs() {
573         if (mQsExpansionEnabled) {
574             mQsExpandImmediate = true;
575         }
576         expand(true /* animate */);
577     }
578 
579     @Override
fling(float vel, boolean expand)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
flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)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
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)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
onInterceptTouchEvent(MotionEvent event)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 
onQsIntercept(MotionEvent event)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
isInContentBounds(float x, float y)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 
initDownStates(MotionEvent event)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 
flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent)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 
logQsSwipeDown(float y)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 
flingExpandsQs(float vel)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 
isFalseTouch()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 
getQsExpansionFraction()761     private float getQsExpansionFraction() {
762         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
763                 / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
764     }
765 
766     @Override
getOpeningHeight()767     protected float getOpeningHeight() {
768         return mNotificationStackScroller.getOpeningHeight();
769     }
770 
771     @Override
onTouchEvent(MotionEvent event)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 
handleQsTouch(MotionEvent event)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 
isInQsArea(float x, float y)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 
isOpenQsEvent(MotionEvent event)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 
handleQsDown(MotionEvent event)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
flingExpands(float vel, float vectorVel, float x, float y)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
hasConflictingGestures()902     protected boolean hasConflictingGestures() {
903         return mStatusBar.getBarState() != StatusBarState.SHADE;
904     }
905 
906     @Override
shouldGestureIgnoreXTouchSlop(float x, float y)907     protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
908         return !mAffordanceHelper.isOnAffordanceIcon(x, y);
909     }
910 
onQsTouch(MotionEvent event)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 
getFalsingThreshold()972     private int getFalsingThreshold() {
973         float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
974         return (int) (mQsFalsingThreshold * factor);
975     }
976 
977     @Override
onOverscrollTopChanged(float amount, boolean isRubberbanded)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
flingTopOverscroll(float velocity, boolean open)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 
setOverScrolling(boolean overscrolling)1015     private void setOverScrolling(boolean overscrolling) {
1016         mStackScrollerOverscrolling = overscrolling;
1017         if (mQs == null) return;
1018         mQs.setOverscrolling(overscrolling);
1019     }
1020 
onQsExpansionStarted()1021     private void onQsExpansionStarted() {
1022         onQsExpansionStarted(0);
1023     }
1024 
onQsExpansionStarted(int overscrollAmount)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 
setQsExpanded(boolean expanded)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 
setBarState(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)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 
animateKeyguardStatusBarOut()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 
animateKeyguardStatusBarIn(long duration)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 
setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade)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 
setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)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 
updateQsState()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 
setQsExpansion(float height)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 
updateQsExpansion()1281     protected void updateQsExpansion() {
1282         if (mQs == null) return;
1283         mQs.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
1284     }
1285 
getKeyguardOrLockScreenString()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 
calculateQsTopPadding()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 
requestScrollerTopPaddingUpdate(boolean animate)1326     protected void requestScrollerTopPaddingUpdate(boolean animate) {
1327         mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
1328                 mAnimateNextTopPaddingChange || animate,
1329                 mKeyguardShowing
1330                         && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
1331         mAnimateNextTopPaddingChange = false;
1332     }
1333 
trackMovement(MotionEvent event)1334     private void trackMovement(MotionEvent event) {
1335         if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
1336         mLastTouchX = event.getX();
1337         mLastTouchY = event.getY();
1338     }
1339 
initVelocityTracker()1340     private void initVelocityTracker() {
1341         if (mQsVelocityTracker != null) {
1342             mQsVelocityTracker.recycle();
1343         }
1344         mQsVelocityTracker = VelocityTracker.obtain();
1345     }
1346 
getCurrentQSVelocity()1347     private float getCurrentQSVelocity() {
1348         if (mQsVelocityTracker == null) {
1349             return 0;
1350         }
1351         mQsVelocityTracker.computeCurrentVelocity(1000);
1352         return mQsVelocityTracker.getYVelocity();
1353     }
1354 
cancelQsAnimation()1355     private void cancelQsAnimation() {
1356         if (mQsExpansionAnimator != null) {
1357             mQsExpansionAnimator.cancel();
1358         }
1359     }
1360 
flingSettings(float vel, boolean expand)1361     public void flingSettings(float vel, boolean expand) {
1362         flingSettings(vel, expand, null, false /* isClick */);
1363     }
1364 
flingSettings(float vel, boolean expand, final Runnable onFinishRunnable, boolean isClick)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      */
shouldQuickSettingsIntercept(float x, float y, float yDiff)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
isScrolledToBottom()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
getMaxPanelHeight()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 
isInSettings()1458     public boolean isInSettings() {
1459         return mQsExpanded;
1460     }
1461 
isExpanding()1462     public boolean isExpanding() {
1463         return mIsExpanding;
1464     }
1465 
1466     @Override
onHeightUpdated(float expandedHeight)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 
updatePanelExpanded()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      */
getTempQsMaxExpansion()1515     private int getTempQsMaxExpansion() {
1516         return mQsMaxExpansionHeight;
1517     }
1518 
calculatePanelHeightShade()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 
calculatePanelHeightQsExpanded()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 
updateNotificationTranslucency()1558     private void updateNotificationTranslucency() {
1559         float alpha = 1f;
1560         if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
1561             alpha = getFadeoutAlpha();
1562         }
1563         mNotificationStackScroller.setAlpha(alpha);
1564     }
1565 
getFadeoutAlpha()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
getOverExpansionAmount()1575     protected float getOverExpansionAmount() {
1576         return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
1577     }
1578 
1579     @Override
getOverExpansionPixels()1580     protected float getOverExpansionPixels() {
1581         return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
1582     }
1583 
updateUnlockIcon()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      */
updateHeader()1606     private void updateHeader() {
1607         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1608             updateHeaderKeyguardAlpha();
1609         }
1610         updateQsExpansion();
1611     }
1612 
getHeaderTranslation()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      */
getKeyguardContentsAlpha()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 
updateHeaderKeyguardAlpha()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 
updateKeyguardBottomAreaAlpha()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 
getNotificationsTopY()1662     private float getNotificationsTopY() {
1663         if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1664             return getExpandedHeight();
1665         }
1666         return mNotificationStackScroller.getNotificationsTopY();
1667     }
1668 
1669     @Override
onExpandingStarted()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
onExpandingFinished()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 
setListening(boolean listening)1718     private void setListening(boolean listening) {
1719         mKeyguardStatusBar.setListening(listening);
1720         if (mQs == null) return;
1721         mQs.setListening(listening);
1722     }
1723 
1724     @Override
expand(boolean animate)1725     public void expand(boolean animate) {
1726         super.expand(animate);
1727         setListening(true);
1728     }
1729 
1730     @Override
setOverExpansion(float overExpansion, boolean isPixels)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
onTrackingStarted()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
onTrackingStopped(boolean expand)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
onHeightChanged(ExpandableView view, boolean needsAnimation)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
onReset(ExpandableView view)1805     public void onReset(ExpandableView view) {
1806     }
1807 
onQsHeightChanged()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
onConfigurationChanged(Configuration newConfig)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
onApplyWindowInsets(WindowInsets insets)1828     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1829         mNavigationBarBottomHeight = insets.getStableInsetBottom();
1830         updateMaxHeadsUpTranslation();
1831         return insets;
1832     }
1833 
updateMaxHeadsUpTranslation()1834     private void updateMaxHeadsUpTranslation() {
1835         mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
1836     }
1837 
1838     @Override
onRtlPropertiesChanged(int layoutDirection)1839     public void onRtlPropertiesChanged(int layoutDirection) {
1840         if (layoutDirection != mOldLayoutDirection) {
1841             mAffordanceHelper.onRtlPropertiesChanged();
1842             mOldLayoutDirection = layoutDirection;
1843         }
1844     }
1845 
1846     @Override
onClick(View v)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
onAnimationToSideStarted(boolean rightPage, float translation, float vel)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
onAnimationToSideEnded()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
startUnlockHintAnimation()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      */
startHighlightIconAnimation(final KeyguardAffordanceView icon)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
getMaxTranslationDistance()1939     public float getMaxTranslationDistance() {
1940         return (float) Math.hypot(getWidth(), getHeight());
1941     }
1942 
1943     @Override
onSwipingStarted(boolean rightIcon)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
onSwipingAborted()1957     public void onSwipingAborted() {
1958         mFalsingManager.onAffordanceSwipingAborted();
1959         mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
1960     }
1961 
1962     @Override
onIconClicked(boolean rightIcon)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
onUnlockHintFinished()1988     protected void onUnlockHintFinished() {
1989         super.onUnlockHintFinished();
1990         mNotificationStackScroller.setUnlockHintRunning(false);
1991     }
1992 
1993     @Override
onUnlockHintStarted()1994     protected void onUnlockHintStarted() {
1995         super.onUnlockHintStarted();
1996         mNotificationStackScroller.setUnlockHintRunning(true);
1997     }
1998 
1999     @Override
getLeftIcon()2000     public KeyguardAffordanceView getLeftIcon() {
2001         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2002                 ? mKeyguardBottomArea.getRightView()
2003                 : mKeyguardBottomArea.getLeftView();
2004     }
2005 
2006     @Override
getCenterIcon()2007     public KeyguardAffordanceView getCenterIcon() {
2008         return mKeyguardBottomArea.getLockIcon();
2009     }
2010 
2011     @Override
getRightIcon()2012     public KeyguardAffordanceView getRightIcon() {
2013         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2014                 ? mKeyguardBottomArea.getLeftView()
2015                 : mKeyguardBottomArea.getRightView();
2016     }
2017 
2018     @Override
getLeftPreview()2019     public View getLeftPreview() {
2020         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2021                 ? mKeyguardBottomArea.getRightPreview()
2022                 : mKeyguardBottomArea.getLeftPreview();
2023     }
2024 
2025     @Override
getRightPreview()2026     public View getRightPreview() {
2027         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
2028                 ? mKeyguardBottomArea.getLeftPreview()
2029                 : mKeyguardBottomArea.getRightPreview();
2030     }
2031 
2032     @Override
getAffordanceFalsingFactor()2033     public float getAffordanceFalsingFactor() {
2034         return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
2035     }
2036 
2037     @Override
needsAntiFalsing()2038     public boolean needsAntiFalsing() {
2039         return mStatusBarState == StatusBarState.KEYGUARD;
2040     }
2041 
2042     @Override
getPeekHeight()2043     protected float getPeekHeight() {
2044         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
2045             return mNotificationStackScroller.getPeekHeight();
2046         } else {
2047             return mQsMinExpansionHeight;
2048         }
2049     }
2050 
2051     @Override
shouldUseDismissingAnimation()2052     protected boolean shouldUseDismissingAnimation() {
2053         return mStatusBarState != StatusBarState.SHADE
2054                 && (!mStatusBar.isKeyguardCurrentlySecure() || !isTracking());
2055     }
2056 
2057     @Override
fullyExpandedClearAllVisible()2058     protected boolean fullyExpandedClearAllVisible() {
2059         return mNotificationStackScroller.isDismissViewNotGone()
2060                 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
2061     }
2062 
2063     @Override
isClearAllVisible()2064     protected boolean isClearAllVisible() {
2065         return mNotificationStackScroller.isDismissViewVisible();
2066     }
2067 
2068     @Override
getClearAllHeight()2069     protected int getClearAllHeight() {
2070         return mNotificationStackScroller.getDismissViewHeight();
2071     }
2072 
2073     @Override
isTrackingBlocked()2074     protected boolean isTrackingBlocked() {
2075         return mConflictingQsExpansionGesture && mQsExpanded;
2076     }
2077 
isQsExpanded()2078     public boolean isQsExpanded() {
2079         return mQsExpanded;
2080     }
2081 
isQsDetailShowing()2082     public boolean isQsDetailShowing() {
2083         return mQs.isShowingDetail();
2084     }
2085 
closeQsDetail()2086     public void closeQsDetail() {
2087         mQs.closeDetail();
2088     }
2089 
2090     @Override
shouldDelayChildPressedState()2091     public boolean shouldDelayChildPressedState() {
2092         return true;
2093     }
2094 
isLaunchTransitionFinished()2095     public boolean isLaunchTransitionFinished() {
2096         return mIsLaunchTransitionFinished;
2097     }
2098 
isLaunchTransitionRunning()2099     public boolean isLaunchTransitionRunning() {
2100         return mIsLaunchTransitionRunning;
2101     }
2102 
setLaunchTransitionEndRunnable(Runnable r)2103     public void setLaunchTransitionEndRunnable(Runnable r) {
2104         mLaunchAnimationEndRunnable = r;
2105     }
2106 
setEmptyDragAmount(float amount)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 
interpolate(float t, float start, float end)2118     private static float interpolate(float t, float start, float end) {
2119         return (1 - t) * start + t * end;
2120     }
2121 
setDozing(boolean dozing, boolean animate)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 
updateDozingVisibilities(boolean animate)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
isDozing()2144     public boolean isDozing() {
2145         return mDozing;
2146     }
2147 
showEmptyShadeView(boolean emptyShadeViewVisible)2148     public void showEmptyShadeView(boolean emptyShadeViewVisible) {
2149         mShowEmptyShadeView = emptyShadeViewVisible;
2150         updateEmptyShadeView();
2151     }
2152 
updateEmptyShadeView()2153     private void updateEmptyShadeView() {
2154 
2155         // Hide "No notifications" in QS.
2156         mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
2157     }
2158 
setQsScrimEnabled(boolean qsScrimEnabled)2159     public void setQsScrimEnabled(boolean qsScrimEnabled) {
2160         boolean changed = mQsScrimEnabled != qsScrimEnabled;
2161         mQsScrimEnabled = qsScrimEnabled;
2162         if (changed) {
2163             updateQsState();
2164         }
2165     }
2166 
setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher)2167     public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
2168         mKeyguardUserSwitcher = keyguardUserSwitcher;
2169     }
2170 
onScreenTurningOn()2171     public void onScreenTurningOn() {
2172         mKeyguardStatusView.refreshTime();
2173     }
2174 
2175     @Override
onEmptySpaceClicked(float x, float y)2176     public void onEmptySpaceClicked(float x, float y) {
2177         onEmptySpaceClick(x);
2178     }
2179 
2180     @Override
onMiddleClicked()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
dispatchDraw(Canvas canvas)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
onHeadsUpPinnedModeChanged(final boolean inPinnedMode)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 
setHeadsUpAnimatingAway(boolean headsUpAnimatingAway)2246     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
2247         mHeadsUpAnimatingAway = headsUpAnimatingAway;
2248         mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
2249     }
2250 
2251     @Override
onHeadsUpPinned(ExpandableNotificationRow headsUp)2252     public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
2253         mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
2254     }
2255 
2256     @Override
onHeadsUpUnPinned(ExpandableNotificationRow headsUp)2257     public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
2258     }
2259 
2260     @Override
onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp)2261     public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
2262         mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
2263     }
2264 
2265     @Override
setHeadsUpManager(HeadsUpManager headsUpManager)2266     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2267         super.setHeadsUpManager(headsUpManager);
2268         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
2269                 this);
2270     }
2271 
setTrackingHeadsUp(boolean tracking)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
onClosingFinished()2281     protected void onClosingFinished() {
2282         super.onClosingFinished();
2283         resetVerticalPanelPosition();
2284         setClosingWithAlphaFadeout(false);
2285     }
2286 
setClosingWithAlphaFadeout(boolean closing)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      */
updateVerticalPanelPosition(float x)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 
resetVerticalPanelPosition()2314     private void resetVerticalPanelPosition() {
2315         setVerticalPanelTranslation(0f);
2316     }
2317 
setVerticalPanelTranslation(float translation)2318     protected void setVerticalPanelTranslation(float translation) {
2319         mNotificationStackScroller.setTranslationX(translation);
2320         mQsFrame.setTranslationX(translation);
2321     }
2322 
updateExpandedHeight(float expandedHeight)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      */
isFullWidth()2336     public boolean isFullWidth() {
2337         return mIsFullWidth;
2338     }
2339 
updateStatusBarIcons()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
onFragmentViewDestroyed(String tag, Fragment fragment)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
setTouchDisabled(boolean disabled)2505     public void setTouchDisabled(boolean disabled) {
2506         super.setTouchDisabled(disabled);
2507         if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
2508             mAffordanceHelper.resetImmediately();
2509         }
2510     }
2511 
setDark(boolean dark, boolean animate)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 
setDarkAmount(float amount)2530     private void setDarkAmount(float amount) {
2531         mDarkAmount = amount;
2532         mKeyguardStatusView.setDark(amount == 1);
2533         positionClockAndNotifications();
2534     }
2535 
setNoVisibleNotifications(boolean noNotifications)2536     public void setNoVisibleNotifications(boolean noNotifications) {
2537         mNoVisibleNotifications = noNotifications;
2538         if (mQs != null) {
2539             mQs.setHasNotifications(!noNotifications);
2540         }
2541     }
2542 
setPulsing(boolean pulsing)2543     public void setPulsing(boolean pulsing) {
2544         mKeyguardStatusView.setPulsing(pulsing);
2545     }
2546 }
2547