1 /*
2  * Copyright (C) 2008 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.content.Context;
20 import android.util.AttributeSet;
21 import android.view.GestureDetector;
22 import android.view.GestureDetector.SimpleOnGestureListener;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.widget.ImageView;
26 
27 import com.android.camera.debug.Log;
28 import com.android.camera.ui.TouchCoordinate;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * A button designed to be used for the on-screen shutter button.
35  * It's currently an {@code ImageView} that can call a delegate when the
36  * pressed state changes.
37  */
38 public class ShutterButton extends ImageView {
39     private static final Log.Tag TAG = new Log.Tag("ShutterButton");
40     public static final float ALPHA_WHEN_ENABLED = 1f;
41     public static final float ALPHA_WHEN_DISABLED = 0.2f;
42     private boolean mTouchEnabled = true;
43     private TouchCoordinate mTouchCoordinate;
44     private final GestureDetector mGestureDetector;
45 
46     /**
47      * A callback to be invoked when a ShutterButton's pressed state changes.
48      */
49     public interface OnShutterButtonListener {
50         /**
51          * Called when a ShutterButton has been pressed.
52          *
53          * @param pressed The ShutterButton that was pressed.
54          */
onShutterButtonFocus(boolean pressed)55         void onShutterButtonFocus(boolean pressed);
onShutterCoordinate(TouchCoordinate coord)56         void onShutterCoordinate(TouchCoordinate coord);
onShutterButtonClick()57         void onShutterButtonClick();
58 
59         /**
60          * Called when shutter button is held down for a long press.
61          */
onShutterButtonLongPressed()62         void onShutterButtonLongPressed();
63     }
64 
65     /**
66      * A gesture listener to detect long presses.
67      */
68     private class LongPressGestureListener extends SimpleOnGestureListener {
69         @Override
onLongPress(MotionEvent event)70         public void onLongPress(MotionEvent event) {
71             for (OnShutterButtonListener listener : mListeners) {
72                 listener.onShutterButtonLongPressed();
73             }
74         }
75     }
76 
77     private List<OnShutterButtonListener> mListeners
78         = new ArrayList<OnShutterButtonListener>();
79     private boolean mOldPressed;
80 
ShutterButton(Context context, AttributeSet attrs)81     public ShutterButton(Context context, AttributeSet attrs) {
82         super(context, attrs);
83         mGestureDetector = new GestureDetector(context, new LongPressGestureListener());
84         mGestureDetector.setIsLongpressEnabled(true);
85     }
86 
87     /**
88      * Add an {@link OnShutterButtonListener} to a set of listeners.
89      */
addOnShutterButtonListener(OnShutterButtonListener listener)90     public void addOnShutterButtonListener(OnShutterButtonListener listener) {
91         if (!mListeners.contains(listener)) {
92             mListeners.add(listener);
93         }
94     }
95 
96     /**
97      * Remove an {@link OnShutterButtonListener} from a set of listeners.
98      */
removeOnShutterButtonListener(OnShutterButtonListener listener)99     public void removeOnShutterButtonListener(OnShutterButtonListener listener) {
100         if (mListeners.contains(listener)) {
101             mListeners.remove(listener);
102         }
103     }
104 
105     @Override
dispatchTouchEvent(MotionEvent m)106     public boolean dispatchTouchEvent(MotionEvent m) {
107         if (mTouchEnabled) {
108             // Don't send ACTION_MOVE messages to gesture detector unless event motion is out of
109             // shutter button view. A small motion resets the long tap status. A long tap should
110             // be interpreted as the duration the finger is held down on the shutter button,
111             // regardless of any small motions. If motion moves out of shutter button view, the
112             // gesture detector needs to be notified to reset the long tap status.
113             if (m.getActionMasked() != MotionEvent.ACTION_MOVE
114                 || m.getX() < 0 || m.getY() < 0
115                 || m.getX() >= getWidth() || m.getY() >= getHeight()) {
116                 mGestureDetector.onTouchEvent(m);
117             }
118             if (m.getActionMasked() == MotionEvent.ACTION_UP) {
119                 mTouchCoordinate = new TouchCoordinate(m.getX(), m.getY(), this.getMeasuredWidth(),
120                         this.getMeasuredHeight());
121             }
122             return super.dispatchTouchEvent(m);
123         } else {
124             return false;
125         }
126     }
127 
enableTouch(boolean enable)128     public void enableTouch(boolean enable) {
129         mTouchEnabled = enable;
130     }
131 
132     /**
133      * Hook into the drawable state changing to get changes to isPressed -- the
134      * onPressed listener doesn't always get called when the pressed state
135      * changes.
136      */
137     @Override
drawableStateChanged()138     protected void drawableStateChanged() {
139         super.drawableStateChanged();
140         final boolean pressed = isPressed();
141         if (pressed != mOldPressed) {
142             if (!pressed) {
143                 // When pressing the physical camera button the sequence of
144                 // events is:
145                 //    focus pressed, optional camera pressed, focus released.
146                 // We want to emulate this sequence of events with the shutter
147                 // button. When clicking using a trackball button, the view
148                 // system changes the drawable state before posting click
149                 // notification, so the sequence of events is:
150                 //    pressed(true), optional click, pressed(false)
151                 // When clicking using touch events, the view system changes the
152                 // drawable state after posting click notification, so the
153                 // sequence of events is:
154                 //    pressed(true), pressed(false), optional click
155                 // Since we're emulating the physical camera button, we want to
156                 // have the same order of events. So we want the optional click
157                 // callback to be delivered before the pressed(false) callback.
158                 //
159                 // To do this, we delay the posting of the pressed(false) event
160                 // slightly by pushing it on the event queue. This moves it
161                 // after the optional click notification, so our client always
162                 // sees events in this sequence:
163                 //     pressed(true), optional click, pressed(false)
164                 post(new Runnable() {
165                     @Override
166                     public void run() {
167                         callShutterButtonFocus(pressed);
168                     }
169                 });
170             } else {
171                 callShutterButtonFocus(pressed);
172             }
173             mOldPressed = pressed;
174         }
175     }
176 
callShutterButtonFocus(boolean pressed)177     private void callShutterButtonFocus(boolean pressed) {
178         for (OnShutterButtonListener listener : mListeners) {
179             listener.onShutterButtonFocus(pressed);
180         }
181     }
182 
183     @Override
performClick()184     public boolean performClick() {
185         boolean result = super.performClick();
186         if (getVisibility() == View.VISIBLE) {
187             for (OnShutterButtonListener listener : mListeners) {
188                 listener.onShutterCoordinate(mTouchCoordinate);
189                 mTouchCoordinate = null;
190                 listener.onShutterButtonClick();
191             }
192         }
193         return result;
194     }
195 }
196