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