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