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