Home | History | Annotate | Download | only in gesture
      1 /*
      2  * Copyright (C) 2009 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 android.gesture;
     18 
     19 import android.annotation.ColorInt;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Canvas;
     23 import android.graphics.Paint;
     24 import android.graphics.Path;
     25 import android.graphics.Rect;
     26 import android.graphics.RectF;
     27 import android.util.AttributeSet;
     28 import android.view.MotionEvent;
     29 import android.view.animation.AnimationUtils;
     30 import android.view.animation.AccelerateDecelerateInterpolator;
     31 import android.widget.FrameLayout;
     32 import android.os.SystemClock;
     33 import android.annotation.Widget;
     34 import com.android.internal.R;
     35 
     36 import java.util.ArrayList;
     37 
     38 /**
     39  * A transparent overlay for gesture input that can be placed on top of other
     40  * widgets or contain other widgets.
     41  *
     42  * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
     43  * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
     44  * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
     45  * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
     46  * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
     47  * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
     48  * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
     49  * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
     50  * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
     51  * @attr ref android.R.styleable#GestureOverlayView_gestureColor
     52  * @attr ref android.R.styleable#GestureOverlayView_orientation
     53  * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
     54  */
     55 @Widget
     56 public class GestureOverlayView extends FrameLayout {
     57     public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
     58     public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
     59 
     60     public static final int ORIENTATION_HORIZONTAL = 0;
     61     public static final int ORIENTATION_VERTICAL = 1;
     62 
     63     private static final int FADE_ANIMATION_RATE = 16;
     64     private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
     65     private static final boolean DITHER_FLAG = true;
     66 
     67     private final Paint mGesturePaint = new Paint();
     68 
     69     private long mFadeDuration = 150;
     70     private long mFadeOffset = 420;
     71     private long mFadingStart;
     72     private boolean mFadingHasStarted;
     73     private boolean mFadeEnabled = true;
     74 
     75     private int mCurrentColor;
     76     private int mCertainGestureColor = 0xFFFFFF00;
     77     private int mUncertainGestureColor = 0x48FFFF00;
     78     private float mGestureStrokeWidth = 12.0f;
     79     private int mInvalidateExtraBorder = 10;
     80 
     81     private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
     82     private float mGestureStrokeLengthThreshold = 50.0f;
     83     private float mGestureStrokeSquarenessTreshold = 0.275f;
     84     private float mGestureStrokeAngleThreshold = 40.0f;
     85 
     86     private int mOrientation = ORIENTATION_VERTICAL;
     87 
     88     private final Rect mInvalidRect = new Rect();
     89     private final Path mPath = new Path();
     90     private boolean mGestureVisible = true;
     91 
     92     private float mX;
     93     private float mY;
     94 
     95     private float mCurveEndX;
     96     private float mCurveEndY;
     97 
     98     private float mTotalLength;
     99     private boolean mIsGesturing = false;
    100     private boolean mPreviousWasGesturing = false;
    101     private boolean mInterceptEvents = true;
    102     private boolean mIsListeningForGestures;
    103     private boolean mResetGesture;
    104 
    105     // current gesture
    106     private Gesture mCurrentGesture;
    107     private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
    108 
    109     // TODO: Make this a list of WeakReferences
    110     private final ArrayList<OnGestureListener> mOnGestureListeners =
    111             new ArrayList<OnGestureListener>();
    112     // TODO: Make this a list of WeakReferences
    113     private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
    114             new ArrayList<OnGesturePerformedListener>();
    115     // TODO: Make this a list of WeakReferences
    116     private final ArrayList<OnGesturingListener> mOnGesturingListeners =
    117             new ArrayList<OnGesturingListener>();
    118 
    119     private boolean mHandleGestureActions;
    120 
    121     // fading out effect
    122     private boolean mIsFadingOut = false;
    123     private float mFadingAlpha = 1.0f;
    124     private final AccelerateDecelerateInterpolator mInterpolator =
    125             new AccelerateDecelerateInterpolator();
    126 
    127     private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
    128 
    129     public GestureOverlayView(Context context) {
    130         super(context);
    131         init();
    132     }
    133 
    134     public GestureOverlayView(Context context, AttributeSet attrs) {
    135         this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
    136     }
    137 
    138     public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
    139         this(context, attrs, defStyleAttr, 0);
    140     }
    141 
    142     public GestureOverlayView(
    143             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    144         super(context, attrs, defStyleAttr, defStyleRes);
    145 
    146         final TypedArray a = context.obtainStyledAttributes(
    147                 attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes);
    148 
    149         mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
    150                 mGestureStrokeWidth);
    151         mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
    152         mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
    153                 mCertainGestureColor);
    154         mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
    155                 mUncertainGestureColor);
    156         mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
    157         mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
    158         mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
    159                 mGestureStrokeType);
    160         mGestureStrokeLengthThreshold = a.getFloat(
    161                 R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
    162                 mGestureStrokeLengthThreshold);
    163         mGestureStrokeAngleThreshold = a.getFloat(
    164                 R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
    165                 mGestureStrokeAngleThreshold);
    166         mGestureStrokeSquarenessTreshold = a.getFloat(
    167                 R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
    168                 mGestureStrokeSquarenessTreshold);
    169         mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
    170                 mInterceptEvents);
    171         mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
    172                 mFadeEnabled);
    173         mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
    174 
    175         a.recycle();
    176 
    177         init();
    178     }
    179 
    180     private void init() {
    181         setWillNotDraw(false);
    182 
    183         final Paint gesturePaint = mGesturePaint;
    184         gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
    185         gesturePaint.setColor(mCertainGestureColor);
    186         gesturePaint.setStyle(Paint.Style.STROKE);
    187         gesturePaint.setStrokeJoin(Paint.Join.ROUND);
    188         gesturePaint.setStrokeCap(Paint.Cap.ROUND);
    189         gesturePaint.setStrokeWidth(mGestureStrokeWidth);
    190         gesturePaint.setDither(DITHER_FLAG);
    191 
    192         mCurrentColor = mCertainGestureColor;
    193         setPaintAlpha(255);
    194     }
    195 
    196     public ArrayList<GesturePoint> getCurrentStroke() {
    197         return mStrokeBuffer;
    198     }
    199 
    200     public int getOrientation() {
    201         return mOrientation;
    202     }
    203 
    204     public void setOrientation(int orientation) {
    205         mOrientation = orientation;
    206     }
    207 
    208     public void setGestureColor(@ColorInt int color) {
    209         mCertainGestureColor = color;
    210     }
    211 
    212     public void setUncertainGestureColor(@ColorInt int color) {
    213         mUncertainGestureColor = color;
    214     }
    215 
    216     @ColorInt
    217     public int getUncertainGestureColor() {
    218         return mUncertainGestureColor;
    219     }
    220 
    221     @ColorInt
    222     public int getGestureColor() {
    223         return mCertainGestureColor;
    224     }
    225 
    226     public float getGestureStrokeWidth() {
    227         return mGestureStrokeWidth;
    228     }
    229 
    230     public void setGestureStrokeWidth(float gestureStrokeWidth) {
    231         mGestureStrokeWidth = gestureStrokeWidth;
    232         mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
    233         mGesturePaint.setStrokeWidth(gestureStrokeWidth);
    234     }
    235 
    236     public int getGestureStrokeType() {
    237         return mGestureStrokeType;
    238     }
    239 
    240     public void setGestureStrokeType(int gestureStrokeType) {
    241         mGestureStrokeType = gestureStrokeType;
    242     }
    243 
    244     public float getGestureStrokeLengthThreshold() {
    245         return mGestureStrokeLengthThreshold;
    246     }
    247 
    248     public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
    249         mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
    250     }
    251 
    252     public float getGestureStrokeSquarenessTreshold() {
    253         return mGestureStrokeSquarenessTreshold;
    254     }
    255 
    256     public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
    257         mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
    258     }
    259 
    260     public float getGestureStrokeAngleThreshold() {
    261         return mGestureStrokeAngleThreshold;
    262     }
    263 
    264     public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
    265         mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
    266     }
    267 
    268     public boolean isEventsInterceptionEnabled() {
    269         return mInterceptEvents;
    270     }
    271 
    272     public void setEventsInterceptionEnabled(boolean enabled) {
    273         mInterceptEvents = enabled;
    274     }
    275 
    276     public boolean isFadeEnabled() {
    277         return mFadeEnabled;
    278     }
    279 
    280     public void setFadeEnabled(boolean fadeEnabled) {
    281         mFadeEnabled = fadeEnabled;
    282     }
    283 
    284     public Gesture getGesture() {
    285         return mCurrentGesture;
    286     }
    287 
    288     public void setGesture(Gesture gesture) {
    289         if (mCurrentGesture != null) {
    290             clear(false);
    291         }
    292 
    293         setCurrentColor(mCertainGestureColor);
    294         mCurrentGesture = gesture;
    295 
    296         final Path path = mCurrentGesture.toPath();
    297         final RectF bounds = new RectF();
    298         path.computeBounds(bounds, true);
    299 
    300         // TODO: The path should also be scaled to fit inside this view
    301         mPath.rewind();
    302         mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
    303                 -bounds.top + (getHeight() - bounds.height()) / 2.0f);
    304 
    305         mResetGesture = true;
    306 
    307         invalidate();
    308     }
    309 
    310     public Path getGesturePath() {
    311         return mPath;
    312     }
    313 
    314     public Path getGesturePath(Path path) {
    315         path.set(mPath);
    316         return path;
    317     }
    318 
    319     public boolean isGestureVisible() {
    320         return mGestureVisible;
    321     }
    322 
    323     public void setGestureVisible(boolean visible) {
    324         mGestureVisible = visible;
    325     }
    326 
    327     public long getFadeOffset() {
    328         return mFadeOffset;
    329     }
    330 
    331     public void setFadeOffset(long fadeOffset) {
    332         mFadeOffset = fadeOffset;
    333     }
    334 
    335     public void addOnGestureListener(OnGestureListener listener) {
    336         mOnGestureListeners.add(listener);
    337     }
    338 
    339     public void removeOnGestureListener(OnGestureListener listener) {
    340         mOnGestureListeners.remove(listener);
    341     }
    342 
    343     public void removeAllOnGestureListeners() {
    344         mOnGestureListeners.clear();
    345     }
    346 
    347     public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
    348         mOnGesturePerformedListeners.add(listener);
    349         if (mOnGesturePerformedListeners.size() > 0) {
    350             mHandleGestureActions = true;
    351         }
    352     }
    353 
    354     public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
    355         mOnGesturePerformedListeners.remove(listener);
    356         if (mOnGesturePerformedListeners.size() <= 0) {
    357             mHandleGestureActions = false;
    358         }
    359     }
    360 
    361     public void removeAllOnGesturePerformedListeners() {
    362         mOnGesturePerformedListeners.clear();
    363         mHandleGestureActions = false;
    364     }
    365 
    366     public void addOnGesturingListener(OnGesturingListener listener) {
    367         mOnGesturingListeners.add(listener);
    368     }
    369 
    370     public void removeOnGesturingListener(OnGesturingListener listener) {
    371         mOnGesturingListeners.remove(listener);
    372     }
    373 
    374     public void removeAllOnGesturingListeners() {
    375         mOnGesturingListeners.clear();
    376     }
    377 
    378     public boolean isGesturing() {
    379         return mIsGesturing;
    380     }
    381 
    382     private void setCurrentColor(int color) {
    383         mCurrentColor = color;
    384         if (mFadingHasStarted) {
    385             setPaintAlpha((int) (255 * mFadingAlpha));
    386         } else {
    387             setPaintAlpha(255);
    388         }
    389         invalidate();
    390     }
    391 
    392     /**
    393      * @hide
    394      */
    395     public Paint getGesturePaint() {
    396         return mGesturePaint;
    397     }
    398 
    399     @Override
    400     public void draw(Canvas canvas) {
    401         super.draw(canvas);
    402 
    403         if (mCurrentGesture != null && mGestureVisible) {
    404             canvas.drawPath(mPath, mGesturePaint);
    405         }
    406     }
    407 
    408     private void setPaintAlpha(int alpha) {
    409         alpha += alpha >> 7;
    410         final int baseAlpha = mCurrentColor >>> 24;
    411         final int useAlpha = baseAlpha * alpha >> 8;
    412         mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
    413     }
    414 
    415     public void clear(boolean animated) {
    416         clear(animated, false, true);
    417     }
    418 
    419     private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
    420         setPaintAlpha(255);
    421         removeCallbacks(mFadingOut);
    422         mResetGesture = false;
    423         mFadingOut.fireActionPerformed = fireActionPerformed;
    424         mFadingOut.resetMultipleStrokes = false;
    425 
    426         if (animated && mCurrentGesture != null) {
    427             mFadingAlpha = 1.0f;
    428             mIsFadingOut = true;
    429             mFadingHasStarted = false;
    430             mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
    431 
    432             postDelayed(mFadingOut, mFadeOffset);
    433         } else {
    434             mFadingAlpha = 1.0f;
    435             mIsFadingOut = false;
    436             mFadingHasStarted = false;
    437 
    438             if (immediate) {
    439                 mCurrentGesture = null;
    440                 mPath.rewind();
    441                 invalidate();
    442             } else if (fireActionPerformed) {
    443                 postDelayed(mFadingOut, mFadeOffset);
    444             } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
    445                 mFadingOut.resetMultipleStrokes = true;
    446                 postDelayed(mFadingOut, mFadeOffset);
    447             } else {
    448                 mCurrentGesture = null;
    449                 mPath.rewind();
    450                 invalidate();
    451             }
    452         }
    453     }
    454 
    455     public void cancelClearAnimation() {
    456         setPaintAlpha(255);
    457         mIsFadingOut = false;
    458         mFadingHasStarted = false;
    459         removeCallbacks(mFadingOut);
    460         mPath.rewind();
    461         mCurrentGesture = null;
    462     }
    463 
    464     public void cancelGesture() {
    465         mIsListeningForGestures = false;
    466 
    467         // add the stroke to the current gesture
    468         mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
    469 
    470         // pass the event to handlers
    471         final long now = SystemClock.uptimeMillis();
    472         final MotionEvent event = MotionEvent.obtain(now, now,
    473                 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
    474 
    475         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
    476         int count = listeners.size();
    477         for (int i = 0; i < count; i++) {
    478             listeners.get(i).onGestureCancelled(this, event);
    479         }
    480 
    481         event.recycle();
    482 
    483         clear(false);
    484         mIsGesturing = false;
    485         mPreviousWasGesturing = false;
    486         mStrokeBuffer.clear();
    487 
    488         final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
    489         count = otherListeners.size();
    490         for (int i = 0; i < count; i++) {
    491             otherListeners.get(i).onGesturingEnded(this);
    492         }
    493     }
    494 
    495     @Override
    496     protected void onDetachedFromWindow() {
    497         super.onDetachedFromWindow();
    498         cancelClearAnimation();
    499     }
    500 
    501     @Override
    502     public boolean dispatchTouchEvent(MotionEvent event) {
    503         if (isEnabled()) {
    504             final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
    505                     mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
    506                     mInterceptEvents;
    507 
    508             processEvent(event);
    509 
    510             if (cancelDispatch) {
    511                 event.setAction(MotionEvent.ACTION_CANCEL);
    512             }
    513 
    514             super.dispatchTouchEvent(event);
    515 
    516             return true;
    517         }
    518 
    519         return super.dispatchTouchEvent(event);
    520     }
    521 
    522     private boolean processEvent(MotionEvent event) {
    523         switch (event.getAction()) {
    524             case MotionEvent.ACTION_DOWN:
    525                 touchDown(event);
    526                 invalidate();
    527                 return true;
    528             case MotionEvent.ACTION_MOVE:
    529                 if (mIsListeningForGestures) {
    530                     Rect rect = touchMove(event);
    531                     if (rect != null) {
    532                         invalidate(rect);
    533                     }
    534                     return true;
    535                 }
    536                 break;
    537             case MotionEvent.ACTION_UP:
    538                 if (mIsListeningForGestures) {
    539                     touchUp(event, false);
    540                     invalidate();
    541                     return true;
    542                 }
    543                 break;
    544             case MotionEvent.ACTION_CANCEL:
    545                 if (mIsListeningForGestures) {
    546                     touchUp(event, true);
    547                     invalidate();
    548                     return true;
    549                 }
    550         }
    551 
    552         return false;
    553     }
    554 
    555     private void touchDown(MotionEvent event) {
    556         mIsListeningForGestures = true;
    557 
    558         float x = event.getX();
    559         float y = event.getY();
    560 
    561         mX = x;
    562         mY = y;
    563 
    564         mTotalLength = 0;
    565         mIsGesturing = false;
    566 
    567         if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
    568             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
    569             mResetGesture = false;
    570             mCurrentGesture = null;
    571             mPath.rewind();
    572         } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
    573             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
    574         }
    575 
    576         // if there is fading out going on, stop it.
    577         if (mFadingHasStarted) {
    578             cancelClearAnimation();
    579         } else if (mIsFadingOut) {
    580             setPaintAlpha(255);
    581             mIsFadingOut = false;
    582             mFadingHasStarted = false;
    583             removeCallbacks(mFadingOut);
    584         }
    585 
    586         if (mCurrentGesture == null) {
    587             mCurrentGesture = new Gesture();
    588         }
    589 
    590         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
    591         mPath.moveTo(x, y);
    592 
    593         final int border = mInvalidateExtraBorder;
    594         mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
    595 
    596         mCurveEndX = x;
    597         mCurveEndY = y;
    598 
    599         // pass the event to handlers
    600         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
    601         final int count = listeners.size();
    602         for (int i = 0; i < count; i++) {
    603             listeners.get(i).onGestureStarted(this, event);
    604         }
    605     }
    606 
    607     private Rect touchMove(MotionEvent event) {
    608         Rect areaToRefresh = null;
    609 
    610         final float x = event.getX();
    611         final float y = event.getY();
    612 
    613         final float previousX = mX;
    614         final float previousY = mY;
    615 
    616         final float dx = Math.abs(x - previousX);
    617         final float dy = Math.abs(y - previousY);
    618 
    619         if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
    620             areaToRefresh = mInvalidRect;
    621 
    622             // start with the curve end
    623             final int border = mInvalidateExtraBorder;
    624             areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
    625                     (int) mCurveEndX + border, (int) mCurveEndY + border);
    626 
    627             float cX = mCurveEndX = (x + previousX) / 2;
    628             float cY = mCurveEndY = (y + previousY) / 2;
    629 
    630             mPath.quadTo(previousX, previousY, cX, cY);
    631 
    632             // union with the control point of the new curve
    633             areaToRefresh.union((int) previousX - border, (int) previousY - border,
    634                     (int) previousX + border, (int) previousY + border);
    635 
    636             // union with the end point of the new curve
    637             areaToRefresh.union((int) cX - border, (int) cY - border,
    638                     (int) cX + border, (int) cY + border);
    639 
    640             mX = x;
    641             mY = y;
    642 
    643             mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
    644 
    645             if (mHandleGestureActions && !mIsGesturing) {
    646                 mTotalLength += (float) Math.hypot(dx, dy);
    647 
    648                 if (mTotalLength > mGestureStrokeLengthThreshold) {
    649                     final OrientedBoundingBox box =
    650                             GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
    651 
    652                     float angle = Math.abs(box.orientation);
    653                     if (angle > 90) {
    654                         angle = 180 - angle;
    655                     }
    656 
    657                     if (box.squareness > mGestureStrokeSquarenessTreshold ||
    658                             (mOrientation == ORIENTATION_VERTICAL ?
    659                                     angle < mGestureStrokeAngleThreshold :
    660                                     angle > mGestureStrokeAngleThreshold)) {
    661 
    662                         mIsGesturing = true;
    663                         setCurrentColor(mCertainGestureColor);
    664 
    665                         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
    666                         int count = listeners.size();
    667                         for (int i = 0; i < count; i++) {
    668                             listeners.get(i).onGesturingStarted(this);
    669                         }
    670                     }
    671                 }
    672             }
    673 
    674             // pass the event to handlers
    675             final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
    676             final int count = listeners.size();
    677             for (int i = 0; i < count; i++) {
    678                 listeners.get(i).onGesture(this, event);
    679             }
    680         }
    681 
    682         return areaToRefresh;
    683     }
    684 
    685     private void touchUp(MotionEvent event, boolean cancel) {
    686         mIsListeningForGestures = false;
    687 
    688         // A gesture wasn't started or was cancelled
    689         if (mCurrentGesture != null) {
    690             // add the stroke to the current gesture
    691             mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
    692 
    693             if (!cancel) {
    694                 // pass the event to handlers
    695                 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
    696                 int count = listeners.size();
    697                 for (int i = 0; i < count; i++) {
    698                     listeners.get(i).onGestureEnded(this, event);
    699                 }
    700 
    701                 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
    702                         false);
    703             } else {
    704                 cancelGesture(event);
    705 
    706             }
    707         } else {
    708             cancelGesture(event);
    709         }
    710 
    711         mStrokeBuffer.clear();
    712         mPreviousWasGesturing = mIsGesturing;
    713         mIsGesturing = false;
    714 
    715         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
    716         int count = listeners.size();
    717         for (int i = 0; i < count; i++) {
    718             listeners.get(i).onGesturingEnded(this);
    719         }
    720     }
    721 
    722     private void cancelGesture(MotionEvent event) {
    723         // pass the event to handlers
    724         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
    725         final int count = listeners.size();
    726         for (int i = 0; i < count; i++) {
    727             listeners.get(i).onGestureCancelled(this, event);
    728         }
    729 
    730         clear(false);
    731     }
    732 
    733     private void fireOnGesturePerformed() {
    734         final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
    735         final int count = actionListeners.size();
    736         for (int i = 0; i < count; i++) {
    737             actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
    738         }
    739     }
    740 
    741     private class FadeOutRunnable implements Runnable {
    742         boolean fireActionPerformed;
    743         boolean resetMultipleStrokes;
    744 
    745         public void run() {
    746             if (mIsFadingOut) {
    747                 final long now = AnimationUtils.currentAnimationTimeMillis();
    748                 final long duration = now - mFadingStart;
    749 
    750                 if (duration > mFadeDuration) {
    751                     if (fireActionPerformed) {
    752                         fireOnGesturePerformed();
    753                     }
    754 
    755                     mPreviousWasGesturing = false;
    756                     mIsFadingOut = false;
    757                     mFadingHasStarted = false;
    758                     mPath.rewind();
    759                     mCurrentGesture = null;
    760                     setPaintAlpha(255);
    761                 } else {
    762                     mFadingHasStarted = true;
    763                     float interpolatedTime = Math.max(0.0f,
    764                             Math.min(1.0f, duration / (float) mFadeDuration));
    765                     mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
    766                     setPaintAlpha((int) (255 * mFadingAlpha));
    767                     postDelayed(this, FADE_ANIMATION_RATE);
    768                 }
    769             } else if (resetMultipleStrokes) {
    770                 mResetGesture = true;
    771             } else {
    772                 fireOnGesturePerformed();
    773 
    774                 mFadingHasStarted = false;
    775                 mPath.rewind();
    776                 mCurrentGesture = null;
    777                 mPreviousWasGesturing = false;
    778                 setPaintAlpha(255);
    779             }
    780 
    781             invalidate();
    782         }
    783     }
    784 
    785     public static interface OnGesturingListener {
    786         void onGesturingStarted(GestureOverlayView overlay);
    787 
    788         void onGesturingEnded(GestureOverlayView overlay);
    789     }
    790 
    791     public static interface OnGestureListener {
    792         void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
    793 
    794         void onGesture(GestureOverlayView overlay, MotionEvent event);
    795 
    796         void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
    797 
    798         void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
    799     }
    800 
    801     public static interface OnGesturePerformedListener {
    802         void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
    803     }
    804 }
    805