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.util.Log;
     22 import android.view.MotionEvent;
     23 import android.view.ScaleGestureDetector;
     24 import android.view.View;
     25 import android.view.ViewConfiguration;
     26 
     27 import com.android.camera.ui.PieRenderer;
     28 import com.android.camera.ui.RenderOverlay;
     29 import com.android.camera.ui.ZoomRenderer;
     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 
     47     private CameraActivity mActivity;
     48     private CameraModule mModule;
     49     private RenderOverlay mOverlay;
     50     private PieRenderer mPie;
     51     private ZoomRenderer mZoom;
     52     private MotionEvent mDown;
     53     private MotionEvent mCurrent;
     54     private ScaleGestureDetector mScale;
     55     private List<View> mReceivers;
     56     private int mMode;
     57     private int mSlop;
     58     private int mTapTimeout;
     59     private boolean mEnabled;
     60     private boolean mZoomOnly;
     61     private int mOrientation;
     62     private int[] mLocation;
     63 
     64     private Handler mHandler = new Handler() {
     65         public void handleMessage(Message msg) {
     66             if (msg.what == MSG_PIE) {
     67                 mMode = MODE_PIE;
     68                 openPie();
     69                 cancelActivityTouchHandling(mDown);
     70             }
     71         }
     72     };
     73 
     74     public PreviewGestures(CameraActivity ctx, CameraModule module,
     75             ZoomRenderer zoom, PieRenderer pie) {
     76         mActivity = ctx;
     77         mModule = module;
     78         mPie = pie;
     79         mZoom = zoom;
     80         mMode = MODE_ALL;
     81         mScale = new ScaleGestureDetector(ctx, this);
     82         mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
     83         mTapTimeout = ViewConfiguration.getTapTimeout();
     84         mEnabled = true;
     85         mLocation = new int[2];
     86     }
     87 
     88     public void setRenderOverlay(RenderOverlay overlay) {
     89         mOverlay = overlay;
     90     }
     91 
     92     public void setOrientation(int orientation) {
     93         mOrientation = orientation;
     94     }
     95 
     96     public void setEnabled(boolean enabled) {
     97         mEnabled = enabled;
     98         if (!enabled) {
     99             cancelPie();
    100         }
    101     }
    102 
    103     public void setZoomOnly(boolean zoom) {
    104         mZoomOnly = zoom;
    105     }
    106 
    107     public void addTouchReceiver(View v) {
    108         if (mReceivers == null) {
    109             mReceivers = new ArrayList<View>();
    110         }
    111         mReceivers.add(v);
    112     }
    113 
    114     public void clearTouchReceivers() {
    115         if (mReceivers != null) {
    116             mReceivers.clear();
    117         }
    118     }
    119 
    120     public boolean dispatchTouch(MotionEvent m) {
    121         if (!mEnabled) {
    122             return mActivity.superDispatchTouchEvent(m);
    123         }
    124         mCurrent = m;
    125         if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
    126             if (checkReceivers(m)) {
    127                 mMode = MODE_MODULE;
    128                 return mActivity.superDispatchTouchEvent(m);
    129             } else {
    130                 mMode = MODE_ALL;
    131                 mDown = MotionEvent.obtain(m);
    132                 if (mPie != null && mPie.showsItems()) {
    133                     mMode = MODE_PIE;
    134                     return sendToPie(m);
    135                 }
    136                 if (mPie != null && !mZoomOnly) {
    137                     mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
    138                 }
    139                 if (mZoom != null) {
    140                     mScale.onTouchEvent(m);
    141                 }
    142                 // make sure this is ok
    143                 return mActivity.superDispatchTouchEvent(m);
    144             }
    145         } else if (mMode == MODE_NONE) {
    146             return false;
    147         } else if (mMode == MODE_PIE) {
    148             if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
    149                 sendToPie(makeCancelEvent(m));
    150                 if (mZoom != null) {
    151                     onScaleBegin(mScale);
    152                 }
    153             } else {
    154                 return sendToPie(m);
    155             }
    156             return true;
    157         } else if (mMode == MODE_ZOOM) {
    158             mScale.onTouchEvent(m);
    159             if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
    160                 mMode = MODE_NONE;
    161                 onScaleEnd(mScale);
    162             }
    163             return true;
    164         } else if (mMode == MODE_MODULE) {
    165             return mActivity.superDispatchTouchEvent(m);
    166         } else {
    167             // didn't receive down event previously;
    168             // assume module wasn't initialzed and ignore this event.
    169             if (mDown == null) {
    170                 return true;
    171             }
    172             if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
    173                 if (!mZoomOnly) {
    174                     cancelPie();
    175                     sendToPie(makeCancelEvent(m));
    176                 }
    177                 if (mZoom != null) {
    178                     mScale.onTouchEvent(m);
    179                     onScaleBegin(mScale);
    180                 }
    181             } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
    182                     && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
    183                 // user initiated and stopped zoom gesture without zooming
    184                 mScale.onTouchEvent(m);
    185                 onScaleEnd(mScale);
    186             }
    187             // not zoom or pie mode and no timeout yet
    188             if (mZoom != null) {
    189                 boolean res = mScale.onTouchEvent(m);
    190                 if (mScale.isInProgress()) {
    191                     cancelPie();
    192                     cancelActivityTouchHandling(m);
    193                     return res;
    194                 }
    195             }
    196             if (MotionEvent.ACTION_UP == m.getActionMasked()) {
    197                 cancelPie();
    198                 cancelActivityTouchHandling(m);
    199                 // must have been tap
    200                 if (m.getEventTime() - mDown.getEventTime() < mTapTimeout) {
    201                     mModule.onSingleTapUp(null,
    202                             (int) mDown.getX() - mOverlay.getWindowPositionX(),
    203                             (int) mDown.getY() - mOverlay.getWindowPositionY());
    204                     return true;
    205                 } else {
    206                     return mActivity.superDispatchTouchEvent(m);
    207                 }
    208             } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
    209                 if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
    210                         || Math.abs(m.getY() - mDown.getY()) > mSlop) {
    211                     // moved too far and no timeout yet, no focus or pie
    212                     cancelPie();
    213                     if (isSwipe(m, true)) {
    214                         mMode = MODE_MODULE;
    215                         return mActivity.superDispatchTouchEvent(m);
    216                     } else {
    217                         cancelActivityTouchHandling(m);
    218                         if (isSwipe(m , false)) {
    219                             mMode = MODE_NONE;
    220                         } else if (!mZoomOnly) {
    221                             mMode = MODE_PIE;
    222                             openPie();
    223                             sendToPie(m);
    224                         }
    225                     }
    226                 }
    227             }
    228             return false;
    229         }
    230     }
    231 
    232     private boolean checkReceivers(MotionEvent m) {
    233         if (mReceivers != null) {
    234             for (View receiver : mReceivers) {
    235                 if (isInside(m, receiver)) {
    236                     return true;
    237                 }
    238             }
    239         }
    240         return false;
    241     }
    242 
    243     // left tests for finger moving right to left
    244     private boolean isSwipe(MotionEvent m, boolean left) {
    245         float dx = 0;
    246         float dy = 0;
    247         switch (mOrientation) {
    248         case 0:
    249             dx = m.getX() - mDown.getX();
    250             dy = Math.abs(m.getY() - mDown.getY());
    251             break;
    252         case 90:
    253             dx = - (m.getY() - mDown.getY());
    254             dy = Math.abs(m.getX() - mDown.getX());
    255             break;
    256         case 180:
    257             dx = -(m.getX() - mDown.getX());
    258             dy = Math.abs(m.getY() - mDown.getY());
    259             break;
    260         case 270:
    261             dx = m.getY() - mDown.getY();
    262             dy = Math.abs(m.getX() - mDown.getX());
    263             break;
    264         }
    265         if (left) {
    266             return (dx < 0 && dy / -dx < 0.6f);
    267         } else {
    268             return (dx > 0 && dy / dx < 0.6f);
    269         }
    270     }
    271 
    272     private boolean isInside(MotionEvent evt, View v) {
    273         v.getLocationInWindow(mLocation);
    274         return (v.getVisibility() == View.VISIBLE
    275                 && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth()
    276                 && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight());
    277     }
    278 
    279     public void cancelActivityTouchHandling(MotionEvent m) {
    280         mActivity.superDispatchTouchEvent(makeCancelEvent(m));
    281     }
    282 
    283     private MotionEvent makeCancelEvent(MotionEvent m) {
    284         MotionEvent c = MotionEvent.obtain(m);
    285         c.setAction(MotionEvent.ACTION_CANCEL);
    286         return c;
    287     }
    288 
    289     private void openPie() {
    290         mDown.offsetLocation(-mOverlay.getWindowPositionX(),
    291                 -mOverlay.getWindowPositionY());
    292         mOverlay.directDispatchTouch(mDown, mPie);
    293     }
    294 
    295     private void cancelPie() {
    296         mHandler.removeMessages(MSG_PIE);
    297     }
    298 
    299     private boolean sendToPie(MotionEvent m) {
    300         m.offsetLocation(-mOverlay.getWindowPositionX(),
    301                 -mOverlay.getWindowPositionY());
    302         return mOverlay.directDispatchTouch(m, mPie);
    303     }
    304 
    305     @Override
    306     public boolean onScale(ScaleGestureDetector detector) {
    307         return mZoom.onScale(detector);
    308     }
    309 
    310     @Override
    311     public boolean onScaleBegin(ScaleGestureDetector detector) {
    312         if (mMode != MODE_ZOOM) {
    313             mMode = MODE_ZOOM;
    314             cancelActivityTouchHandling(mCurrent);
    315         }
    316         if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
    317             return mZoom.onScaleBegin(detector);
    318         } else {
    319             return true;
    320         }
    321     }
    322 
    323     @Override
    324     public void onScaleEnd(ScaleGestureDetector detector) {
    325         if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
    326             mZoom.onScaleEnd(detector);
    327         }
    328     }
    329 }
    330