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