1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.util.AttributeSet;
30 import android.util.MathUtils;
31 import android.view.MotionEvent;
32 import android.view.VelocityTracker;
33 import android.view.View;
34 import android.view.ViewTreeObserver;
35 import android.view.accessibility.AccessibilityEvent;
36 import android.view.animation.AnimationUtils;
37 import android.view.animation.Interpolator;
38 import android.widget.FrameLayout;
39 import android.widget.TextView;
40 
41 import com.android.keyguard.KeyguardStatusView;
42 import com.android.systemui.EventLogTags;
43 import com.android.systemui.EventLogConstants;
44 import com.android.systemui.R;
45 import com.android.systemui.qs.QSContainer;
46 import com.android.systemui.qs.QSPanel;
47 import com.android.systemui.statusbar.ExpandableView;
48 import com.android.systemui.statusbar.FlingAnimationUtils;
49 import com.android.systemui.statusbar.GestureRecorder;
50 import com.android.systemui.statusbar.KeyguardAffordanceView;
51 import com.android.systemui.statusbar.StatusBarState;
52 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
53 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
54 import com.android.systemui.statusbar.stack.StackStateAnimator;
55 
56 public class NotificationPanelView extends PanelView implements
57         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
58         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
59         KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener {
60 
61     private static final boolean DEBUG = false;
62 
63     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
64     // changed.
65     private static final int CAP_HEIGHT = 1456;
66     private static final int FONT_HEIGHT = 2163;
67 
68     private static final float HEADER_RUBBERBAND_FACTOR = 2.05f;
69     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
70 
71     public static final long DOZE_ANIMATION_DURATION = 700;
72 
73     private KeyguardAffordanceHelper mAfforanceHelper;
74     private StatusBarHeaderView mHeader;
75     private KeyguardUserSwitcher mKeyguardUserSwitcher;
76     private KeyguardStatusBarView mKeyguardStatusBar;
77     private QSContainer mQsContainer;
78     private QSPanel mQsPanel;
79     private KeyguardStatusView mKeyguardStatusView;
80     private ObservableScrollView mScrollView;
81     private TextView mClockView;
82     private View mReserveNotificationSpace;
83     private View mQsNavbarScrim;
84     private View mNotificationContainerParent;
85     private NotificationStackScrollLayout mNotificationStackScroller;
86     private int mNotificationTopPadding;
87     private boolean mAnimateNextTopPaddingChange;
88 
89     private int mTrackingPointer;
90     private VelocityTracker mVelocityTracker;
91     private boolean mQsTracking;
92 
93     /**
94      * Handles launching the secure camera properly even when other applications may be using the
95      * camera hardware.
96      */
97     private SecureCameraLaunchManager mSecureCameraLaunchManager;
98 
99     /**
100      * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
101      * the expansion for quick settings.
102      */
103     private boolean mConflictingQsExpansionGesture;
104 
105     /**
106      * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
107      * intercepted yet.
108      */
109     private boolean mIntercepting;
110     private boolean mQsExpanded;
111     private boolean mQsExpandedWhenExpandingStarted;
112     private boolean mQsFullyExpanded;
113     private boolean mKeyguardShowing;
114     private boolean mDozing;
115     private int mStatusBarState;
116     private float mInitialHeightOnTouch;
117     private float mInitialTouchX;
118     private float mInitialTouchY;
119     private float mLastTouchX;
120     private float mLastTouchY;
121     private float mQsExpansionHeight;
122     private int mQsMinExpansionHeight;
123     private int mQsMaxExpansionHeight;
124     private int mQsPeekHeight;
125     private boolean mStackScrollerOverscrolling;
126     private boolean mQsExpansionFromOverscroll;
127     private float mLastOverscroll;
128     private boolean mQsExpansionEnabled = true;
129     private ValueAnimator mQsExpansionAnimator;
130     private FlingAnimationUtils mFlingAnimationUtils;
131     private int mStatusBarMinHeight;
132     private boolean mUnlockIconActive;
133     private int mNotificationsHeaderCollideDistance;
134     private int mUnlockMoveDistance;
135     private float mEmptyDragAmount;
136 
137     private Interpolator mFastOutSlowInInterpolator;
138     private Interpolator mFastOutLinearInterpolator;
139     private Interpolator mDozeAnimationInterpolator;
140     private ObjectAnimator mClockAnimator;
141     private int mClockAnimationTarget = -1;
142     private int mTopPaddingAdjustment;
143     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
144             new KeyguardClockPositionAlgorithm();
145     private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
146             new KeyguardClockPositionAlgorithm.Result();
147     private boolean mIsExpanding;
148 
149     private boolean mBlockTouches;
150     private int mNotificationScrimWaitDistance;
151     // Used for two finger gesture as well as accessibility shortcut to QS.
152     private boolean mQsExpandImmediate;
153     private boolean mTwoFingerQsExpandPossible;
154 
155     /**
156      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
157      * need to take this into account in our panel height calculation.
158      */
159     private int mScrollYOverride = -1;
160     private boolean mQsAnimatorExpand;
161     private boolean mIsLaunchTransitionFinished;
162     private boolean mIsLaunchTransitionRunning;
163     private Runnable mLaunchAnimationEndRunnable;
164     private boolean mOnlyAffordanceInThisMotion;
165     private boolean mKeyguardStatusViewAnimating;
166     private boolean mHeaderAnimatingIn;
167     private ObjectAnimator mQsContainerAnimator;
168     private ValueAnimator mQsSizeChangeAnimator;
169 
170     private boolean mShadeEmpty;
171 
172     private boolean mQsScrimEnabled = true;
173     private boolean mLastAnnouncementWasQuickSettings;
174     private boolean mQsTouchAboveFalsingThreshold;
175     private int mQsFalsingThreshold;
176 
177     private float mKeyguardStatusBarAnimateAlpha = 1f;
178     private int mOldLayoutDirection;
179 
NotificationPanelView(Context context, AttributeSet attrs)180     public NotificationPanelView(Context context, AttributeSet attrs) {
181         super(context, attrs);
182         setWillNotDraw(!DEBUG);
183     }
184 
setStatusBar(PhoneStatusBar bar)185     public void setStatusBar(PhoneStatusBar bar) {
186         mStatusBar = bar;
187     }
188 
189     @Override
onFinishInflate()190     protected void onFinishInflate() {
191         super.onFinishInflate();
192         mHeader = (StatusBarHeaderView) findViewById(R.id.header);
193         mHeader.setOnClickListener(this);
194         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
195         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
196         mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
197         mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
198         mClockView = (TextView) findViewById(R.id.clock_view);
199         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
200         mScrollView.setListener(this);
201         mScrollView.setFocusable(false);
202         mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
203         mNotificationContainerParent = findViewById(R.id.notification_container_parent);
204         mNotificationStackScroller = (NotificationStackScrollLayout)
205                 findViewById(R.id.notification_stack_scroller);
206         mNotificationStackScroller.setOnHeightChangedListener(this);
207         mNotificationStackScroller.setOverscrollTopChangedListener(this);
208         mNotificationStackScroller.setOnEmptySpaceClickListener(this);
209         mNotificationStackScroller.setScrollView(mScrollView);
210         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
211                 android.R.interpolator.fast_out_slow_in);
212         mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
213                 android.R.interpolator.fast_out_linear_in);
214         mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(),
215                 android.R.interpolator.linear_out_slow_in);
216         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
217         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
218         mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
219         mSecureCameraLaunchManager =
220                 new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
221 
222         // recompute internal state when qspanel height changes
223         mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
224             @Override
225             public void onLayoutChange(View v, int left, int top, int right,
226                     int bottom, int oldLeft, int oldTop, int oldRight,
227                     int oldBottom) {
228                 final int height = bottom - top;
229                 final int oldHeight = oldBottom - oldTop;
230                 if (height != oldHeight) {
231                     onScrollChanged();
232                 }
233             }
234         });
235     }
236 
237     @Override
loadDimens()238     protected void loadDimens() {
239         super.loadDimens();
240         mNotificationTopPadding = getResources().getDimensionPixelSize(
241                 R.dimen.notifications_top_padding);
242         mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
243         mStatusBarMinHeight = getResources().getDimensionPixelSize(
244                 com.android.internal.R.dimen.status_bar_height);
245         mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
246         mNotificationsHeaderCollideDistance =
247                 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
248         mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
249         mClockPositionAlgorithm.loadDimens(getResources());
250         mNotificationScrimWaitDistance =
251                 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
252         mQsFalsingThreshold = getResources().getDimensionPixelSize(
253                 R.dimen.qs_falsing_threshold);
254     }
255 
updateResources()256     public void updateResources() {
257         int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
258         int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
259         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
260         if (lp.width != panelWidth) {
261             lp.width = panelWidth;
262             lp.gravity = panelGravity;
263             mHeader.setLayoutParams(lp);
264             mHeader.post(mUpdateHeader);
265         }
266 
267         lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
268         if (lp.width != panelWidth) {
269             lp.width = panelWidth;
270             lp.gravity = panelGravity;
271             mNotificationStackScroller.setLayoutParams(lp);
272         }
273 
274         lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
275         if (lp.width != panelWidth) {
276             lp.width = panelWidth;
277             lp.gravity = panelGravity;
278             mScrollView.setLayoutParams(lp);
279         }
280     }
281 
282     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)283     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
284         super.onLayout(changed, left, top, right, bottom);
285 
286         // Update Clock Pivot
287         mKeyguardStatusView.setPivotX(getWidth() / 2);
288         mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
289 
290         // Calculate quick setting heights.
291         int oldMaxHeight = mQsMaxExpansionHeight;
292         mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
293         mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
294         positionClockAndNotifications();
295         if (mQsExpanded && mQsFullyExpanded) {
296             mQsExpansionHeight = mQsMaxExpansionHeight;
297             requestScrollerTopPaddingUpdate(false /* animate */);
298             requestPanelHeightUpdate();
299 
300             // Size has changed, start an animation.
301             if (mQsMaxExpansionHeight != oldMaxHeight) {
302                 startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
303             }
304         } else if (!mQsExpanded) {
305             setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
306         }
307         mNotificationStackScroller.setStackHeight(getExpandedHeight());
308         updateHeader();
309         mNotificationStackScroller.updateIsSmallScreen(
310                 mHeader.getCollapsedHeight() + mQsPeekHeight);
311 
312         // If we are running a size change animation, the animation takes care of the height of
313         // the container. However, if we are not animating, we always need to make the QS container
314         // the desired height so when closing the QS detail, it stays smaller after the size change
315         // animation is finished but the detail view is still being animated away (this animation
316         // takes longer than the size change animation).
317         if (mQsSizeChangeAnimator == null) {
318             mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
319         }
320     }
321 
322     @Override
onAttachedToWindow()323     public void onAttachedToWindow() {
324         mSecureCameraLaunchManager.create();
325     }
326 
327     @Override
onDetachedFromWindow()328     public void onDetachedFromWindow() {
329         mSecureCameraLaunchManager.destroy();
330     }
331 
startQsSizeChangeAnimation(int oldHeight, final int newHeight)332     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
333         if (mQsSizeChangeAnimator != null) {
334             oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
335             mQsSizeChangeAnimator.cancel();
336         }
337         mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
338         mQsSizeChangeAnimator.setDuration(300);
339         mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator);
340         mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
341             @Override
342             public void onAnimationUpdate(ValueAnimator animation) {
343                 requestScrollerTopPaddingUpdate(false /* animate */);
344                 requestPanelHeightUpdate();
345                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
346                 mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
347             }
348         });
349         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
350             @Override
351             public void onAnimationEnd(Animator animation) {
352                 mQsSizeChangeAnimator = null;
353             }
354         });
355         mQsSizeChangeAnimator.start();
356     }
357 
358     /**
359      * Positions the clock and notifications dynamically depending on how many notifications are
360      * showing.
361      */
positionClockAndNotifications()362     private void positionClockAndNotifications() {
363         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
364         int stackScrollerPadding;
365         if (mStatusBarState != StatusBarState.KEYGUARD) {
366             int bottom = mHeader.getCollapsedHeight();
367             stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
368                     ? bottom + mQsPeekHeight + mNotificationTopPadding
369                     : mKeyguardStatusBar.getHeight() + mNotificationTopPadding;
370             mTopPaddingAdjustment = 0;
371         } else {
372             mClockPositionAlgorithm.setup(
373                     mStatusBar.getMaxKeyguardNotifications(),
374                     getMaxPanelHeight(),
375                     getExpandedHeight(),
376                     mNotificationStackScroller.getNotGoneChildCount(),
377                     getHeight(),
378                     mKeyguardStatusView.getHeight(),
379                     mEmptyDragAmount);
380             mClockPositionAlgorithm.run(mClockPositionResult);
381             if (animate || mClockAnimator != null) {
382                 startClockAnimation(mClockPositionResult.clockY);
383             } else {
384                 mKeyguardStatusView.setY(mClockPositionResult.clockY);
385             }
386             updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
387             stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
388             mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
389         }
390         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
391         requestScrollerTopPaddingUpdate(animate);
392     }
393 
startClockAnimation(int y)394     private void startClockAnimation(int y) {
395         if (mClockAnimationTarget == y) {
396             return;
397         }
398         mClockAnimationTarget = y;
399         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
400             @Override
401             public boolean onPreDraw() {
402                 getViewTreeObserver().removeOnPreDrawListener(this);
403                 if (mClockAnimator != null) {
404                     mClockAnimator.removeAllListeners();
405                     mClockAnimator.cancel();
406                 }
407                 mClockAnimator = ObjectAnimator
408                         .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
409                 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
410                 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
411                 mClockAnimator.addListener(new AnimatorListenerAdapter() {
412                     @Override
413                     public void onAnimationEnd(Animator animation) {
414                         mClockAnimator = null;
415                         mClockAnimationTarget = -1;
416                     }
417                 });
418                 mClockAnimator.start();
419                 return true;
420             }
421         });
422     }
423 
updateClock(float alpha, float scale)424     private void updateClock(float alpha, float scale) {
425         if (!mKeyguardStatusViewAnimating) {
426             mKeyguardStatusView.setAlpha(alpha);
427         }
428         mKeyguardStatusView.setScaleX(scale);
429         mKeyguardStatusView.setScaleY(scale);
430     }
431 
animateToFullShade(long delay)432     public void animateToFullShade(long delay) {
433         mAnimateNextTopPaddingChange = true;
434         mNotificationStackScroller.goToFullShade(delay);
435         requestLayout();
436     }
437 
setQsExpansionEnabled(boolean qsExpansionEnabled)438     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
439         mQsExpansionEnabled = qsExpansionEnabled;
440         mHeader.setClickable(qsExpansionEnabled);
441     }
442 
443     @Override
resetViews()444     public void resetViews() {
445         mIsLaunchTransitionFinished = false;
446         mBlockTouches = false;
447         mUnlockIconActive = false;
448         mAfforanceHelper.reset(true);
449         closeQs();
450         mStatusBar.dismissPopups();
451         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
452                 true /* cancelAnimators */);
453     }
454 
closeQs()455     public void closeQs() {
456         cancelAnimation();
457         setQsExpansion(mQsMinExpansionHeight);
458     }
459 
animateCloseQs()460     public void animateCloseQs() {
461         if (mQsExpansionAnimator != null) {
462             if (!mQsAnimatorExpand) {
463                 return;
464             }
465             float height = mQsExpansionHeight;
466             mQsExpansionAnimator.cancel();
467             setQsExpansion(height);
468         }
469         flingSettings(0 /* vel */, false);
470     }
471 
openQs()472     public void openQs() {
473         cancelAnimation();
474         if (mQsExpansionEnabled) {
475             setQsExpansion(mQsMaxExpansionHeight);
476         }
477     }
478 
expandWithQs()479     public void expandWithQs() {
480         if (mQsExpansionEnabled) {
481             mQsExpandImmediate = true;
482         }
483         expand();
484     }
485 
486     @Override
fling(float vel, boolean expand)487     public void fling(float vel, boolean expand) {
488         GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
489         if (gr != null) {
490             gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
491         }
492         super.fling(vel, expand);
493     }
494 
495     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)496     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
497         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
498             event.getText().add(getKeyguardOrLockScreenString());
499             mLastAnnouncementWasQuickSettings = false;
500             return true;
501         }
502 
503         return super.dispatchPopulateAccessibilityEvent(event);
504     }
505 
506     @Override
onInterceptTouchEvent(MotionEvent event)507     public boolean onInterceptTouchEvent(MotionEvent event) {
508         if (mBlockTouches) {
509             return false;
510         }
511         resetDownStates(event);
512         int pointerIndex = event.findPointerIndex(mTrackingPointer);
513         if (pointerIndex < 0) {
514             pointerIndex = 0;
515             mTrackingPointer = event.getPointerId(pointerIndex);
516         }
517         final float x = event.getX(pointerIndex);
518         final float y = event.getY(pointerIndex);
519 
520         switch (event.getActionMasked()) {
521             case MotionEvent.ACTION_DOWN:
522                 mIntercepting = true;
523                 mInitialTouchY = y;
524                 mInitialTouchX = x;
525                 initVelocityTracker();
526                 trackMovement(event);
527                 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
528                     getParent().requestDisallowInterceptTouchEvent(true);
529                 }
530                 if (mQsExpansionAnimator != null) {
531                     onQsExpansionStarted();
532                     mInitialHeightOnTouch = mQsExpansionHeight;
533                     mQsTracking = true;
534                     mIntercepting = false;
535                     mNotificationStackScroller.removeLongPressCallback();
536                 }
537                 break;
538             case MotionEvent.ACTION_POINTER_UP:
539                 final int upPointer = event.getPointerId(event.getActionIndex());
540                 if (mTrackingPointer == upPointer) {
541                     // gesture is ongoing, find a new pointer to track
542                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
543                     mTrackingPointer = event.getPointerId(newIndex);
544                     mInitialTouchX = event.getX(newIndex);
545                     mInitialTouchY = event.getY(newIndex);
546                 }
547                 break;
548 
549             case MotionEvent.ACTION_MOVE:
550                 final float h = y - mInitialTouchY;
551                 trackMovement(event);
552                 if (mQsTracking) {
553 
554                     // Already tracking because onOverscrolled was called. We need to update here
555                     // so we don't stop for a frame until the next touch event gets handled in
556                     // onTouchEvent.
557                     setQsExpansion(h + mInitialHeightOnTouch);
558                     trackMovement(event);
559                     mIntercepting = false;
560                     return true;
561                 }
562                 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
563                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
564                     mQsTracking = true;
565                     onQsExpansionStarted();
566                     mInitialHeightOnTouch = mQsExpansionHeight;
567                     mInitialTouchY = y;
568                     mInitialTouchX = x;
569                     mIntercepting = false;
570                     mNotificationStackScroller.removeLongPressCallback();
571                     return true;
572                 }
573                 break;
574 
575             case MotionEvent.ACTION_CANCEL:
576             case MotionEvent.ACTION_UP:
577                 trackMovement(event);
578                 if (mQsTracking) {
579                     flingQsWithCurrentVelocity(
580                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
581                     mQsTracking = false;
582                 }
583                 mIntercepting = false;
584                 break;
585         }
586         return super.onInterceptTouchEvent(event);
587     }
588 
589     @Override
isInContentBounds(float x, float y)590     protected boolean isInContentBounds(float x, float y) {
591         float yTransformed = y - mNotificationStackScroller.getY();
592         float stackScrollerX = mNotificationStackScroller.getX();
593         return mNotificationStackScroller.isInContentBounds(yTransformed) && stackScrollerX < x
594                 && x < stackScrollerX + mNotificationStackScroller.getWidth();
595     }
596 
resetDownStates(MotionEvent event)597     private void resetDownStates(MotionEvent event) {
598         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
599             mOnlyAffordanceInThisMotion = false;
600             mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
601         }
602     }
603 
604     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)605     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
606 
607         // Block request when interacting with the scroll view so we can still intercept the
608         // scrolling when QS is expanded.
609         if (mScrollView.isHandlingTouchEvent()) {
610             return;
611         }
612         super.requestDisallowInterceptTouchEvent(disallowIntercept);
613     }
614 
flingQsWithCurrentVelocity(boolean isCancelMotionEvent)615     private void flingQsWithCurrentVelocity(boolean isCancelMotionEvent) {
616         float vel = getCurrentVelocity();
617         flingSettings(vel, flingExpandsQs(vel) && !isCancelMotionEvent);
618     }
619 
flingExpandsQs(float vel)620     private boolean flingExpandsQs(float vel) {
621         if (isBelowFalsingThreshold()) {
622             return false;
623         }
624         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
625             return getQsExpansionFraction() > 0.5f;
626         } else {
627             return vel > 0;
628         }
629     }
630 
isBelowFalsingThreshold()631     private boolean isBelowFalsingThreshold() {
632         return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD;
633     }
634 
getQsExpansionFraction()635     private float getQsExpansionFraction() {
636         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
637                 / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
638     }
639 
640     @Override
onTouchEvent(MotionEvent event)641     public boolean onTouchEvent(MotionEvent event) {
642         if (mBlockTouches) {
643             return false;
644         }
645         resetDownStates(event);
646         if ((!mIsExpanding || mHintAnimationRunning)
647                 && !mQsExpanded
648                 && mStatusBar.getBarState() != StatusBarState.SHADE) {
649             mAfforanceHelper.onTouchEvent(event);
650         }
651         if (mOnlyAffordanceInThisMotion) {
652             return true;
653         }
654         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
655                 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
656                 && mQsExpansionEnabled) {
657 
658             // Down in the empty area while fully expanded - go to QS.
659             mQsTracking = true;
660             mConflictingQsExpansionGesture = true;
661             onQsExpansionStarted();
662             mInitialHeightOnTouch = mQsExpansionHeight;
663             mInitialTouchY = event.getX();
664             mInitialTouchX = event.getY();
665         }
666         if (mExpandedHeight != 0) {
667             handleQsDown(event);
668         }
669         if (!mQsExpandImmediate && mQsTracking) {
670             onQsTouch(event);
671             if (!mConflictingQsExpansionGesture) {
672                 return true;
673             }
674         }
675         if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
676                 || event.getActionMasked() == MotionEvent.ACTION_UP) {
677             mConflictingQsExpansionGesture = false;
678         }
679         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
680                 && mQsExpansionEnabled) {
681             mTwoFingerQsExpandPossible = true;
682         }
683         if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
684                 && event.getPointerCount() == 2
685                 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
686             mQsExpandImmediate = true;
687             requestPanelHeightUpdate();
688 
689             // Normally, we start listening when the panel is expanded, but here we need to start
690             // earlier so the state is already up to date when dragging down.
691             setListening(true);
692         }
693         super.onTouchEvent(event);
694         return true;
695     }
696 
isInQsArea(float x, float y)697     private boolean isInQsArea(float x, float y) {
698         return (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
699                 (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
700                 || y <= mQsContainer.getY() + mQsContainer.getHeight());
701     }
702 
handleQsDown(MotionEvent event)703     private void handleQsDown(MotionEvent event) {
704         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
705                 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
706             mQsTracking = true;
707             onQsExpansionStarted();
708             mInitialHeightOnTouch = mQsExpansionHeight;
709             mInitialTouchY = event.getX();
710             mInitialTouchX = event.getY();
711 
712             // If we interrupt an expansion gesture here, make sure to update the state correctly.
713             if (mIsExpanding) {
714                 onExpandingFinished();
715             }
716         }
717     }
718 
719     @Override
flingExpands(float vel, float vectorVel)720     protected boolean flingExpands(float vel, float vectorVel) {
721         boolean expands = super.flingExpands(vel, vectorVel);
722 
723         // If we are already running a QS expansion, make sure that we keep the panel open.
724         if (mQsExpansionAnimator != null) {
725             expands = true;
726         }
727         return expands;
728     }
729 
730     @Override
hasConflictingGestures()731     protected boolean hasConflictingGestures() {
732         return mStatusBar.getBarState() != StatusBarState.SHADE;
733     }
734 
onQsTouch(MotionEvent event)735     private void onQsTouch(MotionEvent event) {
736         int pointerIndex = event.findPointerIndex(mTrackingPointer);
737         if (pointerIndex < 0) {
738             pointerIndex = 0;
739             mTrackingPointer = event.getPointerId(pointerIndex);
740         }
741         final float y = event.getY(pointerIndex);
742         final float x = event.getX(pointerIndex);
743 
744         switch (event.getActionMasked()) {
745             case MotionEvent.ACTION_DOWN:
746                 mQsTracking = true;
747                 mInitialTouchY = y;
748                 mInitialTouchX = x;
749                 onQsExpansionStarted();
750                 mInitialHeightOnTouch = mQsExpansionHeight;
751                 initVelocityTracker();
752                 trackMovement(event);
753                 break;
754 
755             case MotionEvent.ACTION_POINTER_UP:
756                 final int upPointer = event.getPointerId(event.getActionIndex());
757                 if (mTrackingPointer == upPointer) {
758                     // gesture is ongoing, find a new pointer to track
759                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
760                     final float newY = event.getY(newIndex);
761                     final float newX = event.getX(newIndex);
762                     mTrackingPointer = event.getPointerId(newIndex);
763                     mInitialHeightOnTouch = mQsExpansionHeight;
764                     mInitialTouchY = newY;
765                     mInitialTouchX = newX;
766                 }
767                 break;
768 
769             case MotionEvent.ACTION_MOVE:
770                 final float h = y - mInitialTouchY;
771                 setQsExpansion(h + mInitialHeightOnTouch);
772                 if (h >= getFalsingThreshold()) {
773                     mQsTouchAboveFalsingThreshold = true;
774                 }
775                 trackMovement(event);
776                 break;
777 
778             case MotionEvent.ACTION_UP:
779             case MotionEvent.ACTION_CANCEL:
780                 mQsTracking = false;
781                 mTrackingPointer = -1;
782                 trackMovement(event);
783                 float fraction = getQsExpansionFraction();
784                 if ((fraction != 0f || y >= mInitialTouchY)
785                         && (fraction != 1f || y <= mInitialTouchY)) {
786                     flingQsWithCurrentVelocity(
787                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
788                 } else {
789                     mScrollYOverride = -1;
790                 }
791                 if (mVelocityTracker != null) {
792                     mVelocityTracker.recycle();
793                     mVelocityTracker = null;
794                 }
795                 break;
796         }
797     }
798 
getFalsingThreshold()799     private int getFalsingThreshold() {
800         float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
801         return (int) (mQsFalsingThreshold * factor);
802     }
803 
804     @Override
onOverscrolled(float lastTouchX, float lastTouchY, int amount)805     public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
806         if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
807                 -1 /* yDiff: Not relevant here */)) {
808             mQsTracking = true;
809             onQsExpansionStarted(amount);
810             mInitialHeightOnTouch = mQsExpansionHeight;
811             mInitialTouchY = mLastTouchY;
812             mInitialTouchX = mLastTouchX;
813         }
814     }
815 
816     @Override
onOverscrollTopChanged(float amount, boolean isRubberbanded)817     public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
818         cancelAnimation();
819         if (!mQsExpansionEnabled) {
820             amount = 0f;
821         }
822         float rounded = amount >= 1f ? amount : 0f;
823         mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
824         mQsExpansionFromOverscroll = rounded != 0f;
825         mLastOverscroll = rounded;
826         updateQsState();
827         setQsExpansion(mQsMinExpansionHeight + rounded);
828     }
829 
830     @Override
flingTopOverscroll(float velocity, boolean open)831     public void flingTopOverscroll(float velocity, boolean open) {
832         mLastOverscroll = 0f;
833         setQsExpansion(mQsExpansionHeight);
834         flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
835                 new Runnable() {
836             @Override
837             public void run() {
838                 mStackScrollerOverscrolling = false;
839                 mQsExpansionFromOverscroll = false;
840                 updateQsState();
841             }
842         });
843     }
844 
onQsExpansionStarted()845     private void onQsExpansionStarted() {
846         onQsExpansionStarted(0);
847     }
848 
onQsExpansionStarted(int overscrollAmount)849     private void onQsExpansionStarted(int overscrollAmount) {
850         cancelAnimation();
851 
852         // Reset scroll position and apply that position to the expanded height.
853         float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
854         if (mScrollView.getScrollY() != 0) {
855             mScrollYOverride = mScrollView.getScrollY();
856         }
857         mScrollView.scrollTo(0, 0);
858         setQsExpansion(height);
859         requestPanelHeightUpdate();
860     }
861 
setQsExpanded(boolean expanded)862     private void setQsExpanded(boolean expanded) {
863         boolean changed = mQsExpanded != expanded;
864         if (changed) {
865             mQsExpanded = expanded;
866             updateQsState();
867             requestPanelHeightUpdate();
868             mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
869             mStatusBar.setQsExpanded(expanded);
870             mQsPanel.setExpanded(expanded);
871         }
872     }
873 
setBarState(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)874     public void setBarState(int statusBarState, boolean keyguardFadingAway,
875             boolean goingToFullShade) {
876         boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
877                 || statusBarState == StatusBarState.SHADE_LOCKED;
878         if (!mKeyguardShowing && keyguardShowing) {
879             setQsTranslation(mQsExpansionHeight);
880             mHeader.setTranslationY(0f);
881         }
882         setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
883         setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
884         if (goingToFullShade) {
885             animateKeyguardStatusBarOut();
886         } else {
887             mKeyguardStatusBar.setAlpha(1f);
888             mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
889         }
890         mStatusBarState = statusBarState;
891         mKeyguardShowing = keyguardShowing;
892         updateQsState();
893         if (goingToFullShade) {
894             animateHeaderSlidingIn();
895         }
896     }
897 
898     private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
899         @Override
900         public void run() {
901             mKeyguardStatusViewAnimating = false;
902             mKeyguardStatusView.setVisibility(View.GONE);
903         }
904     };
905 
906     private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
907         @Override
908         public void run() {
909             mKeyguardStatusViewAnimating = false;
910         }
911     };
912 
913     private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
914             = new AnimatorListenerAdapter() {
915         @Override
916         public void onAnimationEnd(Animator animation) {
917             mHeaderAnimatingIn = false;
918             mQsContainerAnimator = null;
919             mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
920         }
921     };
922 
923     private final OnLayoutChangeListener mQsContainerAnimatorUpdater
924             = new OnLayoutChangeListener() {
925         @Override
926         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
927                 int oldTop, int oldRight, int oldBottom) {
928             int oldHeight = oldBottom - oldTop;
929             int height = bottom - top;
930             if (height != oldHeight && mQsContainerAnimator != null) {
931                 PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
932                 float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
933                 float newStartValue = -height - top;
934                 values[0].setFloatValues(newStartValue, newEndValue);
935                 mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
936             }
937         }
938     };
939 
940     private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
941             = new ViewTreeObserver.OnPreDrawListener() {
942         @Override
943         public boolean onPreDraw() {
944             getViewTreeObserver().removeOnPreDrawListener(this);
945             mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
946             mHeader.animate()
947                     .translationY(0f)
948                     .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay())
949                     .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
950                     .setInterpolator(mFastOutSlowInInterpolator)
951                     .start();
952             mQsContainer.setY(-mQsContainer.getHeight());
953             mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
954                     mQsContainer.getTranslationY(),
955                     mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
956                             - mQsContainer.getTop());
957             mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay());
958             mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
959             mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
960             mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
961             mQsContainerAnimator.start();
962             mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
963             return true;
964         }
965     };
966 
animateHeaderSlidingIn()967     private void animateHeaderSlidingIn() {
968         mHeaderAnimatingIn = true;
969         getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
970 
971     }
972 
973     private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
974         @Override
975         public void run() {
976             mKeyguardStatusBar.setVisibility(View.INVISIBLE);
977             mKeyguardStatusBar.setAlpha(1f);
978             mKeyguardStatusBarAnimateAlpha = 1f;
979         }
980     };
981 
animateKeyguardStatusBarOut()982     private void animateKeyguardStatusBarOut() {
983         mKeyguardStatusBar.animate()
984                 .alpha(0f)
985                 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
986                 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
987                 .setInterpolator(PhoneStatusBar.ALPHA_OUT)
988                 .setUpdateListener(mStatusBarAnimateAlphaListener)
989                 .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable)
990                 .start();
991     }
992 
993     private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
994             new ValueAnimator.AnimatorUpdateListener() {
995         @Override
996         public void onAnimationUpdate(ValueAnimator animation) {
997             mKeyguardStatusBarAnimateAlpha = mKeyguardStatusBar.getAlpha();
998         }
999     };
1000 
animateKeyguardStatusBarIn()1001     private void animateKeyguardStatusBarIn() {
1002         mKeyguardStatusBar.setVisibility(View.VISIBLE);
1003         mKeyguardStatusBar.setAlpha(0f);
1004         mKeyguardStatusBar.animate()
1005                 .alpha(1f)
1006                 .setStartDelay(0)
1007                 .setDuration(DOZE_ANIMATION_DURATION)
1008                 .setInterpolator(mDozeAnimationInterpolator)
1009                 .setUpdateListener(mStatusBarAnimateAlphaListener)
1010                 .start();
1011     }
1012 
1013     private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
1014         @Override
1015         public void run() {
1016             mKeyguardBottomArea.setVisibility(View.GONE);
1017         }
1018     };
1019 
setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade)1020     private void setKeyguardBottomAreaVisibility(int statusBarState,
1021             boolean goingToFullShade) {
1022         if (goingToFullShade) {
1023             mKeyguardBottomArea.animate().cancel();
1024             mKeyguardBottomArea.animate()
1025                     .alpha(0f)
1026                     .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
1027                     .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
1028                     .setInterpolator(PhoneStatusBar.ALPHA_OUT)
1029                     .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
1030                     .start();
1031         } else if (statusBarState == StatusBarState.KEYGUARD
1032                 || statusBarState == StatusBarState.SHADE_LOCKED) {
1033             mKeyguardBottomArea.animate().cancel();
1034             mKeyguardBottomArea.setVisibility(View.VISIBLE);
1035             mKeyguardBottomArea.setAlpha(1f);
1036         } else {
1037             mKeyguardBottomArea.animate().cancel();
1038             mKeyguardBottomArea.setVisibility(View.GONE);
1039             mKeyguardBottomArea.setAlpha(1f);
1040         }
1041     }
1042 
setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)1043     private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
1044             boolean goingToFullShade) {
1045         if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
1046                 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
1047             mKeyguardStatusView.animate().cancel();
1048             mKeyguardStatusViewAnimating = true;
1049             mKeyguardStatusView.animate()
1050                     .alpha(0f)
1051                     .setStartDelay(0)
1052                     .setDuration(160)
1053                     .setInterpolator(PhoneStatusBar.ALPHA_OUT)
1054                     .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
1055             if (keyguardFadingAway) {
1056                 mKeyguardStatusView.animate()
1057                         .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
1058                         .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
1059                         .start();
1060             }
1061         } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
1062                 && statusBarState == StatusBarState.KEYGUARD) {
1063             mKeyguardStatusView.animate().cancel();
1064             mKeyguardStatusView.setVisibility(View.VISIBLE);
1065             mKeyguardStatusViewAnimating = true;
1066             mKeyguardStatusView.setAlpha(0f);
1067             mKeyguardStatusView.animate()
1068                     .alpha(1f)
1069                     .setStartDelay(0)
1070                     .setDuration(320)
1071                     .setInterpolator(PhoneStatusBar.ALPHA_IN)
1072                     .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
1073         } else if (statusBarState == StatusBarState.KEYGUARD) {
1074             mKeyguardStatusView.animate().cancel();
1075             mKeyguardStatusViewAnimating = false;
1076             mKeyguardStatusView.setVisibility(View.VISIBLE);
1077             mKeyguardStatusView.setAlpha(1f);
1078         } else {
1079             mKeyguardStatusView.animate().cancel();
1080             mKeyguardStatusViewAnimating = false;
1081             mKeyguardStatusView.setVisibility(View.GONE);
1082             mKeyguardStatusView.setAlpha(1f);
1083         }
1084     }
1085 
updateQsState()1086     private void updateQsState() {
1087         boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
1088         mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE);
1089         mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling));
1090         mNotificationStackScroller.setScrollingEnabled(
1091                 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
1092                         || mQsExpansionFromOverscroll));
1093         mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
1094         mQsContainer.setVisibility(
1095                 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
1096         mScrollView.setTouchEnabled(mQsExpanded);
1097         updateEmptyShadeView();
1098         mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
1099                 && !mStackScrollerOverscrolling && mQsScrimEnabled
1100                         ? View.VISIBLE
1101                         : View.INVISIBLE);
1102         if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
1103             mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
1104         }
1105     }
1106 
setQsExpansion(float height)1107     private void setQsExpansion(float height) {
1108         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
1109         mQsFullyExpanded = height == mQsMaxExpansionHeight;
1110         if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
1111             setQsExpanded(true);
1112         } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
1113             setQsExpanded(false);
1114             if (mLastAnnouncementWasQuickSettings && !mTracking) {
1115                 announceForAccessibility(getKeyguardOrLockScreenString());
1116                 mLastAnnouncementWasQuickSettings = false;
1117             }
1118         }
1119         mQsExpansionHeight = height;
1120         mHeader.setExpansion(getHeaderExpansionFraction());
1121         setQsTranslation(height);
1122         requestScrollerTopPaddingUpdate(false /* animate */);
1123         updateNotificationScrim(height);
1124         if (mKeyguardShowing) {
1125             updateHeaderKeyguard();
1126         }
1127         if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
1128                 && !mStackScrollerOverscrolling && mQsScrimEnabled) {
1129             mQsNavbarScrim.setAlpha(getQsExpansionFraction());
1130         }
1131 
1132         // Upon initialisation when we are not layouted yet we don't want to announce that we are
1133         // fully expanded, hence the != 0.0f check.
1134         if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
1135             announceForAccessibility(getContext().getString(
1136                     R.string.accessibility_desc_quick_settings));
1137             mLastAnnouncementWasQuickSettings = true;
1138         }
1139         if (DEBUG) {
1140             invalidate();
1141         }
1142     }
1143 
getKeyguardOrLockScreenString()1144     private String getKeyguardOrLockScreenString() {
1145         if (mStatusBarState == StatusBarState.KEYGUARD) {
1146             return getContext().getString(R.string.accessibility_desc_lock_screen);
1147         } else {
1148             return getContext().getString(R.string.accessibility_desc_notification_shade);
1149         }
1150     }
1151 
updateNotificationScrim(float height)1152     private void updateNotificationScrim(float height) {
1153         int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
1154         float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
1155         progress = Math.max(0.0f, Math.min(progress, 1.0f));
1156     }
1157 
getHeaderExpansionFraction()1158     private float getHeaderExpansionFraction() {
1159         if (!mKeyguardShowing) {
1160             return getQsExpansionFraction();
1161         } else {
1162             return 1f;
1163         }
1164     }
1165 
setQsTranslation(float height)1166     private void setQsTranslation(float height) {
1167         if (!mHeaderAnimatingIn) {
1168             mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
1169         }
1170         if (mKeyguardShowing) {
1171             mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
1172         }
1173     }
1174 
calculateQsTopPadding()1175     private float calculateQsTopPadding() {
1176         if (mKeyguardShowing
1177                 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
1178 
1179             // Either QS pushes the notifications down when fully expanded, or QS is fully above the
1180             // notifications (mostly on tablets). maxNotifications denotes the normal top padding
1181             // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to
1182             // take the maximum and linearly interpolate with the panel expansion for a nice motion.
1183             int maxNotifications = mClockPositionResult.stackScrollerPadding
1184                     - mClockPositionResult.stackScrollerPaddingAdjustment
1185                     - mNotificationTopPadding;
1186             int maxQs = getTempQsMaxExpansion();
1187             int max = mStatusBarState == StatusBarState.KEYGUARD
1188                     ? Math.max(maxNotifications, maxQs)
1189                     : maxQs;
1190             return (int) interpolate(getExpandedFraction(),
1191                     mQsMinExpansionHeight, max);
1192         } else if (mQsSizeChangeAnimator != null) {
1193             return (int) mQsSizeChangeAnimator.getAnimatedValue();
1194         } else if (mKeyguardShowing && mScrollYOverride == -1) {
1195 
1196             // We can only do the smoother transition on Keyguard when we also are not collapsing
1197             // from a scrolled quick settings.
1198             return interpolate(getQsExpansionFraction(),
1199                     mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
1200                     mQsMaxExpansionHeight);
1201         } else {
1202             return mQsExpansionHeight;
1203         }
1204     }
1205 
requestScrollerTopPaddingUpdate(boolean animate)1206     private void requestScrollerTopPaddingUpdate(boolean animate) {
1207         mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
1208                 mScrollView.getScrollY(),
1209                 mAnimateNextTopPaddingChange || animate,
1210                 mKeyguardShowing
1211                         && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
1212         mAnimateNextTopPaddingChange = false;
1213     }
1214 
trackMovement(MotionEvent event)1215     private void trackMovement(MotionEvent event) {
1216         if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
1217         mLastTouchX = event.getX();
1218         mLastTouchY = event.getY();
1219     }
1220 
initVelocityTracker()1221     private void initVelocityTracker() {
1222         if (mVelocityTracker != null) {
1223             mVelocityTracker.recycle();
1224         }
1225         mVelocityTracker = VelocityTracker.obtain();
1226     }
1227 
getCurrentVelocity()1228     private float getCurrentVelocity() {
1229         if (mVelocityTracker == null) {
1230             return 0;
1231         }
1232         mVelocityTracker.computeCurrentVelocity(1000);
1233         return mVelocityTracker.getYVelocity();
1234     }
1235 
cancelAnimation()1236     private void cancelAnimation() {
1237         if (mQsExpansionAnimator != null) {
1238             mQsExpansionAnimator.cancel();
1239         }
1240     }
1241 
flingSettings(float vel, boolean expand)1242     private void flingSettings(float vel, boolean expand) {
1243         flingSettings(vel, expand, null);
1244     }
1245 
flingSettings(float vel, boolean expand, final Runnable onFinishRunnable)1246     private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
1247         float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
1248         if (target == mQsExpansionHeight) {
1249             mScrollYOverride = -1;
1250             if (onFinishRunnable != null) {
1251                 onFinishRunnable.run();
1252             }
1253             return;
1254         }
1255         boolean belowFalsingThreshold = isBelowFalsingThreshold();
1256         if (belowFalsingThreshold) {
1257             vel = 0;
1258         }
1259         mScrollView.setBlockFlinging(true);
1260         ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
1261         mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
1262         if (belowFalsingThreshold) {
1263             animator.setDuration(350);
1264         }
1265         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1266             @Override
1267             public void onAnimationUpdate(ValueAnimator animation) {
1268                 setQsExpansion((Float) animation.getAnimatedValue());
1269             }
1270         });
1271         animator.addListener(new AnimatorListenerAdapter() {
1272             @Override
1273             public void onAnimationEnd(Animator animation) {
1274                 mScrollView.setBlockFlinging(false);
1275                 mScrollYOverride = -1;
1276                 mQsExpansionAnimator = null;
1277                 if (onFinishRunnable != null) {
1278                     onFinishRunnable.run();
1279                 }
1280             }
1281         });
1282         animator.start();
1283         mQsExpansionAnimator = animator;
1284         mQsAnimatorExpand = expand;
1285     }
1286 
1287     /**
1288      * @return Whether we should intercept a gesture to open Quick Settings.
1289      */
shouldQuickSettingsIntercept(float x, float y, float yDiff)1290     private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
1291         if (!mQsExpansionEnabled) {
1292             return false;
1293         }
1294         View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
1295         boolean onHeader = x >= header.getLeft() && x <= header.getRight()
1296                 && y >= header.getTop() && y <= header.getBottom();
1297         if (mQsExpanded) {
1298             return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
1299         } else {
1300             return onHeader;
1301         }
1302     }
1303 
1304     @Override
isScrolledToBottom()1305     protected boolean isScrolledToBottom() {
1306         if (!isInSettings()) {
1307             return mStatusBar.getBarState() == StatusBarState.KEYGUARD
1308                     || mNotificationStackScroller.isScrolledToBottom();
1309         } else {
1310             return mScrollView.isScrolledToBottom();
1311         }
1312     }
1313 
1314     @Override
getMaxPanelHeight()1315     protected int getMaxPanelHeight() {
1316         int min = mStatusBarMinHeight;
1317         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
1318                 && mNotificationStackScroller.getNotGoneChildCount() == 0) {
1319             int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
1320                     * HEADER_RUBBERBAND_FACTOR);
1321             min = Math.max(min, minHeight);
1322         }
1323         int maxHeight;
1324         if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
1325             maxHeight = calculatePanelHeightQsExpanded();
1326         } else {
1327             maxHeight = calculatePanelHeightShade();
1328         }
1329         maxHeight = Math.max(maxHeight, min);
1330         return maxHeight;
1331     }
1332 
isInSettings()1333     private boolean isInSettings() {
1334         return mQsExpanded;
1335     }
1336 
1337     @Override
onHeightUpdated(float expandedHeight)1338     protected void onHeightUpdated(float expandedHeight) {
1339         if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
1340             positionClockAndNotifications();
1341         }
1342         if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
1343                 && !mQsExpansionFromOverscroll) {
1344             float t;
1345             if (mKeyguardShowing) {
1346 
1347                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
1348                 t = expandedHeight / getMaxPanelHeight();
1349             } else {
1350 
1351                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
1352                 // minimum QS expansion + minStackHeight
1353                 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
1354                         + mNotificationStackScroller.getMinStackHeight();
1355                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
1356                 t = (expandedHeight - panelHeightQsCollapsed)
1357                         / (panelHeightQsExpanded - panelHeightQsCollapsed);
1358             }
1359             setQsExpansion(mQsMinExpansionHeight
1360                     + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
1361         }
1362         mNotificationStackScroller.setStackHeight(expandedHeight);
1363         updateHeader();
1364         updateUnlockIcon();
1365         updateNotificationTranslucency();
1366         if (DEBUG) {
1367             invalidate();
1368         }
1369     }
1370 
1371     /**
1372      * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
1373      *         collapsing QS / the panel when QS was scrolled
1374      */
getTempQsMaxExpansion()1375     private int getTempQsMaxExpansion() {
1376         int qsTempMaxExpansion = mQsMaxExpansionHeight;
1377         if (mScrollYOverride != -1) {
1378             qsTempMaxExpansion -= mScrollYOverride;
1379         }
1380         return qsTempMaxExpansion;
1381     }
1382 
calculatePanelHeightShade()1383     private int calculatePanelHeightShade() {
1384         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
1385         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
1386                 - mTopPaddingAdjustment;
1387         maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
1388         return maxHeight;
1389     }
1390 
calculatePanelHeightQsExpanded()1391     private int calculatePanelHeightQsExpanded() {
1392         float notificationHeight = mNotificationStackScroller.getHeight()
1393                 - mNotificationStackScroller.getEmptyBottomMargin()
1394                 - mNotificationStackScroller.getTopPadding();
1395 
1396         // When only empty shade view is visible in QS collapsed state, simulate that we would have
1397         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
1398         // and expanding/collapsing the whole panel from/to quick settings.
1399         if (mNotificationStackScroller.getNotGoneChildCount() == 0
1400                 && mShadeEmpty) {
1401             notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight()
1402                     + mNotificationStackScroller.getBottomStackPeekSize()
1403                     + mNotificationStackScroller.getCollapseSecondCardPadding();
1404         }
1405         int maxQsHeight = mQsMaxExpansionHeight;
1406 
1407         // If an animation is changing the size of the QS panel, take the animated value.
1408         if (mQsSizeChangeAnimator != null) {
1409             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
1410         }
1411         float totalHeight = Math.max(
1412                 maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(),
1413                 mStatusBarState == StatusBarState.KEYGUARD
1414                         ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
1415                         : 0)
1416                 + notificationHeight;
1417         if (totalHeight > mNotificationStackScroller.getHeight()) {
1418             float fullyCollapsedHeight = maxQsHeight
1419                     + mNotificationStackScroller.getMinStackHeight()
1420                     + mNotificationStackScroller.getNotificationTopPadding()
1421                     - getScrollViewScrollY();
1422             totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
1423         }
1424         return (int) totalHeight;
1425     }
1426 
getScrollViewScrollY()1427     private int getScrollViewScrollY() {
1428         if (mScrollYOverride != -1 && !mQsTracking) {
1429             return mScrollYOverride;
1430         } else {
1431             return mScrollView.getScrollY();
1432         }
1433     }
updateNotificationTranslucency()1434     private void updateNotificationTranslucency() {
1435         float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
1436                 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
1437                         - mNotificationStackScroller.getCollapseSecondCardPadding());
1438         alpha = Math.max(0, Math.min(alpha, 1));
1439         alpha = (float) Math.pow(alpha, 0.75);
1440         if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
1441             mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
1442         } else if (alpha == 1f
1443                 && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
1444             mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
1445         }
1446         mNotificationStackScroller.setAlpha(alpha);
1447     }
1448 
1449     @Override
getOverExpansionAmount()1450     protected float getOverExpansionAmount() {
1451         return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
1452     }
1453 
1454     @Override
getOverExpansionPixels()1455     protected float getOverExpansionPixels() {
1456         return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
1457     }
1458 
updateUnlockIcon()1459     private void updateUnlockIcon() {
1460         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1461                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1462             boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
1463             KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1464             if (active && !mUnlockIconActive && mTracking) {
1465                 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
1466                 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
1467                         mFastOutLinearInterpolator);
1468             } else if (!active && mUnlockIconActive && mTracking) {
1469                 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
1470                         150, mFastOutLinearInterpolator, null);
1471                 lockIcon.setImageScale(1.0f, true, 150,
1472                         mFastOutLinearInterpolator);
1473             }
1474             mUnlockIconActive = active;
1475         }
1476     }
1477 
1478     /**
1479      * Hides the header when notifications are colliding with it.
1480      */
updateHeader()1481     private void updateHeader() {
1482         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1483                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1484             updateHeaderKeyguard();
1485         } else {
1486             updateHeaderShade();
1487         }
1488 
1489     }
1490 
updateHeaderShade()1491     private void updateHeaderShade() {
1492         if (!mHeaderAnimatingIn) {
1493             mHeader.setTranslationY(getHeaderTranslation());
1494         }
1495         setQsTranslation(mQsExpansionHeight);
1496     }
1497 
getHeaderTranslation()1498     private float getHeaderTranslation() {
1499         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1500                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1501             return 0;
1502         }
1503         if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1504             if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
1505                 return 0;
1506             } else {
1507                 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
1508             }
1509         }
1510         return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
1511     }
1512 
updateHeaderKeyguard()1513     private void updateHeaderKeyguard() {
1514         float alphaNotifications;
1515         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1516 
1517             // When on Keyguard, we hide the header as soon as the top card of the notification
1518             // stack scroller is close enough (collision distance) to the bottom of the header.
1519             alphaNotifications = getNotificationsTopY()
1520                     /
1521                     (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
1522         } else {
1523 
1524             // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
1525             // soon as we start translating the stack.
1526             alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
1527         }
1528         alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1);
1529         alphaNotifications = (float) Math.pow(alphaNotifications, 0.75);
1530         float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
1531         mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion)
1532                 * mKeyguardStatusBarAnimateAlpha);
1533         mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications));
1534         setQsTranslation(mQsExpansionHeight);
1535     }
1536 
getNotificationsTopY()1537     private float getNotificationsTopY() {
1538         if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1539             return getExpandedHeight();
1540         }
1541         return mNotificationStackScroller.getNotificationsTopY();
1542     }
1543 
1544     @Override
onExpandingStarted()1545     protected void onExpandingStarted() {
1546         super.onExpandingStarted();
1547         mNotificationStackScroller.onExpansionStarted();
1548         mIsExpanding = true;
1549         mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
1550         if (mQsExpanded) {
1551             onQsExpansionStarted();
1552         }
1553     }
1554 
1555     @Override
onExpandingFinished()1556     protected void onExpandingFinished() {
1557         super.onExpandingFinished();
1558         mNotificationStackScroller.onExpansionStopped();
1559         mIsExpanding = false;
1560         mScrollYOverride = -1;
1561         if (mExpandedHeight == 0f) {
1562             setListening(false);
1563         } else {
1564             setListening(true);
1565         }
1566         mQsExpandImmediate = false;
1567         mTwoFingerQsExpandPossible = false;
1568     }
1569 
setListening(boolean listening)1570     private void setListening(boolean listening) {
1571         mHeader.setListening(listening);
1572         mKeyguardStatusBar.setListening(listening);
1573         mQsPanel.setListening(listening);
1574     }
1575 
1576     @Override
instantExpand()1577     public void instantExpand() {
1578         super.instantExpand();
1579         setListening(true);
1580     }
1581 
1582     @Override
setOverExpansion(float overExpansion, boolean isPixels)1583     protected void setOverExpansion(float overExpansion, boolean isPixels) {
1584         if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
1585             return;
1586         }
1587         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1588             mNotificationStackScroller.setOnHeightChangedListener(null);
1589             if (isPixels) {
1590                 mNotificationStackScroller.setOverScrolledPixels(
1591                         overExpansion, true /* onTop */, false /* animate */);
1592             } else {
1593                 mNotificationStackScroller.setOverScrollAmount(
1594                         overExpansion, true /* onTop */, false /* animate */);
1595             }
1596             mNotificationStackScroller.setOnHeightChangedListener(this);
1597         }
1598     }
1599 
1600     @Override
onTrackingStarted()1601     protected void onTrackingStarted() {
1602         super.onTrackingStarted();
1603         if (mQsFullyExpanded) {
1604             mQsExpandImmediate = true;
1605         }
1606         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1607                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1608             mAfforanceHelper.animateHideLeftRightIcon();
1609         }
1610     }
1611 
1612     @Override
onTrackingStopped(boolean expand)1613     protected void onTrackingStopped(boolean expand) {
1614         super.onTrackingStopped(expand);
1615         if (expand) {
1616             mNotificationStackScroller.setOverScrolledPixels(
1617                     0.0f, true /* onTop */, true /* animate */);
1618         }
1619         if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1620                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1621             if (!mHintAnimationRunning) {
1622                 mAfforanceHelper.reset(true);
1623             }
1624         }
1625         if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1626                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1627             KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1628             lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
1629             lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
1630         }
1631     }
1632 
1633     @Override
onHeightChanged(ExpandableView view)1634     public void onHeightChanged(ExpandableView view) {
1635 
1636         // Block update if we are in quick settings and just the top padding changed
1637         // (i.e. view == null).
1638         if (view == null && mQsExpanded) {
1639             return;
1640         }
1641         requestPanelHeightUpdate();
1642     }
1643 
1644     @Override
onReset(ExpandableView view)1645     public void onReset(ExpandableView view) {
1646     }
1647 
1648     @Override
onScrollChanged()1649     public void onScrollChanged() {
1650         if (mQsExpanded) {
1651             requestScrollerTopPaddingUpdate(false /* animate */);
1652             requestPanelHeightUpdate();
1653         }
1654     }
1655 
1656     @Override
onConfigurationChanged(Configuration newConfig)1657     protected void onConfigurationChanged(Configuration newConfig) {
1658         super.onConfigurationChanged(newConfig);
1659         mAfforanceHelper.onConfigurationChanged();
1660     }
1661 
1662     @Override
onRtlPropertiesChanged(int layoutDirection)1663     public void onRtlPropertiesChanged(int layoutDirection) {
1664         if (layoutDirection != mOldLayoutDirection) {
1665             mAfforanceHelper.onRtlPropertiesChanged();
1666             mOldLayoutDirection = layoutDirection;
1667         }
1668     }
1669 
1670     @Override
onClick(View v)1671     public void onClick(View v) {
1672         if (v == mHeader) {
1673             onQsExpansionStarted();
1674             if (mQsExpanded) {
1675                 flingSettings(0 /* vel */, false /* expand */);
1676             } else if (mQsExpansionEnabled) {
1677                 flingSettings(0 /* vel */, true /* expand */);
1678             }
1679         }
1680     }
1681 
1682     @Override
onAnimationToSideStarted(boolean rightPage, float translation, float vel)1683     public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
1684         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1685         mIsLaunchTransitionRunning = true;
1686         mLaunchAnimationEndRunnable = null;
1687         float displayDensity = mStatusBar.getDisplayDensity();
1688         int lengthDp = Math.abs((int) (translation / displayDensity));
1689         int velocityDp = Math.abs((int) (vel / displayDensity));
1690         if (start) {
1691             EventLogTags.writeSysuiLockscreenGesture(
1692                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
1693             mKeyguardBottomArea.launchPhone();
1694         } else {
1695             EventLogTags.writeSysuiLockscreenGesture(
1696                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
1697             mSecureCameraLaunchManager.startSecureCameraLaunch();
1698         }
1699         mStatusBar.startLaunchTransitionTimeout();
1700         mBlockTouches = true;
1701     }
1702 
1703     @Override
onAnimationToSideEnded()1704     public void onAnimationToSideEnded() {
1705         mIsLaunchTransitionRunning = false;
1706         mIsLaunchTransitionFinished = true;
1707         if (mLaunchAnimationEndRunnable != null) {
1708             mLaunchAnimationEndRunnable.run();
1709             mLaunchAnimationEndRunnable = null;
1710         }
1711     }
1712 
1713     @Override
onEdgeClicked(boolean right)1714     protected void onEdgeClicked(boolean right) {
1715         if ((right && getRightIcon().getVisibility() != View.VISIBLE)
1716                 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)
1717                 || isDozing()) {
1718             return;
1719         }
1720         mHintAnimationRunning = true;
1721         mAfforanceHelper.startHintAnimation(right, new Runnable() {
1722             @Override
1723             public void run() {
1724                 mHintAnimationRunning = false;
1725                 mStatusBar.onHintFinished();
1726             }
1727         });
1728         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
1729         if (start) {
1730             mStatusBar.onPhoneHintStarted();
1731         } else {
1732             mStatusBar.onCameraHintStarted();
1733         }
1734     }
1735 
1736     @Override
startUnlockHintAnimation()1737     protected void startUnlockHintAnimation() {
1738         super.startUnlockHintAnimation();
1739         startHighlightIconAnimation(getCenterIcon());
1740     }
1741 
1742     /**
1743      * Starts the highlight (making it fully opaque) animation on an icon.
1744      */
startHighlightIconAnimation(final KeyguardAffordanceView icon)1745     private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1746         icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1747                 mFastOutSlowInInterpolator, new Runnable() {
1748                     @Override
1749                     public void run() {
1750                         icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
1751                                 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1752                                 mFastOutSlowInInterpolator, null);
1753                     }
1754                 });
1755     }
1756 
1757     @Override
getPageWidth()1758     public float getPageWidth() {
1759         return getWidth();
1760     }
1761 
1762     @Override
onSwipingStarted()1763     public void onSwipingStarted() {
1764         mSecureCameraLaunchManager.onSwipingStarted();
1765         requestDisallowInterceptTouchEvent(true);
1766         mOnlyAffordanceInThisMotion = true;
1767     }
1768 
1769     @Override
getLeftIcon()1770     public KeyguardAffordanceView getLeftIcon() {
1771         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1772                 ? mKeyguardBottomArea.getCameraView()
1773                 : mKeyguardBottomArea.getPhoneView();
1774     }
1775 
1776     @Override
getCenterIcon()1777     public KeyguardAffordanceView getCenterIcon() {
1778         return mKeyguardBottomArea.getLockIcon();
1779     }
1780 
1781     @Override
getRightIcon()1782     public KeyguardAffordanceView getRightIcon() {
1783         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1784                 ? mKeyguardBottomArea.getPhoneView()
1785                 : mKeyguardBottomArea.getCameraView();
1786     }
1787 
1788     @Override
getLeftPreview()1789     public View getLeftPreview() {
1790         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1791                 ? mKeyguardBottomArea.getCameraPreview()
1792                 : mKeyguardBottomArea.getPhonePreview();
1793     }
1794 
1795     @Override
getRightPreview()1796     public View getRightPreview() {
1797         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1798                 ? mKeyguardBottomArea.getPhonePreview()
1799                 : mKeyguardBottomArea.getCameraPreview();
1800     }
1801 
1802     @Override
getAffordanceFalsingFactor()1803     public float getAffordanceFalsingFactor() {
1804         return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
1805     }
1806 
1807     @Override
getPeekHeight()1808     protected float getPeekHeight() {
1809         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1810             return mNotificationStackScroller.getPeekHeight();
1811         } else {
1812             return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
1813         }
1814     }
1815 
1816     @Override
getCannedFlingDurationFactor()1817     protected float getCannedFlingDurationFactor() {
1818         if (mQsExpanded) {
1819             return 0.7f;
1820         } else {
1821             return 0.6f;
1822         }
1823     }
1824 
1825     @Override
fullyExpandedClearAllVisible()1826     protected boolean fullyExpandedClearAllVisible() {
1827         return mNotificationStackScroller.isDismissViewNotGone()
1828                 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
1829     }
1830 
1831     @Override
isClearAllVisible()1832     protected boolean isClearAllVisible() {
1833         return mNotificationStackScroller.isDismissViewVisible();
1834     }
1835 
1836     @Override
getClearAllHeight()1837     protected int getClearAllHeight() {
1838         return mNotificationStackScroller.getDismissViewHeight();
1839     }
1840 
1841     @Override
isTrackingBlocked()1842     protected boolean isTrackingBlocked() {
1843         return mConflictingQsExpansionGesture && mQsExpanded;
1844     }
1845 
notifyVisibleChildrenChanged()1846     public void notifyVisibleChildrenChanged() {
1847         if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
1848             mReserveNotificationSpace.setVisibility(View.VISIBLE);
1849         } else {
1850             mReserveNotificationSpace.setVisibility(View.GONE);
1851         }
1852     }
1853 
isQsExpanded()1854     public boolean isQsExpanded() {
1855         return mQsExpanded;
1856     }
1857 
isQsDetailShowing()1858     public boolean isQsDetailShowing() {
1859         return mQsPanel.isShowingDetail();
1860     }
1861 
closeQsDetail()1862     public void closeQsDetail() {
1863         mQsPanel.closeDetail();
1864     }
1865 
1866     @Override
shouldDelayChildPressedState()1867     public boolean shouldDelayChildPressedState() {
1868         return true;
1869     }
1870 
isLaunchTransitionFinished()1871     public boolean isLaunchTransitionFinished() {
1872         return mIsLaunchTransitionFinished;
1873     }
1874 
isLaunchTransitionRunning()1875     public boolean isLaunchTransitionRunning() {
1876         return mIsLaunchTransitionRunning;
1877     }
1878 
setLaunchTransitionEndRunnable(Runnable r)1879     public void setLaunchTransitionEndRunnable(Runnable r) {
1880         mLaunchAnimationEndRunnable = r;
1881     }
1882 
setEmptyDragAmount(float amount)1883     public void setEmptyDragAmount(float amount) {
1884         float factor = 0.8f;
1885         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1886             factor = 0.4f;
1887         } else if (!mStatusBar.hasActiveNotifications()) {
1888             factor = 0.4f;
1889         }
1890         mEmptyDragAmount = amount * factor;
1891         positionClockAndNotifications();
1892     }
1893 
interpolate(float t, float start, float end)1894     private static float interpolate(float t, float start, float end) {
1895         return (1 - t) * start + t * end;
1896     }
1897 
setDozing(boolean dozing, boolean animate)1898     public void setDozing(boolean dozing, boolean animate) {
1899         if (dozing == mDozing) return;
1900         mDozing = dozing;
1901         if (mDozing) {
1902             mKeyguardStatusBar.setVisibility(View.INVISIBLE);
1903             mKeyguardBottomArea.setVisibility(View.INVISIBLE);
1904         } else {
1905             mKeyguardBottomArea.setVisibility(View.VISIBLE);
1906             mKeyguardStatusBar.setVisibility(View.VISIBLE);
1907             if (animate) {
1908                 animateKeyguardStatusBarIn();
1909                 mKeyguardBottomArea.startFinishDozeAnimation();
1910             }
1911         }
1912     }
1913 
1914     @Override
isDozing()1915     public boolean isDozing() {
1916         return mDozing;
1917     }
1918 
setShadeEmpty(boolean shadeEmpty)1919     public void setShadeEmpty(boolean shadeEmpty) {
1920         mShadeEmpty = shadeEmpty;
1921         updateEmptyShadeView();
1922     }
1923 
updateEmptyShadeView()1924     private void updateEmptyShadeView() {
1925 
1926         // Hide "No notifications" in QS.
1927         mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
1928     }
1929 
setQsScrimEnabled(boolean qsScrimEnabled)1930     public void setQsScrimEnabled(boolean qsScrimEnabled) {
1931         boolean changed = mQsScrimEnabled != qsScrimEnabled;
1932         mQsScrimEnabled = qsScrimEnabled;
1933         if (changed) {
1934             updateQsState();
1935         }
1936     }
1937 
setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher)1938     public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
1939         mKeyguardUserSwitcher = keyguardUserSwitcher;
1940     }
1941 
1942     private final Runnable mUpdateHeader = new Runnable() {
1943         @Override
1944         public void run() {
1945             mHeader.updateEverything();
1946         }
1947     };
1948 
onScreenTurnedOn()1949     public void onScreenTurnedOn() {
1950         mKeyguardStatusView.refreshTime();
1951     }
1952 
1953     @Override
onEmptySpaceClicked(float x, float y)1954     public void onEmptySpaceClicked(float x, float y) {
1955         onEmptySpaceClick(x);
1956     }
1957 
1958     @Override
dispatchDraw(Canvas canvas)1959     protected void dispatchDraw(Canvas canvas) {
1960         super.dispatchDraw(canvas);
1961         if (DEBUG) {
1962             Paint p = new Paint();
1963             p.setColor(Color.RED);
1964             p.setStrokeWidth(2);
1965             p.setStyle(Paint.Style.STROKE);
1966             canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
1967             p.setColor(Color.BLUE);
1968             canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
1969             p.setColor(Color.GREEN);
1970             canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
1971                     calculatePanelHeightQsExpanded(), p);
1972             p.setColor(Color.YELLOW);
1973             canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
1974                     calculatePanelHeightShade(), p);
1975             p.setColor(Color.MAGENTA);
1976             canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
1977                     calculateQsTopPadding(), p);
1978             p.setColor(Color.CYAN);
1979             canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
1980                     mNotificationStackScroller.getTopPadding(), p);
1981         }
1982     }
1983 }
1984