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