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