1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package sk.baka.ambient.views;
20
21 import java.util.HashMap;
22 import java.util.Map;
23
24 import android.view.KeyEvent;
25 import android.view.View;
26 import android.view.KeyEvent.Callback;
27
28 /***
29 * Handles key events. {@link #onKey(int, int, KeyEvent)} is invoked on
30 * {@link Callback#onKeyDown(int, KeyEvent)} and
31 * {@link Callback#onKeyMultiple(int, int, KeyEvent)}. If key-down event is
32 * suppressed, the key-up event is suppressed as well.
33 * <p/>
34 * Optionally, it can be directed to move focus on the Center+Up/Down keys.
35 *
36 * @author Martin Vysny
37 */
38 public class KeyEventHandler implements KeyEvent.Callback {
39
40 /***
41 * Creates new handler.
42 */
43 protected KeyEventHandler() {
44 }
45
46 /***
47 * Moves focus to next view.
48 *
49 * @param view
50 * the current view.
51 * @param up
52 * moves the focus up if <code>true</code>, moves the focus down
53 * otherwise.
54 * @return <code>true</code> if the focus was moved
55 */
56 public static boolean moveFocus(final View view, final boolean up) {
57 if (view == null) {
58 return false;
59 }
60 final View nextFocusable = view.focusSearch(!up ? View.FOCUS_DOWN
61 : View.FOCUS_UP);
62 if (nextFocusable != null) {
63 return nextFocusable.requestFocus();
64 }
65 return false;
66 }
67
68 /***
69 * Invoked on key press. By default invokes one of the
70 * <code>onUp, onDown...</code> event handlers.
71 *
72 * @param keyCode
73 * the key code
74 * @param count
75 * the key repeat count. 0 is the first key press
76 * @param event
77 * the event
78 * @return <code>true</code> if the event was handled and is to be
79 * suppressed, <code>false</code> otherwise.
80 */
81 protected boolean onKey(int keyCode, int count, KeyEvent event) {
82 switch (keyCode) {
83 case KeyEvent.KEYCODE_DPAD_UP:
84 return onUp();
85 case KeyEvent.KEYCODE_DPAD_CENTER:
86 case KeyEvent.KEYCODE_ENTER:
87 return onConfirm();
88 case KeyEvent.KEYCODE_DPAD_DOWN:
89 return onDown();
90 case KeyEvent.KEYCODE_DPAD_LEFT:
91 return onLeft();
92 case KeyEvent.KEYCODE_DPAD_RIGHT:
93 return onRight();
94 }
95 return false;
96 }
97
98 /***
99 * Handles the key-up event. The event is not cancelable by overriding
100 * class. By default does nothing.
101 *
102 * @param keyCode
103 * the key code
104 * @param event
105 * the event
106 */
107 protected void keyUp(final int keyCode, final KeyEvent event) {
108 }
109
110 /***
111 * Invoked on {@link KeyEvent#KEYCODE_DPAD_UP up} arrow key press.
112 *
113 * @return <code>true</code> if the event was handled and is to be
114 * suppressed, <code>false</code> otherwise.
115 */
116 protected boolean onUp() {
117 return false;
118 }
119
120 /***
121 * Invoked on {@link KeyEvent#KEYCODE_DPAD_DOWN down} arrow key press.
122 *
123 * @return <code>true</code> if the event was handled and is to be
124 * suppressed, <code>false</code> otherwise.
125 */
126 protected boolean onDown() {
127 return false;
128 }
129
130 /***
131 * Invoked on {@link KeyEvent#KEYCODE_DPAD_LEFT left} arrow key press.
132 *
133 * @return <code>true</code> if the event was handled and is to be
134 * suppressed, <code>false</code> otherwise.
135 */
136 protected boolean onLeft() {
137 return false;
138 }
139
140 /***
141 * Invoked on {@link KeyEvent#KEYCODE_DPAD_RIGHT right} arrow key press.
142 *
143 * @return <code>true</code> if the event was handled and is to be
144 * suppressed, <code>false</code> otherwise.
145 */
146 protected boolean onRight() {
147 return false;
148 }
149
150 /***
151 * Invoked on {@link KeyEvent#KEYCODE_DPAD_CENTER center} or
152 * {@link KeyEvent#KEYCODE_ENTER} key press.
153 *
154 * @return <code>true</code> if the event was handled and is to be
155 * suppressed, <code>false</code> otherwise.
156 */
157 protected boolean onConfirm() {
158 return false;
159 }
160
161 /***
162 * Map of currently pressed buttons. If the map contains <code>true</code>
163 * then the {@link Callback#onKeyDown(int, KeyEvent)} was handled by this
164 * handler and the {@link Callback#onKeyUp(int, KeyEvent)} must be
165 * suppressed.
166 */
167 protected final Map<Integer, Boolean> keyDownHandled = new HashMap<Integer, Boolean>();
168
169 public final boolean onKeyDown(int keyCode, KeyEvent event) {
170 return onKeyMultiple(keyCode, event.getRepeatCount(), event);
171 }
172
173 public final boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
174 final boolean result = onKey(keyCode, count, event);
175 if (!keyDownHandled.containsKey(keyCode)) {
176 keyDownHandled.put(keyCode, result);
177 }
178 return result;
179 }
180
181 /***
182 * Checks if given key code is the Center/Enter key.
183 *
184 * @param keyCode
185 * the key code to check.
186 * @return <code>true</code> on Center/Enter key, <code>false</code>
187 * otherwise.
188 */
189 public boolean isConfirm(final int keyCode) {
190 return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
191 || keyCode == KeyEvent.KEYCODE_ENTER;
192 }
193
194 public final boolean onKeyUp(int keyCode, KeyEvent event) {
195 keyUp(keyCode, event);
196 final Boolean suppress = keyDownHandled.remove(keyCode);
197 if (suppress == null) {
198 return false;
199 }
200 return suppress;
201 }
202
203 /***
204 * Handles a generic key event.
205 *
206 * @param event
207 * the event to handle
208 * @return <code>true</code> if the event was consumed, <code>false</code>
209 * otherwise.
210 */
211 public final boolean onKey(final KeyEvent event) {
212 switch (event.getAction()) {
213 case KeyEvent.ACTION_UP:
214 return onKeyUp(event.getKeyCode(), event);
215 case KeyEvent.ACTION_DOWN:
216 return onKeyDown(event.getKeyCode(), event);
217 case KeyEvent.ACTION_MULTIPLE:
218 return onKeyMultiple(event.getKeyCode(), event.getRepeatCount(),
219 event);
220 }
221 return false;
222 }
223
224 /***
225 * Composes multiple prioritized callbacks. The callbacks are invoked in
226 * order until a callback consumes the key or there are no more callbacks to
227 * process.
228 *
229 * @param keyCallbacks
230 * a list of key callbacks. Must not be <code>null</code> nor
231 * empty.
232 * @return a callback instance.
233 */
234 public static Callback compose(final Callback... keyCallbacks) {
235 final Callback[] copy = keyCallbacks.clone();
236 return new Callback() {
237 public boolean onKeyDown(int keyCode, KeyEvent event) {
238 for (final Callback c : copy) {
239 if (c.onKeyDown(keyCode, event)) {
240 return true;
241 }
242 }
243 return false;
244 }
245
246 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
247 for (final Callback c : copy) {
248 if (c.onKeyMultiple(keyCode, count, event)) {
249 return true;
250 }
251 }
252 return false;
253 }
254
255 public boolean onKeyUp(int keyCode, KeyEvent event) {
256 for (final Callback c : copy) {
257 if (c.onKeyUp(keyCode, event)) {
258 return true;
259 }
260 }
261 return false;
262 }
263 };
264 }
265 }