Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.camera;
     18 
     19 import android.os.Handler;
     20 import android.os.Message;
     21 import android.view.MotionEvent;
     22 import android.view.ScaleGestureDetector;
     23 import android.view.View;
     24 import android.view.ViewConfiguration;
     25 
     26 import com.android.camera.ui.PieRenderer;
     27 import com.android.camera.ui.RenderOverlay;
     28 import com.android.camera.ui.ZoomRenderer;
     29 import com.android.gallery3d.R;
     30 
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 
     34 public class PreviewGestures
     35         implements ScaleGestureDetector.OnScaleGestureListener {
     36 
     37     private static final String TAG = "CAM_gestures";
     38 
     39     private static final long TIMEOUT_PIE = 200;
     40     private static final int MSG_PIE = 1;
     41     private static final int MODE_NONE = 0;
     42     private static final int MODE_PIE = 1;
     43     private static final int MODE_ZOOM = 2;
     44     private static final int MODE_MODULE = 3;
     45     private static final int MODE_ALL = 4;
     46     private static final int MODE_SWIPE = 5;
     47 
     48     public static final int DIR_UP = 0;
     49     public static final int DIR_DOWN = 1;
     50     public static final int DIR_LEFT = 2;
     51     public static final int DIR_RIGHT = 3;
     52 
     53     private CameraActivity mActivity;
     54     private SingleTapListener mTapListener;
     55     private RenderOverlay mOverlay;
     56     private PieRenderer mPie;
     57     private ZoomRenderer mZoom;
     58     private MotionEvent mDown;
     59     private MotionEvent mCurrent;
     60     private ScaleGestureDetector mScale;
     61     private List<View> mReceivers;
     62     private List<View> mUnclickableAreas;
     63     private int mMode;
     64     private int mSlop;
     65     private int mTapTimeout;
     66     private boolean mEnabled;
     67     private boolean mZoomOnly;
     68     private int mOrientation;
     69     private int[] mLocation;
     70     private SwipeListener mSwipeListener;
     71 
     72     private Handler mHandler = new Handler() {
     73         public void handleMessage(Message msg) {
     74             if (msg.what == MSG_PIE) {
     75                 mMode = MODE_PIE;
     76                 openPie();
     77                 cancelActivityTouchHandling(mDown);
     78             }
     79         }
     80     };
     81 
     82     public interface SingleTapListener {
     83         public void onSingleTapUp(View v, int x, int y);
     84     }
     85 
     86     interface SwipeListener {
     87         public void onSwipe(int direction);
     88     }
     89 
     90     public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener,
     91             ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) {
     92         mActivity = ctx;
     93         mTapListener = tapListener;
     94         mPie = pie;
     95         mZoom = zoom;
     96         mMode = MODE_ALL;
     97         mScale = new ScaleGestureDetector(ctx, this);
     98         mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
     99         mTapTimeout = ViewConfiguration.getTapTimeout();
    100         mEnabled = true;
    101         mLocation = new int[2];
    102         mSwipeListener = swipe;
    103     }
    104 
    105     public void setRenderOverlay(RenderOverlay overlay) {
    106         mOverlay = overlay;
    107     }
    108 
    109     public void setOrientation(int orientation) {
    110         mOrientation = orientation;
    111     }
    112 
    113     public void setEnabled(boolean enabled) {
    114         mEnabled = enabled;
    115         if (!enabled) {
    116             cancelPie();
    117         }
    118     }
    119 
    120     public void setZoomOnly(boolean zoom) {
    121         mZoomOnly = zoom;
    122     }
    123 
    124     public void addTouchReceiver(View v) {
    125         if (mReceivers == null) {
    126             mReceivers = new ArrayList<View>();
    127         }
    128         mReceivers.add(v);
    129     }
    130 
    131     public void removeTouchReceiver(View v) {
    132         if (mReceivers == null || v == null) return;
    133         mReceivers.remove(v);
    134     }
    135 
    136     public void addUnclickableArea(View v) {
    137         if (mUnclickableAreas == null) {
    138             mUnclickableAreas = new ArrayList<View>();
    139         }
    140         mUnclickableAreas.add(v);
    141     }
    142 
    143     public void clearTouchReceivers() {
    144         if (mReceivers != null) {
    145             mReceivers.clear();
    146         }
    147     }
    148 
    149     public void clearUnclickableAreas() {
    150         if (mUnclickableAreas != null) {
    151             mUnclickableAreas.clear();
    152         }
    153     }
    154 
    155     private boolean checkClickable(MotionEvent m) {
    156         if (mUnclickableAreas != null) {
    157             for (View v : mUnclickableAreas) {
    158                 if (isInside(m, v)) {
    159                     return false;
    160                 }
    161             }
    162         }
    163         return true;
    164     }
    165 
    166     public void reset() {
    167         clearTouchReceivers();
    168         clearUnclickableAreas();
    169     }
    170 
    171     public boolean dispatchTouch(MotionEvent m) {
    172         if (!mEnabled) {
    173             return mActivity.superDispatchTouchEvent(m);
    174         }
    175         mCurrent = m;
    176         if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
    177             if (checkReceivers(m)) {
    178                 mMode = MODE_MODULE;
    179                 return mActivity.superDispatchTouchEvent(m);
    180             } else {
    181                 mMode = MODE_ALL;
    182                 mDown = MotionEvent.obtain(m);
    183                 if (mPie != null && mPie.showsItems()) {
    184                     mMode = MODE_PIE;
    185                     return sendToPie(m);
    186                 }
    187                 if (mPie != null && !mZoomOnly && checkClickable(m)) {
    188                     mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
    189                 }
    190                 if (mZoom != null) {
    191                     mScale.onTouchEvent(m);
    192                 }
    193                 // make sure this is ok
    194                 return mActivity.superDispatchTouchEvent(m);
    195             }
    196         } else if (mMode == MODE_NONE) {
    197             return false;
    198         } else if (mMode == MODE_SWIPE) {
    199             if (MotionEvent.ACTION_UP == m.getActionMasked()) {
    200                 mSwipeListener.onSwipe(getSwipeDirection(m));
    201             }
    202             return true;
    203         } else if (mMode == MODE_PIE) {
    204             if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
    205                 sendToPie(makeCancelEvent(m));
    206                 if (mZoom != null) {
    207                     onScaleBegin(mScale);
    208                 }
    209             } else {
    210                 return sendToPie(m);
    211             }
    212             return true;
    213         } else if (mMode == MODE_ZOOM) {
    214             mScale.onTouchEvent(m);
    215             if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
    216                 mMode = MODE_NONE;
    217                 onScaleEnd(mScale);
    218             }
    219             return true;
    220         } else if (mMode == MODE_MODULE) {
    221             return mActivity.superDispatchTouchEvent(m);
    222         } else {
    223             // didn't receive down event previously;
    224             // assume module wasn't initialzed and ignore this event.
    225             if (mDown == null) {
    226                 return true;
    227             }
    228             if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
    229                 if (!mZoomOnly) {
    230                     cancelPie();
    231                     sendToPie(makeCancelEvent(m));
    232                 }
    233                 if (mZoom != null) {
    234                     mScale.onTouchEvent(m);
    235                     onScaleBegin(mScale);
    236                 }
    237             } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
    238                     && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
    239                 // user initiated and stopped zoom gesture without zooming
    240                 mScale.onTouchEvent(m);
    241                 onScaleEnd(mScale);
    242             }
    243             // not zoom or pie mode and no timeout yet
    244             if (mZoom != null) {
    245                 boolean res = mScale.onTouchEvent(m);
    246                 if (mScale.isInProgress()) {
    247                     cancelPie();
    248                     cancelActivityTouchHandling(m);
    249                     return res;
    250                 }
    251             }
    252             if (MotionEvent.ACTION_UP == m.getActionMasked()) {
    253                 cancelPie();
    254                 // must have been tap
    255                 if (m.getEventTime() - mDown.getEventTime() < mTapTimeout
    256                         && checkClickable(m)) {
    257                     cancelActivityTouchHandling(m);
    258                     mTapListener.onSingleTapUp(null,
    259                             (int) mDown.getX() - mOverlay.getWindowPositionX(),
    260                             (int) mDown.getY() - mOverlay.getWindowPositionY());
    261                     return true;
    262                 } else {
    263                     return mActivity.superDispatchTouchEvent(m);
    264                 }
    265             } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
    266                 if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
    267                         || Math.abs(m.getY() - mDown.getY()) > mSlop) {
    268                     // moved too far and no timeout yet, no focus or pie
    269                     cancelPie();
    270                     int dir = getSwipeDirection(m);
    271                     if (dir == DIR_LEFT) {
    272                         mMode = MODE_MODULE;
    273                         return mActivity.superDispatchTouchEvent(m);
    274                     } else {
    275                         cancelActivityTouchHandling(m);
    276                         mMode = MODE_NONE;
    277                     }
    278                 }
    279             }
    280             return false;
    281         }
    282     }
    283 
    284     private boolean checkReceivers(MotionEvent m) {
    285         if (mReceivers != null) {
    286             for (View receiver : mReceivers) {
    287                 if (isInside(m, receiver)) {
    288                     return true;
    289                 }
    290             }
    291         }
    292         return false;
    293     }
    294 
    295     // left tests for finger moving right to left
    296     private int getSwipeDirection(MotionEvent m) {
    297         float dx = 0;
    298         float dy = 0;
    299         switch (mOrientation) {
    300         case 0:
    301             dx = m.getX() - mDown.getX();
    302             dy = m.getY() - mDown.getY();
    303             break;
    304         case 90:
    305             dx = - (m.getY() - mDown.getY());
    306             dy = m.getX() - mDown.getX();
    307             break;
    308         case 180:
    309             dx = -(m.getX() - mDown.getX());
    310             dy = m.getY() - mDown.getY();
    311             break;
    312         case 270:
    313             dx = m.getY() - mDown.getY();
    314             dy = m.getX() - mDown.getX();
    315             break;
    316         }
    317         if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT;
    318         if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT;
    319         if (dy > 0) return DIR_DOWN;
    320         return DIR_UP;
    321     }
    322 
    323     private boolean isInside(MotionEvent evt, View v) {
    324         v.getLocationInWindow(mLocation);
    325         // when view is flipped horizontally
    326         if ((int) v.getRotationY() == 180) {
    327             mLocation[0] -= v.getWidth();
    328         }
    329         // when view is flipped vertically
    330         if ((int) v.getRotationX() == 180) {
    331             mLocation[1] -= v.getHeight();
    332         }
    333         return (v.getVisibility() == View.VISIBLE
    334                 && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth()
    335                 && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight());
    336     }
    337 
    338     public void cancelActivityTouchHandling(MotionEvent m) {
    339         mActivity.superDispatchTouchEvent(makeCancelEvent(m));
    340     }
    341 
    342     private MotionEvent makeCancelEvent(MotionEvent m) {
    343         MotionEvent c = MotionEvent.obtain(m);
    344         c.setAction(MotionEvent.ACTION_CANCEL);
    345         return c;
    346     }
    347 
    348     private void openPie() {
    349         mDown.offsetLocation(-mOverlay.getWindowPositionX(),
    350                 -mOverlay.getWindowPositionY());
    351         mOverlay.directDispatchTouch(mDown, mPie);
    352     }
    353 
    354     private void cancelPie() {
    355         mHandler.removeMessages(MSG_PIE);
    356     }
    357 
    358     private boolean sendToPie(MotionEvent m) {
    359         m.offsetLocation(-mOverlay.getWindowPositionX(),
    360                 -mOverlay.getWindowPositionY());
    361         return mOverlay.directDispatchTouch(m, mPie);
    362     }
    363 
    364     @Override
    365     public boolean onScale(ScaleGestureDetector detector) {
    366         return mZoom.onScale(detector);
    367     }
    368 
    369     @Override
    370     public boolean onScaleBegin(ScaleGestureDetector detector) {
    371         if (mMode != MODE_ZOOM) {
    372             mMode = MODE_ZOOM;
    373             cancelActivityTouchHandling(mCurrent);
    374         }
    375         if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
    376             return mZoom.onScaleBegin(detector);
    377         } else {
    378             return true;
    379         }
    380     }
    381 
    382     @Override
    383     public void onScaleEnd(ScaleGestureDetector detector) {
    384         if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
    385             mZoom.onScaleEnd(detector);
    386         }
    387     }
    388 }
    389