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