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