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