1 /*
2  * Copyright (C) 2019 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 static java.lang.Float.isNaN;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ObjectAnimator;
24 import android.animation.ValueAnimator;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.os.SystemClock;
28 import android.os.VibrationEffect;
29 import android.util.Log;
30 import android.view.InputDevice;
31 import android.view.MotionEvent;
32 import android.view.VelocityTracker;
33 import android.view.View;
34 import android.view.ViewConfiguration;
35 import android.view.ViewGroup;
36 import android.view.ViewTreeObserver;
37 import android.view.animation.Interpolator;
38 
39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
40 import com.android.internal.util.LatencyTracker;
41 import com.android.systemui.DejankUtils;
42 import com.android.systemui.Interpolators;
43 import com.android.systemui.R;
44 import com.android.systemui.doze.DozeLog;
45 import com.android.systemui.plugins.FalsingManager;
46 import com.android.systemui.statusbar.FlingAnimationUtils;
47 import com.android.systemui.statusbar.StatusBarState;
48 import com.android.systemui.statusbar.SysuiStatusBarStateController;
49 import com.android.systemui.statusbar.VibratorHelper;
50 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
51 import com.android.systemui.statusbar.policy.KeyguardStateController;
52 
53 import java.io.FileDescriptor;
54 import java.io.PrintWriter;
55 import java.util.ArrayList;
56 
57 public abstract class PanelViewController {
58     public static final boolean DEBUG = PanelBar.DEBUG;
59     public static final String TAG = PanelView.class.getSimpleName();
60     private static final int INITIAL_OPENING_PEEK_DURATION = 200;
61     private static final int PEEK_ANIMATION_DURATION = 360;
62     private static final int NO_FIXED_DURATION = -1;
63     protected long mDownTime;
64     protected boolean mTouchSlopExceededBeforeDown;
65     private float mMinExpandHeight;
66     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
67     private boolean mPanelUpdateWhenAnimatorEnds;
68     private boolean mVibrateOnOpening;
69     protected boolean mLaunchingNotification;
70     private int mFixedDuration = NO_FIXED_DURATION;
71     protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
72 
logf(String fmt, Object... args)73     private void logf(String fmt, Object... args) {
74         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
75     }
76 
77     protected StatusBar mStatusBar;
78     protected HeadsUpManagerPhone mHeadsUpManager;
79     protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
80 
81     private float mPeekHeight;
82     private float mHintDistance;
83     private float mInitialOffsetOnTouch;
84     private boolean mCollapsedAndHeadsUpOnDown;
85     private float mExpandedFraction = 0;
86     protected float mExpandedHeight = 0;
87     private boolean mPanelClosedOnDown;
88     private boolean mHasLayoutedSinceDown;
89     private float mUpdateFlingVelocity;
90     private boolean mUpdateFlingOnLayout;
91     private boolean mPeekTouching;
92     private boolean mJustPeeked;
93     private boolean mClosing;
94     protected boolean mTracking;
95     private boolean mTouchSlopExceeded;
96     private int mTrackingPointer;
97     private int mTouchSlop;
98     private float mSlopMultiplier;
99     protected boolean mHintAnimationRunning;
100     private boolean mOverExpandedBeforeFling;
101     private boolean mTouchAboveFalsingThreshold;
102     private int mUnlockFalsingThreshold;
103     private boolean mTouchStartedInEmptyArea;
104     private boolean mMotionAborted;
105     private boolean mUpwardsWhenThresholdReached;
106     private boolean mAnimatingOnDown;
107 
108     private ValueAnimator mHeightAnimator;
109     private ObjectAnimator mPeekAnimator;
110     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
111     private FlingAnimationUtils mFlingAnimationUtils;
112     private FlingAnimationUtils mFlingAnimationUtilsClosing;
113     private FlingAnimationUtils mFlingAnimationUtilsDismissing;
114     private final LatencyTracker mLatencyTracker;
115     private final FalsingManager mFalsingManager;
116     private final DozeLog mDozeLog;
117     private final VibratorHelper mVibratorHelper;
118 
119     /**
120      * Whether an instant expand request is currently pending and we are just waiting for layout.
121      */
122     private boolean mInstantExpanding;
123     private boolean mAnimateAfterExpanding;
124 
125     PanelBar mBar;
126 
127     private String mViewName;
128     private float mInitialTouchY;
129     private float mInitialTouchX;
130     private boolean mTouchDisabled;
131 
132     /**
133      * Whether or not the PanelView can be expanded or collapsed with a drag.
134      */
135     private boolean mNotificationsDragEnabled;
136 
137     private Interpolator mBounceInterpolator;
138     protected KeyguardBottomAreaView mKeyguardBottomArea;
139 
140     /**
141      * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
142      */
143     private float mNextCollapseSpeedUpFactor = 1.0f;
144 
145     protected boolean mExpanding;
146     private boolean mGestureWaitForTouchSlop;
147     private boolean mIgnoreXTouchSlop;
148     private boolean mExpandLatencyTracking;
149     private final PanelView mView;
150     protected final Resources mResources;
151     protected final KeyguardStateController mKeyguardStateController;
152     protected final SysuiStatusBarStateController mStatusBarStateController;
153 
onExpandingFinished()154     protected void onExpandingFinished() {
155         mBar.onExpandingFinished();
156     }
157 
onExpandingStarted()158     protected void onExpandingStarted() {
159     }
160 
notifyExpandingStarted()161     protected void notifyExpandingStarted() {
162         if (!mExpanding) {
163             mExpanding = true;
164             onExpandingStarted();
165         }
166     }
167 
notifyExpandingFinished()168     protected final void notifyExpandingFinished() {
169         endClosing();
170         if (mExpanding) {
171             mExpanding = false;
172             onExpandingFinished();
173         }
174     }
175 
runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished)176     private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
177         mPeekHeight = peekHeight;
178         if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
179         if (mHeightAnimator != null) {
180             return;
181         }
182         if (mPeekAnimator != null) {
183             mPeekAnimator.cancel();
184         }
185         mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight).setDuration(
186                 duration);
187         mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
188         mPeekAnimator.addListener(new AnimatorListenerAdapter() {
189             private boolean mCancelled;
190 
191             @Override
192             public void onAnimationCancel(Animator animation) {
193                 mCancelled = true;
194             }
195 
196             @Override
197             public void onAnimationEnd(Animator animation) {
198                 mPeekAnimator = null;
199                 if (!mCancelled && collapseWhenFinished) {
200                     mView.postOnAnimation(mPostCollapseRunnable);
201                 }
202 
203             }
204         });
205         notifyExpandingStarted();
206         mPeekAnimator.start();
207         mJustPeeked = true;
208     }
209 
PanelViewController(PanelView view, FalsingManager falsingManager, DozeLog dozeLog, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper, LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager)210     public PanelViewController(PanelView view,
211             FalsingManager falsingManager, DozeLog dozeLog,
212             KeyguardStateController keyguardStateController,
213             SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
214             LatencyTracker latencyTracker,
215             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
216             StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
217         mView = view;
218         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
219             @Override
220             public void onViewAttachedToWindow(View v) {
221                 mViewName = mResources.getResourceName(mView.getId());
222             }
223 
224             @Override
225             public void onViewDetachedFromWindow(View v) {
226             }
227         });
228 
229         mView.addOnLayoutChangeListener(createLayoutChangeListener());
230         mView.setOnTouchListener(createTouchHandler());
231         mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
232 
233         mResources = mView.getResources();
234         mKeyguardStateController = keyguardStateController;
235         mStatusBarStateController = statusBarStateController;
236         mFlingAnimationUtils = flingAnimationUtilsBuilder
237                 .reset()
238                 .setMaxLengthSeconds(0.6f)
239                 .setSpeedUpFactor(0.6f)
240                 .build();
241         mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
242                 .reset()
243                 .setMaxLengthSeconds(0.5f)
244                 .setSpeedUpFactor(0.6f)
245                 .build();
246         mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
247                 .reset()
248                 .setMaxLengthSeconds(0.5f)
249                 .setSpeedUpFactor(0.6f)
250                 .setX2(0.6f)
251                 .setY2(0.84f)
252                 .build();
253         mLatencyTracker = latencyTracker;
254         mBounceInterpolator = new BounceInterpolator();
255         mFalsingManager = falsingManager;
256         mDozeLog = dozeLog;
257         mNotificationsDragEnabled = mResources.getBoolean(
258                 R.bool.config_enableNotificationShadeDrag);
259         mVibratorHelper = vibratorHelper;
260         mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
261         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
262     }
263 
loadDimens()264     protected void loadDimens() {
265         final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
266         mTouchSlop = configuration.getScaledTouchSlop();
267         mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
268         mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
269         mUnlockFalsingThreshold = mResources.getDimensionPixelSize(
270                 R.dimen.unlock_falsing_threshold);
271     }
272 
getTouchSlop(MotionEvent event)273     protected float getTouchSlop(MotionEvent event) {
274         // Adjust the touch slop if another gesture may be being performed.
275         return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
276                 ? mTouchSlop * mSlopMultiplier
277                 : mTouchSlop;
278     }
279 
addMovement(MotionEvent event)280     private void addMovement(MotionEvent event) {
281         // Add movement to velocity tracker using raw screen X and Y coordinates instead
282         // of window coordinates because the window frame may be moving at the same time.
283         float deltaX = event.getRawX() - event.getX();
284         float deltaY = event.getRawY() - event.getY();
285         event.offsetLocation(deltaX, deltaY);
286         mVelocityTracker.addMovement(event);
287         event.offsetLocation(-deltaX, -deltaY);
288     }
289 
setTouchAndAnimationDisabled(boolean disabled)290     public void setTouchAndAnimationDisabled(boolean disabled) {
291         mTouchDisabled = disabled;
292         if (mTouchDisabled) {
293             cancelHeightAnimator();
294             if (mTracking) {
295                 onTrackingStopped(true /* expanded */);
296             }
297             notifyExpandingFinished();
298         }
299     }
300 
startExpandLatencyTracking()301     public void startExpandLatencyTracking() {
302         if (mLatencyTracker.isEnabled()) {
303             mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
304             mExpandLatencyTracking = true;
305         }
306     }
307 
startOpening(MotionEvent event)308     private void startOpening(MotionEvent event) {
309         runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
310                 false /* collapseWhenFinished */);
311         notifyBarPanelExpansionChanged();
312         maybeVibrateOnOpening();
313 
314         //TODO: keyguard opens QS a different way; log that too?
315 
316         // Log the position of the swipe that opened the panel
317         float width = mStatusBar.getDisplayWidth();
318         float height = mStatusBar.getDisplayHeight();
319         int rot = mStatusBar.getRotation();
320 
321         mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
322                 (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
323         mLockscreenGestureLogger
324                 .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
325     }
326 
maybeVibrateOnOpening()327     protected void maybeVibrateOnOpening() {
328         if (mVibrateOnOpening) {
329             mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
330         }
331     }
332 
getOpeningHeight()333     protected abstract float getOpeningHeight();
334 
335     /**
336      * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
337      * horizontal direction
338      */
isDirectionUpwards(float x, float y)339     private boolean isDirectionUpwards(float x, float y) {
340         float xDiff = x - mInitialTouchX;
341         float yDiff = y - mInitialTouchY;
342         if (yDiff >= 0) {
343             return false;
344         }
345         return Math.abs(yDiff) >= Math.abs(xDiff);
346     }
347 
startExpandingFromPeek()348     protected void startExpandingFromPeek() {
349         mStatusBar.handlePeekToExpandTransistion();
350     }
351 
startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight)352     protected void startExpandMotion(float newX, float newY, boolean startTracking,
353             float expandedHeight) {
354         mInitialOffsetOnTouch = expandedHeight;
355         mInitialTouchY = newY;
356         mInitialTouchX = newX;
357         if (startTracking) {
358             mTouchSlopExceeded = true;
359             setExpandedHeight(mInitialOffsetOnTouch);
360             onTrackingStarted();
361         }
362     }
363 
endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel)364     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
365         mTrackingPointer = -1;
366         if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialTouchX) > mTouchSlop
367                 || Math.abs(y - mInitialTouchY) > mTouchSlop
368                 || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
369             mVelocityTracker.computeCurrentVelocity(1000);
370             float vel = mVelocityTracker.getYVelocity();
371             float vectorVel = (float) Math.hypot(
372                     mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
373 
374             final boolean onKeyguard =
375                     mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
376 
377             final boolean expand;
378             if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
379                 // If we get a cancel, put the shade back to the state it was in when the gesture
380                 // started
381                 if (onKeyguard) {
382                     expand = true;
383                 } else {
384                     expand = !mPanelClosedOnDown;
385                 }
386             } else {
387                 expand = flingExpands(vel, vectorVel, x, y);
388             }
389 
390             mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
391                     mStatusBar.isFalsingThresholdNeeded(), mStatusBar.isWakeUpComingFromTouch());
392             // Log collapse gesture if on lock screen.
393             if (!expand && onKeyguard) {
394                 float displayDensity = mStatusBar.getDisplayDensity();
395                 int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
396                 int velocityDp = (int) Math.abs(vel / displayDensity);
397                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
398                 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
399             }
400             fling(vel, expand, isFalseTouch(x, y));
401             onTrackingStopped(expand);
402             mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
403             if (mUpdateFlingOnLayout) {
404                 mUpdateFlingVelocity = vel;
405             }
406         } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
407                 && !mStatusBar.isBouncerShowing()
408                 && !mKeyguardStateController.isKeyguardFadingAway()) {
409             long timePassed = SystemClock.uptimeMillis() - mDownTime;
410             if (timePassed < ViewConfiguration.getLongPressTimeout()) {
411                 // Lets show the user that he can actually expand the panel
412                 runPeekAnimation(
413                         PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
414             } else {
415                 // We need to collapse the panel since we peeked to the small height.
416                 mView.postOnAnimation(mPostCollapseRunnable);
417             }
418         } else if (!mStatusBar.isBouncerShowing()) {
419             boolean expands = onEmptySpaceClick(mInitialTouchX);
420             onTrackingStopped(expands);
421         }
422 
423         mVelocityTracker.clear();
424         mPeekTouching = false;
425     }
426 
getCurrentExpandVelocity()427     protected float getCurrentExpandVelocity() {
428         mVelocityTracker.computeCurrentVelocity(1000);
429         return mVelocityTracker.getYVelocity();
430     }
431 
getFalsingThreshold()432     private int getFalsingThreshold() {
433         float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
434         return (int) (mUnlockFalsingThreshold * factor);
435     }
436 
shouldGestureWaitForTouchSlop()437     protected abstract boolean shouldGestureWaitForTouchSlop();
438 
shouldGestureIgnoreXTouchSlop(float x, float y)439     protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
440 
onTrackingStopped(boolean expand)441     protected void onTrackingStopped(boolean expand) {
442         mTracking = false;
443         mBar.onTrackingStopped(expand);
444         notifyBarPanelExpansionChanged();
445     }
446 
onTrackingStarted()447     protected void onTrackingStarted() {
448         endClosing();
449         mTracking = true;
450         mBar.onTrackingStarted();
451         notifyExpandingStarted();
452         notifyBarPanelExpansionChanged();
453     }
454 
455     /**
456      * @return Whether a pair of coordinates are inside the visible view content bounds.
457      */
isInContentBounds(float x, float y)458     protected abstract boolean isInContentBounds(float x, float y);
459 
cancelHeightAnimator()460     protected void cancelHeightAnimator() {
461         if (mHeightAnimator != null) {
462             if (mHeightAnimator.isRunning()) {
463                 mPanelUpdateWhenAnimatorEnds = false;
464             }
465             mHeightAnimator.cancel();
466         }
467         endClosing();
468     }
469 
endClosing()470     private void endClosing() {
471         if (mClosing) {
472             mClosing = false;
473             onClosingFinished();
474         }
475     }
476 
canCollapsePanelOnTouch()477     protected boolean canCollapsePanelOnTouch() {
478         return true;
479     }
480 
getContentHeight()481     protected float getContentHeight() {
482         return mExpandedHeight;
483     }
484 
485     /**
486      * @param vel       the current vertical velocity of the motion
487      * @param vectorVel the length of the vectorial velocity
488      * @return whether a fling should expands the panel; contracts otherwise
489      */
flingExpands(float vel, float vectorVel, float x, float y)490     protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
491         if (mFalsingManager.isUnlockingDisabled()) {
492             return true;
493         }
494 
495         if (isFalseTouch(x, y)) {
496             return true;
497         }
498         if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
499             return shouldExpandWhenNotFlinging();
500         } else {
501             return vel > 0;
502         }
503     }
504 
shouldExpandWhenNotFlinging()505     protected boolean shouldExpandWhenNotFlinging() {
506         return getExpandedFraction() > 0.5f;
507     }
508 
509     /**
510      * @param x the final x-coordinate when the finger was lifted
511      * @param y the final y-coordinate when the finger was lifted
512      * @return whether this motion should be regarded as a false touch
513      */
isFalseTouch(float x, float y)514     private boolean isFalseTouch(float x, float y) {
515         if (!mStatusBar.isFalsingThresholdNeeded()) {
516             return false;
517         }
518         if (mFalsingManager.isClassifierEnabled()) {
519             return mFalsingManager.isFalseTouch();
520         }
521         if (!mTouchAboveFalsingThreshold) {
522             return true;
523         }
524         if (mUpwardsWhenThresholdReached) {
525             return false;
526         }
527         return !isDirectionUpwards(x, y);
528     }
529 
fling(float vel, boolean expand)530     protected void fling(float vel, boolean expand) {
531         fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
532     }
533 
fling(float vel, boolean expand, boolean expandBecauseOfFalsing)534     protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
535         fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
536     }
537 
fling(float vel, boolean expand, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)538     protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
539             boolean expandBecauseOfFalsing) {
540         cancelPeek();
541         float target = expand ? getMaxPanelHeight() : 0;
542         if (!expand) {
543             mClosing = true;
544         }
545         flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
546     }
547 
flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)548     protected void flingToHeight(float vel, boolean expand, float target,
549             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
550         // Hack to make the expand transition look nice when clear all button is visible - we make
551         // the animation only to the last notification, and then jump to the maximum panel height so
552         // clear all just fades in and the decelerating motion is towards the last notification.
553         final boolean clearAllExpandHack = expand &&
554                 shouldExpandToTopOfClearAll(getMaxPanelHeight() - getClearAllHeightWithPadding());
555         if (clearAllExpandHack) {
556             target = getMaxPanelHeight() - getClearAllHeightWithPadding();
557         }
558         if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
559             notifyExpandingFinished();
560             return;
561         }
562         mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
563         ValueAnimator animator = createHeightAnimator(target);
564         if (expand) {
565             if (expandBecauseOfFalsing && vel < 0) {
566                 vel = 0;
567             }
568             mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight());
569             if (vel == 0) {
570                 animator.setDuration(350);
571             }
572         } else {
573             if (shouldUseDismissingAnimation()) {
574                 if (vel == 0) {
575                     animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
576                     long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
577                     animator.setDuration(duration);
578                 } else {
579                     mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
580                             mView.getHeight());
581                 }
582             } else {
583                 mFlingAnimationUtilsClosing.apply(
584                         animator, mExpandedHeight, target, vel, mView.getHeight());
585             }
586 
587             // Make it shorter if we run a canned animation
588             if (vel == 0) {
589                 animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
590             }
591             if (mFixedDuration != NO_FIXED_DURATION) {
592                 animator.setDuration(mFixedDuration);
593             }
594         }
595         animator.addListener(new AnimatorListenerAdapter() {
596             private boolean mCancelled;
597 
598             @Override
599             public void onAnimationCancel(Animator animation) {
600                 mCancelled = true;
601             }
602 
603             @Override
604             public void onAnimationEnd(Animator animation) {
605                 if (clearAllExpandHack && !mCancelled) {
606                     setExpandedHeightInternal(getMaxPanelHeight());
607                 }
608                 setAnimator(null);
609                 if (!mCancelled) {
610                     notifyExpandingFinished();
611                 }
612                 notifyBarPanelExpansionChanged();
613             }
614         });
615         setAnimator(animator);
616         animator.start();
617     }
618 
619     /**
620      * When expanding, should we expand to the top of clear all and expand immediately?
621      * This will make sure that the animation will stop smoothly at the end of the last notification
622      * before the clear all affordance.
623      *
624      * @param targetHeight the height that we would animate to, right above clear all
625      *
626      * @return true if we can expand to the top of clear all
627      */
shouldExpandToTopOfClearAll(float targetHeight)628     protected boolean shouldExpandToTopOfClearAll(float targetHeight) {
629         return fullyExpandedClearAllVisible()
630                 && mExpandedHeight < targetHeight
631                 && !isClearAllVisible();
632     }
633 
shouldUseDismissingAnimation()634     protected abstract boolean shouldUseDismissingAnimation();
635 
getName()636     public String getName() {
637         return mViewName;
638     }
639 
setExpandedHeight(float height)640     public void setExpandedHeight(float height) {
641         if (DEBUG) logf("setExpandedHeight(%.1f)", height);
642         setExpandedHeightInternal(height + getOverExpansionPixels());
643     }
644 
requestPanelHeightUpdate()645     protected void requestPanelHeightUpdate() {
646         float currentMaxPanelHeight = getMaxPanelHeight();
647 
648         if (isFullyCollapsed()) {
649             return;
650         }
651 
652         if (currentMaxPanelHeight == mExpandedHeight) {
653             return;
654         }
655 
656         if (mPeekAnimator != null || mPeekTouching) {
657             return;
658         }
659 
660         if (mTracking && !isTrackingBlocked()) {
661             return;
662         }
663 
664         if (mHeightAnimator != null) {
665             mPanelUpdateWhenAnimatorEnds = true;
666             return;
667         }
668 
669         setExpandedHeight(currentMaxPanelHeight);
670     }
671 
setExpandedHeightInternal(float h)672     public void setExpandedHeightInternal(float h) {
673         if (isNaN(h)) {
674             Log.wtf(TAG, "ExpandedHeight set to NaN");
675         }
676         if (mExpandLatencyTracking && h != 0f) {
677             DejankUtils.postAfterTraversal(
678                     () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
679             mExpandLatencyTracking = false;
680         }
681         float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
682         if (mHeightAnimator == null) {
683             float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
684             if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
685                 setOverExpansion(overExpansionPixels, true /* isPixels */);
686             }
687             mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
688         } else {
689             mExpandedHeight = h;
690             if (mOverExpandedBeforeFling) {
691                 setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
692             }
693         }
694 
695         // If we are closing the panel and we are almost there due to a slow decelerating
696         // interpolator, abort the animation.
697         if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
698             mExpandedHeight = 0f;
699             if (mHeightAnimator != null) {
700                 mHeightAnimator.end();
701             }
702         }
703         mExpandedFraction = Math.min(1f,
704                 fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
705         onHeightUpdated(mExpandedHeight);
706         notifyBarPanelExpansionChanged();
707     }
708 
709     /**
710      * @return true if the panel tracking should be temporarily blocked; this is used when a
711      * conflicting gesture (opening QS) is happening
712      */
isTrackingBlocked()713     protected abstract boolean isTrackingBlocked();
714 
setOverExpansion(float overExpansion, boolean isPixels)715     protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
716 
onHeightUpdated(float expandedHeight)717     protected abstract void onHeightUpdated(float expandedHeight);
718 
getOverExpansionAmount()719     protected abstract float getOverExpansionAmount();
720 
getOverExpansionPixels()721     protected abstract float getOverExpansionPixels();
722 
723     /**
724      * This returns the maximum height of the panel. Children should override this if their
725      * desired height is not the full height.
726      *
727      * @return the default implementation simply returns the maximum height.
728      */
getMaxPanelHeight()729     protected abstract int getMaxPanelHeight();
730 
setExpandedFraction(float frac)731     public void setExpandedFraction(float frac) {
732         setExpandedHeight(getMaxPanelHeight() * frac);
733     }
734 
getExpandedHeight()735     public float getExpandedHeight() {
736         return mExpandedHeight;
737     }
738 
getExpandedFraction()739     public float getExpandedFraction() {
740         return mExpandedFraction;
741     }
742 
isFullyExpanded()743     public boolean isFullyExpanded() {
744         return mExpandedHeight >= getMaxPanelHeight();
745     }
746 
isFullyCollapsed()747     public boolean isFullyCollapsed() {
748         return mExpandedFraction <= 0.0f;
749     }
750 
isCollapsing()751     public boolean isCollapsing() {
752         return mClosing || mLaunchingNotification;
753     }
754 
isTracking()755     public boolean isTracking() {
756         return mTracking;
757     }
758 
setBar(PanelBar panelBar)759     public void setBar(PanelBar panelBar) {
760         mBar = panelBar;
761     }
762 
collapse(boolean delayed, float speedUpFactor)763     public void collapse(boolean delayed, float speedUpFactor) {
764         if (DEBUG) logf("collapse: " + this);
765         if (canPanelBeCollapsed()) {
766             cancelHeightAnimator();
767             notifyExpandingStarted();
768 
769             // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
770             mClosing = true;
771             if (delayed) {
772                 mNextCollapseSpeedUpFactor = speedUpFactor;
773                 mView.postDelayed(mFlingCollapseRunnable, 120);
774             } else {
775                 fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
776             }
777         }
778     }
779 
canPanelBeCollapsed()780     public boolean canPanelBeCollapsed() {
781         return !isFullyCollapsed() && !mTracking && !mClosing;
782     }
783 
784     private final Runnable mFlingCollapseRunnable = new Runnable() {
785         @Override
786         public void run() {
787             fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
788                     false /* expandBecauseOfFalsing */);
789         }
790     };
791 
cancelPeek()792     public void cancelPeek() {
793         boolean cancelled = false;
794         if (mPeekAnimator != null) {
795             cancelled = true;
796             mPeekAnimator.cancel();
797         }
798 
799         if (cancelled) {
800             // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
801             // notify mBar that we might have closed ourselves.
802             notifyBarPanelExpansionChanged();
803         }
804     }
805 
expand(final boolean animate)806     public void expand(final boolean animate) {
807         if (!isFullyCollapsed() && !isCollapsing()) {
808             return;
809         }
810 
811         mInstantExpanding = true;
812         mAnimateAfterExpanding = animate;
813         mUpdateFlingOnLayout = false;
814         abortAnimations();
815         cancelPeek();
816         if (mTracking) {
817             onTrackingStopped(true /* expands */); // The panel is expanded after this call.
818         }
819         if (mExpanding) {
820             notifyExpandingFinished();
821         }
822         notifyBarPanelExpansionChanged();
823 
824         // Wait for window manager to pickup the change, so we know the maximum height of the panel
825         // then.
826         mView.getViewTreeObserver().addOnGlobalLayoutListener(
827                 new ViewTreeObserver.OnGlobalLayoutListener() {
828                     @Override
829                     public void onGlobalLayout() {
830                         if (!mInstantExpanding) {
831                             mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
832                             return;
833                         }
834                         if (mStatusBar.getNotificationShadeWindowView().isVisibleToUser()) {
835                             mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
836                             if (mAnimateAfterExpanding) {
837                                 notifyExpandingStarted();
838                                 fling(0, true /* expand */);
839                             } else {
840                                 setExpandedFraction(1f);
841                             }
842                             mInstantExpanding = false;
843                         }
844                     }
845                 });
846 
847         // Make sure a layout really happens.
848         mView.requestLayout();
849     }
850 
instantCollapse()851     public void instantCollapse() {
852         abortAnimations();
853         setExpandedFraction(0f);
854         if (mExpanding) {
855             notifyExpandingFinished();
856         }
857         if (mInstantExpanding) {
858             mInstantExpanding = false;
859             notifyBarPanelExpansionChanged();
860         }
861     }
862 
abortAnimations()863     private void abortAnimations() {
864         cancelPeek();
865         cancelHeightAnimator();
866         mView.removeCallbacks(mPostCollapseRunnable);
867         mView.removeCallbacks(mFlingCollapseRunnable);
868     }
869 
onClosingFinished()870     protected void onClosingFinished() {
871         mBar.onClosingFinished();
872     }
873 
874 
startUnlockHintAnimation()875     protected void startUnlockHintAnimation() {
876 
877         // We don't need to hint the user if an animation is already running or the user is changing
878         // the expansion.
879         if (mHeightAnimator != null || mTracking) {
880             return;
881         }
882         cancelPeek();
883         notifyExpandingStarted();
884         startUnlockHintAnimationPhase1(() -> {
885             notifyExpandingFinished();
886             onUnlockHintFinished();
887             mHintAnimationRunning = false;
888         });
889         onUnlockHintStarted();
890         mHintAnimationRunning = true;
891     }
892 
onUnlockHintFinished()893     protected void onUnlockHintFinished() {
894         mStatusBar.onHintFinished();
895     }
896 
onUnlockHintStarted()897     protected void onUnlockHintStarted() {
898         mStatusBar.onUnlockHintStarted();
899     }
900 
isUnlockHintRunning()901     public boolean isUnlockHintRunning() {
902         return mHintAnimationRunning;
903     }
904 
905     /**
906      * Phase 1: Move everything upwards.
907      */
startUnlockHintAnimationPhase1(final Runnable onAnimationFinished)908     private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
909         float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
910         ValueAnimator animator = createHeightAnimator(target);
911         animator.setDuration(250);
912         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
913         animator.addListener(new AnimatorListenerAdapter() {
914             private boolean mCancelled;
915 
916             @Override
917             public void onAnimationCancel(Animator animation) {
918                 mCancelled = true;
919             }
920 
921             @Override
922             public void onAnimationEnd(Animator animation) {
923                 if (mCancelled) {
924                     setAnimator(null);
925                     onAnimationFinished.run();
926                 } else {
927                     startUnlockHintAnimationPhase2(onAnimationFinished);
928                 }
929             }
930         });
931         animator.start();
932         setAnimator(animator);
933 
934         View[] viewsToAnimate = {
935                 mKeyguardBottomArea.getIndicationArea(),
936                 mStatusBar.getAmbientIndicationContainer()};
937         for (View v : viewsToAnimate) {
938             if (v == null) {
939                 continue;
940             }
941             v.animate().translationY(-mHintDistance).setDuration(250).setInterpolator(
942                     Interpolators.FAST_OUT_SLOW_IN).withEndAction(() -> v.animate().translationY(
943                     0).setDuration(450).setInterpolator(mBounceInterpolator).start()).start();
944         }
945     }
946 
setAnimator(ValueAnimator animator)947     private void setAnimator(ValueAnimator animator) {
948         mHeightAnimator = animator;
949         if (animator == null && mPanelUpdateWhenAnimatorEnds) {
950             mPanelUpdateWhenAnimatorEnds = false;
951             requestPanelHeightUpdate();
952         }
953     }
954 
955     /**
956      * Phase 2: Bounce down.
957      */
startUnlockHintAnimationPhase2(final Runnable onAnimationFinished)958     private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
959         ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
960         animator.setDuration(450);
961         animator.setInterpolator(mBounceInterpolator);
962         animator.addListener(new AnimatorListenerAdapter() {
963             @Override
964             public void onAnimationEnd(Animator animation) {
965                 setAnimator(null);
966                 onAnimationFinished.run();
967                 notifyBarPanelExpansionChanged();
968             }
969         });
970         animator.start();
971         setAnimator(animator);
972     }
973 
createHeightAnimator(float targetHeight)974     private ValueAnimator createHeightAnimator(float targetHeight) {
975         ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
976         animator.addUpdateListener(
977                 animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
978         return animator;
979     }
980 
notifyBarPanelExpansionChanged()981     protected void notifyBarPanelExpansionChanged() {
982         if (mBar != null) {
983             mBar.panelExpansionChanged(
984                     mExpandedFraction,
985                     mExpandedFraction > 0f || mPeekAnimator != null || mInstantExpanding
986                             || isPanelVisibleBecauseOfHeadsUp() || mTracking
987                             || mHeightAnimator != null);
988         }
989         for (int i = 0; i < mExpansionListeners.size(); i++) {
990             mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
991         }
992     }
993 
addExpansionListener(PanelExpansionListener panelExpansionListener)994     public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
995         mExpansionListeners.add(panelExpansionListener);
996     }
997 
isPanelVisibleBecauseOfHeadsUp()998     protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
999 
1000     /**
1001      * Gets called when the user performs a click anywhere in the empty area of the panel.
1002      *
1003      * @return whether the panel will be expanded after the action performed by this method
1004      */
onEmptySpaceClick(float x)1005     protected boolean onEmptySpaceClick(float x) {
1006         if (mHintAnimationRunning) {
1007             return true;
1008         }
1009         return onMiddleClicked();
1010     }
1011 
1012     protected final Runnable mPostCollapseRunnable = new Runnable() {
1013         @Override
1014         public void run() {
1015             collapse(false /* delayed */, 1.0f /* speedUpFactor */);
1016         }
1017     };
1018 
onMiddleClicked()1019     protected abstract boolean onMiddleClicked();
1020 
isDozing()1021     protected abstract boolean isDozing();
1022 
dump(FileDescriptor fd, PrintWriter pw, String[] args)1023     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1024         pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
1025                         + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s "
1026                         + "touchDisabled=%s" + "]",
1027                 this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
1028                 mClosing ? "T" : "f", mTracking ? "T" : "f", mJustPeeked ? "T" : "f", mPeekAnimator,
1029                 ((mPeekAnimator != null && mPeekAnimator.isStarted()) ? " (started)" : ""),
1030                 mHeightAnimator,
1031                 ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
1032                 mTouchDisabled ? "T" : "f"));
1033     }
1034 
resetViews(boolean animate)1035     public abstract void resetViews(boolean animate);
1036 
getPeekHeight()1037     protected abstract float getPeekHeight();
1038 
1039     /**
1040      * @return whether "Clear all" button will be visible when the panel is fully expanded
1041      */
fullyExpandedClearAllVisible()1042     protected abstract boolean fullyExpandedClearAllVisible();
1043 
isClearAllVisible()1044     protected abstract boolean isClearAllVisible();
1045 
1046     /**
1047      * @return the height of the clear all button, in pixels including padding
1048      */
getClearAllHeightWithPadding()1049     protected abstract int getClearAllHeightWithPadding();
1050 
setHeadsUpManager(HeadsUpManagerPhone headsUpManager)1051     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
1052         mHeadsUpManager = headsUpManager;
1053     }
1054 
setLaunchingNotification(boolean launchingNotification)1055     public void setLaunchingNotification(boolean launchingNotification) {
1056         mLaunchingNotification = launchingNotification;
1057     }
1058 
collapseWithDuration(int animationDuration)1059     public void collapseWithDuration(int animationDuration) {
1060         mFixedDuration = animationDuration;
1061         collapse(false /* delayed */, 1.0f /* speedUpFactor */);
1062         mFixedDuration = NO_FIXED_DURATION;
1063     }
1064 
getView()1065     public ViewGroup getView() {
1066         // TODO: remove this method, or at least reduce references to it.
1067         return mView;
1068     }
1069 
isEnabled()1070     public boolean isEnabled() {
1071         return mView.isEnabled();
1072     }
1073 
createLayoutChangeListener()1074     public OnLayoutChangeListener createLayoutChangeListener() {
1075         return new OnLayoutChangeListener();
1076     }
1077 
createTouchHandler()1078     protected TouchHandler createTouchHandler() {
1079         return new TouchHandler();
1080     }
1081 
createOnConfigurationChangedListener()1082     protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
1083         return new OnConfigurationChangedListener();
1084     }
1085 
1086     public class TouchHandler implements View.OnTouchListener {
onInterceptTouchEvent(MotionEvent event)1087         public boolean onInterceptTouchEvent(MotionEvent event) {
1088             if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
1089                     && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
1090                 return false;
1091             }
1092 
1093             /*
1094              * If the user drags anywhere inside the panel we intercept it if the movement is
1095              * upwards. This allows closing the shade from anywhere inside the panel.
1096              *
1097              * We only do this if the current content is scrolled to the bottom,
1098              * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling
1099              * gesture
1100              * possible.
1101              */
1102             int pointerIndex = event.findPointerIndex(mTrackingPointer);
1103             if (pointerIndex < 0) {
1104                 pointerIndex = 0;
1105                 mTrackingPointer = event.getPointerId(pointerIndex);
1106             }
1107             final float x = event.getX(pointerIndex);
1108             final float y = event.getY(pointerIndex);
1109             boolean canCollapsePanel = canCollapsePanelOnTouch();
1110 
1111             switch (event.getActionMasked()) {
1112                 case MotionEvent.ACTION_DOWN:
1113                     mStatusBar.userActivity();
1114                     mAnimatingOnDown = mHeightAnimator != null;
1115                     mMinExpandHeight = 0.0f;
1116                     mDownTime = SystemClock.uptimeMillis();
1117                     if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
1118                             || mPeekAnimator != null) {
1119                         cancelHeightAnimator();
1120                         cancelPeek();
1121                         mTouchSlopExceeded = true;
1122                         return true;
1123                     }
1124                     mInitialTouchY = y;
1125                     mInitialTouchX = x;
1126                     mTouchStartedInEmptyArea = !isInContentBounds(x, y);
1127                     mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
1128                     mJustPeeked = false;
1129                     mMotionAborted = false;
1130                     mPanelClosedOnDown = isFullyCollapsed();
1131                     mCollapsedAndHeadsUpOnDown = false;
1132                     mHasLayoutedSinceDown = false;
1133                     mUpdateFlingOnLayout = false;
1134                     mTouchAboveFalsingThreshold = false;
1135                     addMovement(event);
1136                     break;
1137                 case MotionEvent.ACTION_POINTER_UP:
1138                     final int upPointer = event.getPointerId(event.getActionIndex());
1139                     if (mTrackingPointer == upPointer) {
1140                         // gesture is ongoing, find a new pointer to track
1141                         final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
1142                         mTrackingPointer = event.getPointerId(newIndex);
1143                         mInitialTouchX = event.getX(newIndex);
1144                         mInitialTouchY = event.getY(newIndex);
1145                     }
1146                     break;
1147                 case MotionEvent.ACTION_POINTER_DOWN:
1148                     if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
1149                         mMotionAborted = true;
1150                         mVelocityTracker.clear();
1151                     }
1152                     break;
1153                 case MotionEvent.ACTION_MOVE:
1154                     final float h = y - mInitialTouchY;
1155                     addMovement(event);
1156                     if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) {
1157                         float hAbs = Math.abs(h);
1158                         float touchSlop = getTouchSlop(event);
1159                         if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop))
1160                                 && hAbs > Math.abs(x - mInitialTouchX)) {
1161                             cancelHeightAnimator();
1162                             startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
1163                             return true;
1164                         }
1165                     }
1166                     break;
1167                 case MotionEvent.ACTION_CANCEL:
1168                 case MotionEvent.ACTION_UP:
1169                     mVelocityTracker.clear();
1170                     break;
1171             }
1172             return false;
1173         }
1174 
1175         @Override
onTouch(View v, MotionEvent event)1176         public boolean onTouch(View v, MotionEvent event) {
1177             if (mInstantExpanding || (mTouchDisabled
1178                     && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
1179                     && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
1180                 return false;
1181             }
1182 
1183             // If dragging should not expand the notifications shade, then return false.
1184             if (!mNotificationsDragEnabled) {
1185                 if (mTracking) {
1186                     // Turn off tracking if it's on or the shade can get stuck in the down position.
1187                     onTrackingStopped(true /* expand */);
1188                 }
1189                 return false;
1190             }
1191 
1192             // On expanding, single mouse click expands the panel instead of dragging.
1193             if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
1194                 if (event.getAction() == MotionEvent.ACTION_UP) {
1195                     expand(true);
1196                 }
1197                 return true;
1198             }
1199 
1200             /*
1201              * We capture touch events here and update the expand height here in case according to
1202              * the users fingers. This also handles multi-touch.
1203              *
1204              * If the user just clicks shortly, we show a quick peek of the shade.
1205              *
1206              * Flinging is also enabled in order to open or close the shade.
1207              */
1208 
1209             int pointerIndex = event.findPointerIndex(mTrackingPointer);
1210             if (pointerIndex < 0) {
1211                 pointerIndex = 0;
1212                 mTrackingPointer = event.getPointerId(pointerIndex);
1213             }
1214             final float x = event.getX(pointerIndex);
1215             final float y = event.getY(pointerIndex);
1216 
1217             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1218                 mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
1219                 mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
1220             }
1221 
1222             switch (event.getActionMasked()) {
1223                 case MotionEvent.ACTION_DOWN:
1224                     startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
1225                     mJustPeeked = false;
1226                     mMinExpandHeight = 0.0f;
1227                     mPanelClosedOnDown = isFullyCollapsed();
1228                     mHasLayoutedSinceDown = false;
1229                     mUpdateFlingOnLayout = false;
1230                     mMotionAborted = false;
1231                     mPeekTouching = mPanelClosedOnDown;
1232                     mDownTime = SystemClock.uptimeMillis();
1233                     mTouchAboveFalsingThreshold = false;
1234                     mCollapsedAndHeadsUpOnDown =
1235                             isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
1236                     addMovement(event);
1237                     if (!mGestureWaitForTouchSlop || (mHeightAnimator != null
1238                             && !mHintAnimationRunning) || mPeekAnimator != null) {
1239                         mTouchSlopExceeded =
1240                                 (mHeightAnimator != null && !mHintAnimationRunning)
1241                                         || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
1242                         cancelHeightAnimator();
1243                         cancelPeek();
1244                         onTrackingStarted();
1245                     }
1246                     if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
1247                             && !mStatusBar.isBouncerShowing()) {
1248                         startOpening(event);
1249                     }
1250                     break;
1251 
1252                 case MotionEvent.ACTION_POINTER_UP:
1253                     final int upPointer = event.getPointerId(event.getActionIndex());
1254                     if (mTrackingPointer == upPointer) {
1255                         // gesture is ongoing, find a new pointer to track
1256                         final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
1257                         final float newY = event.getY(newIndex);
1258                         final float newX = event.getX(newIndex);
1259                         mTrackingPointer = event.getPointerId(newIndex);
1260                         startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
1261                     }
1262                     break;
1263                 case MotionEvent.ACTION_POINTER_DOWN:
1264                     if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
1265                         mMotionAborted = true;
1266                         endMotionEvent(event, x, y, true /* forceCancel */);
1267                         return false;
1268                     }
1269                     break;
1270                 case MotionEvent.ACTION_MOVE:
1271                     addMovement(event);
1272                     float h = y - mInitialTouchY;
1273 
1274                     // If the panel was collapsed when touching, we only need to check for the
1275                     // y-component of the gesture, as we have no conflicting horizontal gesture.
1276                     if (Math.abs(h) > getTouchSlop(event)
1277                             && (Math.abs(h) > Math.abs(x - mInitialTouchX)
1278                             || mIgnoreXTouchSlop)) {
1279                         mTouchSlopExceeded = true;
1280                         if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
1281                             if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
1282                                 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
1283                                 h = 0;
1284                             }
1285                             cancelHeightAnimator();
1286                             onTrackingStarted();
1287                         }
1288                     }
1289                     float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
1290                     if (newHeight > mPeekHeight) {
1291                         if (mPeekAnimator != null) {
1292                             mPeekAnimator.cancel();
1293                         }
1294                         mJustPeeked = false;
1295                     } else if (mPeekAnimator == null && mJustPeeked) {
1296                         // The initial peek has finished, but we haven't dragged as far yet, lets
1297                         // speed it up by starting at the peek height.
1298                         mInitialOffsetOnTouch = mExpandedHeight;
1299                         mInitialTouchY = y;
1300                         mMinExpandHeight = mExpandedHeight;
1301                         mJustPeeked = false;
1302                     }
1303                     newHeight = Math.max(newHeight, mMinExpandHeight);
1304                     if (-h >= getFalsingThreshold()) {
1305                         mTouchAboveFalsingThreshold = true;
1306                         mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
1307                     }
1308                     if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking)
1309                             && !isTrackingBlocked()) {
1310                         setExpandedHeightInternal(newHeight);
1311                     }
1312                     break;
1313 
1314                 case MotionEvent.ACTION_UP:
1315                 case MotionEvent.ACTION_CANCEL:
1316                     addMovement(event);
1317                     endMotionEvent(event, x, y, false /* forceCancel */);
1318                     break;
1319             }
1320             return !mGestureWaitForTouchSlop || mTracking;
1321         }
1322     }
1323 
1324     public class OnLayoutChangeListener implements View.OnLayoutChangeListener {
1325         @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)1326         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
1327                 int oldTop, int oldRight, int oldBottom) {
1328             mStatusBar.onPanelLaidOut();
1329             requestPanelHeightUpdate();
1330             mHasLayoutedSinceDown = true;
1331             if (mUpdateFlingOnLayout) {
1332                 abortAnimations();
1333                 fling(mUpdateFlingVelocity, true /* expands */);
1334                 mUpdateFlingOnLayout = false;
1335             }
1336         }
1337     }
1338 
1339     public class OnConfigurationChangedListener implements
1340             PanelView.OnConfigurationChangedListener {
1341         @Override
onConfigurationChanged(Configuration newConfig)1342         public void onConfigurationChanged(Configuration newConfig) {
1343             loadDimens();
1344         }
1345     }
1346 }
1347