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