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