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