View Javadoc

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 }