• 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 
18 package com.android.systemui;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.util.Log;
25 import android.view.Gravity;
26 import android.view.HapticFeedbackConstants;
27 import android.view.MotionEvent;
28 import android.view.ScaleGestureDetector;
29 import android.view.ScaleGestureDetector.OnScaleGestureListener;
30 import android.view.VelocityTracker;
31 import android.view.View;
32 import android.view.ViewConfiguration;
33 
34 import com.android.systemui.statusbar.ExpandableNotificationRow;
35 import com.android.systemui.statusbar.ExpandableView;
36 import com.android.systemui.statusbar.FlingAnimationUtils;
37 import com.android.systemui.statusbar.policy.ScrollAdapter;
38 
39 public class ExpandHelper implements Gefingerpoken {
40     public interface Callback {
getChildAtRawPosition(float x, float y)41         ExpandableView getChildAtRawPosition(float x, float y);
getChildAtPosition(float x, float y)42         ExpandableView getChildAtPosition(float x, float y);
canChildBeExpanded(View v)43         boolean canChildBeExpanded(View v);
setUserExpandedChild(View v, boolean userExpanded)44         void setUserExpandedChild(View v, boolean userExpanded);
setUserLockedChild(View v, boolean userLocked)45         void setUserLockedChild(View v, boolean userLocked);
expansionStateChanged(boolean isExpanding)46         void expansionStateChanged(boolean isExpanding);
getMaxExpandHeight(ExpandableView view)47         int getMaxExpandHeight(ExpandableView view);
setExpansionCancelled(View view)48         void setExpansionCancelled(View view);
49     }
50 
51     private static final String TAG = "ExpandHelper";
52     protected static final boolean DEBUG = false;
53     protected static final boolean DEBUG_SCALE = false;
54     private static final float EXPAND_DURATION = 0.3f;
55 
56     // Set to false to disable focus-based gestures (spread-finger vertical pull).
57     private static final boolean USE_DRAG = true;
58     // Set to false to disable scale-based gestures (both horizontal and vertical).
59     private static final boolean USE_SPAN = true;
60     // Both gestures types may be active at the same time.
61     // At least one gesture type should be active.
62     // A variant of the screwdriver gesture will emerge from either gesture type.
63 
64     // amount of overstretch for maximum brightness expressed in U
65     // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
66     private static final float STRETCH_INTERVAL = 2f;
67 
68     @SuppressWarnings("unused")
69     private Context mContext;
70 
71     private boolean mExpanding;
72     private static final int NONE    = 0;
73     private static final int BLINDS  = 1<<0;
74     private static final int PULL    = 1<<1;
75     private static final int STRETCH = 1<<2;
76     private int mExpansionStyle = NONE;
77     private boolean mWatchingForPull;
78     private boolean mHasPopped;
79     private View mEventSource;
80     private float mOldHeight;
81     private float mNaturalHeight;
82     private float mInitialTouchFocusY;
83     private float mInitialTouchX;
84     private float mInitialTouchY;
85     private float mInitialTouchSpan;
86     private float mLastFocusY;
87     private float mLastSpanY;
88     private int mTouchSlop;
89     private float mLastMotionY;
90     private float mPullGestureMinXSpan;
91     private Callback mCallback;
92     private ScaleGestureDetector mSGD;
93     private ViewScaler mScaler;
94     private ObjectAnimator mScaleAnimation;
95     private boolean mEnabled = true;
96     private ExpandableView mResizedView;
97     private float mCurrentHeight;
98 
99     private int mSmallSize;
100     private int mLargeSize;
101     private float mMaximumStretch;
102     private boolean mOnlyMovements;
103 
104     private int mGravity;
105 
106     private ScrollAdapter mScrollAdapter;
107     private FlingAnimationUtils mFlingAnimationUtils;
108     private VelocityTracker mVelocityTracker;
109 
110     private OnScaleGestureListener mScaleGestureListener
111             = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
112         @Override
113         public boolean onScaleBegin(ScaleGestureDetector detector) {
114             if (DEBUG_SCALE) Log.v(TAG, "onscalebegin()");
115 
116             if (!mOnlyMovements) {
117                 startExpanding(mResizedView, STRETCH);
118             }
119             return mExpanding;
120         }
121 
122         @Override
123         public boolean onScale(ScaleGestureDetector detector) {
124             if (DEBUG_SCALE) Log.v(TAG, "onscale() on " + mResizedView);
125             return true;
126         }
127 
128         @Override
129         public void onScaleEnd(ScaleGestureDetector detector) {
130         }
131     };
132 
133     private class ViewScaler {
134         ExpandableView mView;
135 
ViewScaler()136         public ViewScaler() {}
setView(ExpandableView v)137         public void setView(ExpandableView v) {
138             mView = v;
139         }
setHeight(float h)140         public void setHeight(float h) {
141             if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
142             mView.setActualHeight((int) h);
143             mCurrentHeight = h;
144         }
getHeight()145         public float getHeight() {
146             return mView.getActualHeight();
147         }
getNaturalHeight()148         public int getNaturalHeight() {
149             return mCallback.getMaxExpandHeight(mView);
150         }
151     }
152 
153     /**
154      * Handle expansion gestures to expand and contract children of the callback.
155      *
156      * @param context application context
157      * @param callback the container that holds the items to be manipulated
158      * @param small the smallest allowable size for the manuipulated items.
159      * @param large the largest allowable size for the manuipulated items.
160      */
ExpandHelper(Context context, Callback callback, int small, int large)161     public ExpandHelper(Context context, Callback callback, int small, int large) {
162         mSmallSize = small;
163         mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
164         mLargeSize = large;
165         mContext = context;
166         mCallback = callback;
167         mScaler = new ViewScaler();
168         mGravity = Gravity.TOP;
169         mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
170         mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
171 
172         final ViewConfiguration configuration = ViewConfiguration.get(mContext);
173         mTouchSlop = configuration.getScaledTouchSlop();
174 
175         mSGD = new ScaleGestureDetector(context, mScaleGestureListener);
176         mFlingAnimationUtils = new FlingAnimationUtils(context, EXPAND_DURATION);
177     }
178 
updateExpansion()179     private void updateExpansion() {
180         if (DEBUG_SCALE) Log.v(TAG, "updateExpansion()");
181         // are we scaling or dragging?
182         float span = mSGD.getCurrentSpan() - mInitialTouchSpan;
183         span *= USE_SPAN ? 1f : 0f;
184         float drag = mSGD.getFocusY() - mInitialTouchFocusY;
185         drag *= USE_DRAG ? 1f : 0f;
186         drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
187         float pull = Math.abs(drag) + Math.abs(span) + 1f;
188         float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
189         float target = hand + mOldHeight;
190         float newHeight = clamp(target);
191         mScaler.setHeight(newHeight);
192         mLastFocusY = mSGD.getFocusY();
193         mLastSpanY = mSGD.getCurrentSpan();
194     }
195 
clamp(float target)196     private float clamp(float target) {
197         float out = target;
198         out = out < mSmallSize ? mSmallSize : out;
199         out = out > mNaturalHeight ? mNaturalHeight : out;
200         return out;
201     }
202 
findView(float x, float y)203     private ExpandableView findView(float x, float y) {
204         ExpandableView v;
205         if (mEventSource != null) {
206             int[] location = new int[2];
207             mEventSource.getLocationOnScreen(location);
208             x += location[0];
209             y += location[1];
210             v = mCallback.getChildAtRawPosition(x, y);
211         } else {
212             v = mCallback.getChildAtPosition(x, y);
213         }
214         return v;
215     }
216 
isInside(View v, float x, float y)217     private boolean isInside(View v, float x, float y) {
218         if (DEBUG) Log.d(TAG, "isinside (" + x + ", " + y + ")");
219 
220         if (v == null) {
221             if (DEBUG) Log.d(TAG, "isinside null subject");
222             return false;
223         }
224         if (mEventSource != null) {
225             int[] location = new int[2];
226             mEventSource.getLocationOnScreen(location);
227             x += location[0];
228             y += location[1];
229             if (DEBUG) Log.d(TAG, "  to global (" + x + ", " + y + ")");
230         }
231         int[] location = new int[2];
232         v.getLocationOnScreen(location);
233         x -= location[0];
234         y -= location[1];
235         if (DEBUG) Log.d(TAG, "  to local (" + x + ", " + y + ")");
236         if (DEBUG) Log.d(TAG, "  inside (" + v.getWidth() + ", " + v.getHeight() + ")");
237         boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
238         return inside;
239     }
240 
setEventSource(View eventSource)241     public void setEventSource(View eventSource) {
242         mEventSource = eventSource;
243     }
244 
setGravity(int gravity)245     public void setGravity(int gravity) {
246         mGravity = gravity;
247     }
248 
setScrollAdapter(ScrollAdapter adapter)249     public void setScrollAdapter(ScrollAdapter adapter) {
250         mScrollAdapter = adapter;
251     }
252 
253     @Override
onInterceptTouchEvent(MotionEvent ev)254     public boolean onInterceptTouchEvent(MotionEvent ev) {
255         if (!isEnabled()) {
256             return false;
257         }
258         trackVelocity(ev);
259         final int action = ev.getAction();
260         if (DEBUG_SCALE) Log.d(TAG, "intercept: act=" + MotionEvent.actionToString(action) +
261                          " expanding=" + mExpanding +
262                          (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
263                          (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
264                          (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
265         // check for a spread-finger vertical pull gesture
266         mSGD.onTouchEvent(ev);
267         final int x = (int) mSGD.getFocusX();
268         final int y = (int) mSGD.getFocusY();
269 
270         mInitialTouchFocusY = y;
271         mInitialTouchSpan = mSGD.getCurrentSpan();
272         mLastFocusY = mInitialTouchFocusY;
273         mLastSpanY = mInitialTouchSpan;
274         if (DEBUG_SCALE) Log.d(TAG, "set initial span: " + mInitialTouchSpan);
275 
276         if (mExpanding) {
277             mLastMotionY = ev.getRawY();
278             maybeRecycleVelocityTracker(ev);
279             return true;
280         } else {
281             if ((action == MotionEvent.ACTION_MOVE) && 0 != (mExpansionStyle & BLINDS)) {
282                 // we've begun Venetian blinds style expansion
283                 return true;
284             }
285             switch (action & MotionEvent.ACTION_MASK) {
286             case MotionEvent.ACTION_MOVE: {
287                 final float xspan = mSGD.getCurrentSpanX();
288                 if (xspan > mPullGestureMinXSpan &&
289                         xspan > mSGD.getCurrentSpanY() && !mExpanding) {
290                     // detect a vertical pulling gesture with fingers somewhat separated
291                     if (DEBUG_SCALE) Log.v(TAG, "got pull gesture (xspan=" + xspan + "px)");
292                     startExpanding(mResizedView, PULL);
293                     mWatchingForPull = false;
294                 }
295                 if (mWatchingForPull) {
296                     final float yDiff = ev.getRawY() - mInitialTouchY;
297                     final float xDiff = ev.getRawX() - mInitialTouchX;
298                     if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) {
299                         if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
300                         mWatchingForPull = false;
301                         if (mResizedView != null && !isFullyExpanded(mResizedView)) {
302                             if (startExpanding(mResizedView, BLINDS)) {
303                                 mLastMotionY = ev.getRawY();
304                                 mInitialTouchY = ev.getRawY();
305                                 mHasPopped = false;
306                             }
307                         }
308                     }
309                 }
310                 break;
311             }
312 
313             case MotionEvent.ACTION_DOWN:
314                 mWatchingForPull = mScrollAdapter != null &&
315                         isInside(mScrollAdapter.getHostView(), x, y)
316                         && mScrollAdapter.isScrolledToTop();
317                 mResizedView = findView(x, y);
318                 if (mResizedView != null && !mCallback.canChildBeExpanded(mResizedView)) {
319                     mResizedView = null;
320                     mWatchingForPull = false;
321                 }
322                 mInitialTouchY = ev.getRawY();
323                 mInitialTouchX = ev.getRawX();
324                 break;
325 
326             case MotionEvent.ACTION_CANCEL:
327             case MotionEvent.ACTION_UP:
328                 if (DEBUG) Log.d(TAG, "up/cancel");
329                 finishExpanding(ev.getActionMasked() == MotionEvent.ACTION_CANCEL /* forceAbort */,
330                         getCurrentVelocity());
331                 clearView();
332                 break;
333             }
334             mLastMotionY = ev.getRawY();
335             maybeRecycleVelocityTracker(ev);
336             return mExpanding;
337         }
338     }
339 
trackVelocity(MotionEvent event)340     private void trackVelocity(MotionEvent event) {
341         int action = event.getActionMasked();
342         switch(action) {
343             case MotionEvent.ACTION_DOWN:
344                 if (mVelocityTracker == null) {
345                     mVelocityTracker = VelocityTracker.obtain();
346                 } else {
347                     mVelocityTracker.clear();
348                 }
349                 mVelocityTracker.addMovement(event);
350                 break;
351             case MotionEvent.ACTION_MOVE:
352                 if (mVelocityTracker == null) {
353                     mVelocityTracker = VelocityTracker.obtain();
354                 }
355                 mVelocityTracker.addMovement(event);
356                 break;
357             default:
358                 break;
359         }
360     }
361 
maybeRecycleVelocityTracker(MotionEvent event)362     private void maybeRecycleVelocityTracker(MotionEvent event) {
363         if (mVelocityTracker != null && (event.getActionMasked() == MotionEvent.ACTION_CANCEL
364                 || event.getActionMasked() == MotionEvent.ACTION_UP)) {
365             mVelocityTracker.recycle();
366             mVelocityTracker = null;
367         }
368     }
369 
getCurrentVelocity()370     private float getCurrentVelocity() {
371         if (mVelocityTracker != null) {
372             mVelocityTracker.computeCurrentVelocity(1000);
373             return mVelocityTracker.getYVelocity();
374         } else {
375             return 0f;
376         }
377     }
378 
setEnabled(boolean enable)379     public void setEnabled(boolean enable) {
380         mEnabled = enable;
381     }
382 
isEnabled()383     private boolean isEnabled() {
384         return mEnabled;
385     }
386 
isFullyExpanded(ExpandableView underFocus)387     private boolean isFullyExpanded(ExpandableView underFocus) {
388         return underFocus.getIntrinsicHeight() == underFocus.getMaxContentHeight()
389                 && (!underFocus.isSummaryWithChildren() || underFocus.areChildrenExpanded());
390     }
391 
392     @Override
onTouchEvent(MotionEvent ev)393     public boolean onTouchEvent(MotionEvent ev) {
394         if (!isEnabled() && !mExpanding) {
395             // In case we're expanding we still want to finish the current motion.
396             return false;
397         }
398         trackVelocity(ev);
399         final int action = ev.getActionMasked();
400         if (DEBUG_SCALE) Log.d(TAG, "touch: act=" + MotionEvent.actionToString(action) +
401                 " expanding=" + mExpanding +
402                 (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
403                 (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
404                 (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
405 
406         mSGD.onTouchEvent(ev);
407         final int x = (int) mSGD.getFocusX();
408         final int y = (int) mSGD.getFocusY();
409 
410         if (mOnlyMovements) {
411             mLastMotionY = ev.getRawY();
412             return false;
413         }
414         switch (action) {
415             case MotionEvent.ACTION_DOWN:
416                 mWatchingForPull = mScrollAdapter != null &&
417                         isInside(mScrollAdapter.getHostView(), x, y);
418                 mResizedView = findView(x, y);
419                 mInitialTouchX = ev.getRawX();
420                 mInitialTouchY = ev.getRawY();
421                 break;
422             case MotionEvent.ACTION_MOVE: {
423                 if (mWatchingForPull) {
424                     final float yDiff = ev.getRawY() - mInitialTouchY;
425                     final float xDiff = ev.getRawX() - mInitialTouchX;
426                     if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) {
427                         if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
428                         mWatchingForPull = false;
429                         if (mResizedView != null && !isFullyExpanded(mResizedView)) {
430                             if (startExpanding(mResizedView, BLINDS)) {
431                                 mInitialTouchY = ev.getRawY();
432                                 mLastMotionY = ev.getRawY();
433                                 mHasPopped = false;
434                             }
435                         }
436                     }
437                 }
438                 if (mExpanding && 0 != (mExpansionStyle & BLINDS)) {
439                     final float rawHeight = ev.getRawY() - mLastMotionY + mCurrentHeight;
440                     final float newHeight = clamp(rawHeight);
441                     boolean isFinished = false;
442                     boolean expanded = false;
443                     if (rawHeight > mNaturalHeight) {
444                         isFinished = true;
445                         expanded = true;
446                     }
447                     if (rawHeight < mSmallSize) {
448                         isFinished = true;
449                         expanded = false;
450                     }
451 
452                     if (!mHasPopped) {
453                         if (mEventSource != null) {
454                             mEventSource.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
455                         }
456                         mHasPopped = true;
457                     }
458 
459                     mScaler.setHeight(newHeight);
460                     mLastMotionY = ev.getRawY();
461                     if (isFinished) {
462                         mCallback.expansionStateChanged(false);
463                     } else {
464                         mCallback.expansionStateChanged(true);
465                     }
466                     return true;
467                 }
468 
469                 if (mExpanding) {
470 
471                     // Gestural expansion is running
472                     updateExpansion();
473                     mLastMotionY = ev.getRawY();
474                     return true;
475                 }
476 
477                 break;
478             }
479 
480             case MotionEvent.ACTION_POINTER_UP:
481             case MotionEvent.ACTION_POINTER_DOWN:
482                 if (DEBUG) Log.d(TAG, "pointer change");
483                 mInitialTouchY += mSGD.getFocusY() - mLastFocusY;
484                 mInitialTouchSpan += mSGD.getCurrentSpan() - mLastSpanY;
485                 break;
486 
487             case MotionEvent.ACTION_UP:
488             case MotionEvent.ACTION_CANCEL:
489                 if (DEBUG) Log.d(TAG, "up/cancel");
490                 finishExpanding(!isEnabled() || ev.getActionMasked() == MotionEvent.ACTION_CANCEL,
491                         getCurrentVelocity());
492                 clearView();
493                 break;
494         }
495         mLastMotionY = ev.getRawY();
496         maybeRecycleVelocityTracker(ev);
497         return mResizedView != null;
498     }
499 
500     /**
501      * @return True if the view is expandable, false otherwise.
502      */
startExpanding(ExpandableView v, int expandType)503     private boolean startExpanding(ExpandableView v, int expandType) {
504         if (!(v instanceof ExpandableNotificationRow)) {
505             return false;
506         }
507         mExpansionStyle = expandType;
508         if (mExpanding && v == mResizedView) {
509             return true;
510         }
511         mExpanding = true;
512         mCallback.expansionStateChanged(true);
513         if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v);
514         mCallback.setUserLockedChild(v, true);
515         mScaler.setView(v);
516         mOldHeight = mScaler.getHeight();
517         mCurrentHeight = mOldHeight;
518         boolean canBeExpanded = mCallback.canChildBeExpanded(v);
519         if (canBeExpanded) {
520             if (DEBUG) Log.d(TAG, "working on an expandable child");
521             mNaturalHeight = mScaler.getNaturalHeight();
522             mSmallSize = v.getCollapsedHeight();
523         } else {
524             if (DEBUG) Log.d(TAG, "working on a non-expandable child");
525             mNaturalHeight = mOldHeight;
526         }
527         if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
528                     " mNaturalHeight: " + mNaturalHeight);
529         return true;
530     }
531 
532     /**
533      * Finish the current expand motion
534      * @param forceAbort whether the expansion should be forcefully aborted and returned to the old
535      *                   state
536      * @param velocity the velocity this was expanded/ collapsed with
537      */
finishExpanding(boolean forceAbort, float velocity)538     private void finishExpanding(boolean forceAbort, float velocity) {
539         if (!mExpanding) return;
540 
541         if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mResizedView);
542 
543         float currentHeight = mScaler.getHeight();
544         final boolean wasClosed = (mOldHeight == mSmallSize);
545         boolean nowExpanded;
546         if (!forceAbort) {
547             if (wasClosed) {
548                 nowExpanded = currentHeight > mOldHeight && velocity >= 0;
549             } else {
550                 nowExpanded = currentHeight >= mOldHeight || velocity > 0;
551             }
552             nowExpanded |= mNaturalHeight == mSmallSize;
553         } else {
554             nowExpanded = !wasClosed;
555         }
556         if (mScaleAnimation.isRunning()) {
557             mScaleAnimation.cancel();
558         }
559         mCallback.expansionStateChanged(false);
560         int naturalHeight = mScaler.getNaturalHeight();
561         float targetHeight = nowExpanded ? naturalHeight : mSmallSize;
562         if (targetHeight != currentHeight && mEnabled) {
563             mScaleAnimation.setFloatValues(targetHeight);
564             mScaleAnimation.setupStartValues();
565             final View scaledView = mResizedView;
566             final boolean expand = nowExpanded;
567             mScaleAnimation.addListener(new AnimatorListenerAdapter() {
568                 public boolean mCancelled;
569 
570                 @Override
571                 public void onAnimationEnd(Animator animation) {
572                     if (!mCancelled) {
573                         mCallback.setUserExpandedChild(scaledView, expand);
574                     } else {
575                         mCallback.setExpansionCancelled(scaledView);
576                     }
577                     mCallback.setUserLockedChild(scaledView, false);
578                     mScaleAnimation.removeListener(this);
579                 }
580 
581                 @Override
582                 public void onAnimationCancel(Animator animation) {
583                     mCancelled = true;
584                 }
585             });
586             velocity = nowExpanded == velocity >= 0 ? velocity : 0;
587             mFlingAnimationUtils.apply(mScaleAnimation, currentHeight, targetHeight, velocity);
588             mScaleAnimation.start();
589         } else {
590             if (targetHeight != currentHeight) {
591                 mScaler.setHeight(targetHeight);
592             }
593             mCallback.setUserExpandedChild(mResizedView, nowExpanded);
594             mCallback.setUserLockedChild(mResizedView, false);
595         }
596 
597         mExpanding = false;
598         mExpansionStyle = NONE;
599 
600         if (DEBUG) Log.d(TAG, "wasClosed is: " + wasClosed);
601         if (DEBUG) Log.d(TAG, "currentHeight is: " + currentHeight);
602         if (DEBUG) Log.d(TAG, "mSmallSize is: " + mSmallSize);
603         if (DEBUG) Log.d(TAG, "targetHeight is: " + targetHeight);
604         if (DEBUG) Log.d(TAG, "scale was finished on view: " + mResizedView);
605     }
606 
clearView()607     private void clearView() {
608         mResizedView = null;
609     }
610 
611     /**
612      * Use this to abort any pending expansions in progress.
613      */
cancel()614     public void cancel() {
615         finishExpanding(true /* forceAbort */, 0f /* velocity */);
616         clearView();
617 
618         // reset the gesture detector
619         mSGD = new ScaleGestureDetector(mContext, mScaleGestureListener);
620     }
621 
622     /**
623      * Change the expansion mode to only observe movements and don't perform any resizing.
624      * This is needed when the expanding is finished and the scroller kicks in,
625      * performing an overscroll motion. We only want to shrink it again when we are not
626      * overscrolled.
627      *
628      * @param onlyMovements Should only movements be observed?
629      */
onlyObserveMovements(boolean onlyMovements)630     public void onlyObserveMovements(boolean onlyMovements) {
631         mOnlyMovements = onlyMovements;
632     }
633 }
634 
635