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