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