OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.content.browser.input; | |
6 | |
7 import android.content.Context; | |
8 import android.hardware.input.InputManager; | |
9 import android.view.InputDevice; | |
10 import android.view.InputDevice.MotionRange; | |
11 import android.view.KeyEvent; | |
12 import android.view.MotionEvent; | |
13 | |
14 import org.chromium.base.CalledByNative; | |
15 import org.chromium.base.JNINamespace; | |
16 import org.chromium.base.ThreadUtils; | |
17 | |
18 import java.util.List; | |
19 | |
20 /** | |
21 * Java counterpart of GamepadPlatformDataFetcherAndroid. | |
22 * Manages game input devices and feed Gamepad API with input data. | |
23 * GamepadPlatformDataFetcherAndroid is merely a wrepper around this. | |
24 * Native callable methods called by GamepadPlatformDataFetcherAndroid on the po ller thread | |
25 * which is a native thread without a java looper. Events are processed on the U I thread. | |
26 */ | |
27 @JNINamespace("content") | |
28 public class GamepadAdapter implements InputManager.InputDeviceListener { | |
29 | |
30 private static final int NUM_WEB_GAMEPADS = 4; | |
31 | |
32 private static GamepadAdapter instance; | |
33 | |
34 private InputManager mInputManager; | |
35 private InputDeviceHandler[] mDeviceHandlers; | |
36 private final Object mDeviceHandlersLock = new Object(); | |
37 private boolean mDataRequested; | |
38 private boolean mIsPaused; | |
39 private long mNativeDataFetcher; | |
40 private int mAttachedToWindowCounter; | |
41 | |
42 private static void initializeInstance() { | |
43 if (instance == null) { | |
44 instance = new GamepadAdapter(); | |
45 } | |
46 } | |
47 | |
48 /** | |
49 * Notifies GamepadAdapter that a {@link ContentView} is attached to a windo w so it should | |
50 * be prepared for input. Must be called before {@link onMotionEvent} or {@l ink onKeyEvent}. | |
51 */ | |
52 public static void onAttachedToWindow(Context context) { | |
53 assert ThreadUtils.runningOnUiThread(); | |
54 initializeInstance(); | |
55 instance.attachedToWindow(context); | |
56 } | |
57 | |
58 private void attachedToWindow(Context context) { | |
59 if (mAttachedToWindowCounter++ == 0) { | |
60 mInputManager = (InputManager) context.getSystemService(Context.INPU T_SERVICE); | |
61 if (mInputManager != null) { | |
boliu
2014/03/12 00:58:46
You'd need to null check everywhere mInputManager
kbalazs
2014/03/12 23:33:50
Done.
| |
62 initializeDevices(); | |
63 mInputManager.registerInputDeviceListener(this, null); | |
64 } | |
65 } | |
66 } | |
67 | |
68 private static boolean isAttached() { | |
69 return instance != null && instance.mAttachedToWindowCounter > 0; | |
70 } | |
71 | |
72 /** | |
73 * Notifies GamepadAdapter that a {@link ContentView} is detached from it's window. | |
74 */ | |
75 public static void onDetachedFromWindow() { | |
76 assert ThreadUtils.runningOnUiThread(); | |
77 assert isAttached(); | |
78 instance.detachedFromWindow(); | |
79 } | |
80 | |
81 private void detachedFromWindow() { | |
82 if (--mAttachedToWindowCounter == 0) { | |
83 synchronized (mDeviceHandlersLock) { | |
84 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) { | |
85 mDeviceHandlers[i] = null; | |
86 } | |
87 } | |
88 mInputManager.unregisterInputDeviceListener(this); | |
89 mInputManager = null; | |
90 } | |
91 } | |
92 | |
93 private void initializeDevices() { | |
94 assert ThreadUtils.runningOnUiThread(); | |
95 InputDeviceHandler[] handlers = new InputDeviceHandler[NUM_WEB_GAMEPADS] ; | |
96 int[] ids = mInputManager.getInputDeviceIds(); | |
97 if (ids == null) return; | |
98 | |
99 int activeDevices = 0; | |
100 for (int i = 0; i < ids.length && activeDevices < NUM_WEB_GAMEPADS; i++) { | |
101 InputDevice device = mInputManager.getInputDevice(ids[i]); | |
102 if (isGameDevice(device)) { | |
103 handlers[activeDevices++] = new InputDeviceHandler(device); | |
104 } | |
105 } | |
106 synchronized (mDeviceHandlersLock) { | |
107 mDeviceHandlers = handlers; | |
108 } | |
109 } | |
110 | |
111 // --------------------------------------------------- | |
112 // Implementation of InputManager.InputDeviceListener. | |
113 @Override | |
114 public void onInputDeviceAdded(int deviceId) { | |
115 ThreadUtils.assertOnUiThread(); | |
116 InputDevice device = mInputManager.getInputDevice(deviceId); | |
117 if (!isGameDevice(device)) | |
118 return; | |
119 int index = nextAvailableIndex(); | |
120 if (index == -1) | |
121 return; | |
122 synchronized (mDeviceHandlersLock) { | |
123 mDeviceHandlers[index] = new InputDeviceHandler(device); | |
124 } | |
125 } | |
126 | |
127 @Override | |
128 public void onInputDeviceRemoved(int deviceId) { | |
129 ThreadUtils.assertOnUiThread(); | |
130 int index = indexForDeviceId(deviceId); | |
131 if (index == -1) | |
132 return; | |
133 synchronized (mDeviceHandlersLock) { | |
134 mDeviceHandlers[index] = null; | |
135 } | |
136 } | |
137 | |
138 @Override | |
139 public void onInputDeviceChanged(int deviceId) { | |
140 ThreadUtils.assertOnUiThread(); | |
141 int index = indexForDeviceId(deviceId); | |
142 if (index == -1) { | |
143 index = nextAvailableIndex(); | |
144 if (index == -1) return; | |
145 } | |
146 InputDevice device = mInputManager.getInputDevice(deviceId); | |
147 synchronized (mDeviceHandlersLock) { | |
148 mDeviceHandlers[index] = null; | |
149 if (isGameDevice(device)) { | |
150 mDeviceHandlers[index] = new InputDeviceHandler(device); | |
151 } | |
152 } | |
153 } | |
154 // --------------------------------------------------- | |
155 | |
156 /** | |
157 * Handles motion events from gamepad devices. | |
158 * | |
159 * @return True if the event has been consumed. | |
160 */ | |
161 public static boolean onMotionEvent(MotionEvent event) { | |
162 assert isAttached(); | |
163 return instance.handleMotionEvent(event); | |
164 } | |
165 | |
166 private boolean handleMotionEvent(MotionEvent event) { | |
167 if (!mDataRequested) return false; | |
jdduke (slow)
2014/03/12 01:06:19
I'm a little concerned about the event flow when |
kbalazs
2014/03/12 23:33:50
Good point, trying to handle this now.
| |
168 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); | |
169 if (handler == null) return false; | |
170 if (!isGameEvent(event)) return false; | |
171 | |
172 handler.handleMotionEvent(event); | |
173 return true; | |
174 } | |
175 | |
176 /** | |
177 * Handles key events from gamepad devices. | |
178 * | |
179 * @return True if the event has been consumed. | |
180 */ | |
181 public static boolean onKeyEvent(KeyEvent event) { | |
182 assert isAttached(); | |
183 return instance.handleKeyEvent(event); | |
184 } | |
185 | |
186 private boolean handleKeyEvent(KeyEvent event) { | |
187 if (!mDataRequested) return false; | |
188 if (event.getAction() != KeyEvent.ACTION_DOWN | |
189 && event.getAction() != KeyEvent.ACTION_UP) { | |
190 return false; | |
191 } | |
192 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); | |
193 if (handler == null) return false; | |
194 int keyCode = event.getKeyCode(); | |
195 if (!isGameKey(keyCode)) return false; | |
196 | |
197 boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; | |
198 handler.handleKeyEvent(keyCode, isDown, event.getEventTime()); | |
199 return true; | |
200 } | |
201 | |
202 @CalledByNative | |
203 static void setDataRequested(long nativeDataFetcher, boolean requested) { | |
204 initializeInstance(); | |
205 instance.mNativeDataFetcher = nativeDataFetcher; | |
jdduke (slow)
2014/03/12 01:06:19
Hmm, we should not be caching the |nativeDataFetch
kbalazs
2014/03/12 23:33:50
Done.
| |
206 instance.mDataRequested = requested; | |
207 } | |
208 | |
209 // Called on polling thread. | |
210 @CalledByNative | |
211 static void getGamepadData() { | |
212 assert instance != null; | |
213 instance.collectGamepadData(); | |
214 } | |
215 | |
216 private void collectGamepadData() { | |
217 assert mNativeDataFetcher != 0; | |
218 mDataRequested = true; | |
jdduke (slow)
2014/03/12 01:06:19
This variable is accessed on the UI thread, it sho
kbalazs
2014/03/12 23:33:50
There are in fact a race with the UI thread about
| |
219 synchronized (mDeviceHandlersLock) { | |
220 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) { | |
221 if (mDeviceHandlers[i] == null) { | |
jdduke (slow)
2014/03/12 01:06:19
Just so I understand this correctly, if |collectGa
kbalazs
2014/03/12 23:33:50
Done.
| |
222 nativeRefreshDevice(mNativeDataFetcher, i, false, null, null , 0, null, null); | |
223 } else { | |
224 InputDeviceHandler handler = mDeviceHandlers[i]; | |
225 WebGamepadData data = handler.produceWebData(); | |
226 nativeRefreshDevice(mNativeDataFetcher, i, true, data.id, da ta.mapping, | |
227 handler.getTimestamp(), data.axes, data.buttons); | |
228 } | |
229 } | |
230 } | |
231 } | |
232 | |
233 private int nextAvailableIndex() { | |
234 for (int i = 0; i < mDeviceHandlers.length; i++) { | |
235 if (mDeviceHandlers[i] == null) | |
236 return i; | |
237 } | |
238 return -1; | |
239 } | |
240 | |
241 private int indexForDeviceId(int deviceId) { | |
242 for (int i = 0; i < mDeviceHandlers.length; i++) { | |
243 if (mDeviceHandlers[i] != null && | |
244 mDeviceHandlers[i].getInputDevice().getId() == deviceId) | |
245 return i; | |
246 } | |
247 return -1; | |
248 } | |
249 | |
250 private InputDeviceHandler handlerForDeviceId(int deviceId) { | |
251 int index = indexForDeviceId(deviceId); | |
252 return index == -1 ? null : mDeviceHandlers[index]; | |
253 } | |
254 | |
255 private static boolean isGameDevice(InputDevice device) { | |
256 return (device.getSources() & InputDevice.SOURCE_JOYSTICK) != 0; | |
257 } | |
258 | |
259 private static boolean isGameEvent(MotionEvent event) { | |
260 return (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0 | |
261 && event.getAction() == MotionEvent.ACTION_MOVE; | |
262 } | |
263 | |
264 private static boolean isGameKey(int keyCode) { | |
265 switch (keyCode) { | |
266 case KeyEvent.KEYCODE_DPAD_UP: | |
267 case KeyEvent.KEYCODE_DPAD_DOWN: | |
268 case KeyEvent.KEYCODE_DPAD_LEFT: | |
269 case KeyEvent.KEYCODE_DPAD_RIGHT: | |
270 case KeyEvent.KEYCODE_DPAD_CENTER: | |
271 return true; | |
272 default: | |
273 return KeyEvent.isGamepadButton(keyCode); | |
274 } | |
275 } | |
276 | |
277 private static class InputDeviceHandler { | |
278 private final InputDevice mDevice; | |
279 private long mTimestamp; | |
280 private final int[] mAxes; | |
281 | |
282 // Apparently all axis id's and keycodes are less then 256. Given this t he most effective | |
283 // representation of an associative array is simply an array. | |
284 private final float[] mAxisValues = new float[256]; | |
285 private final boolean[] mButtonsPressedStates = new boolean[256]; | |
286 | |
287 private final GamepadDataMapper mMapper; | |
288 private final Object mLock = new Object(); | |
289 | |
290 InputDevice getInputDevice() { return mDevice; } | |
291 long getTimestamp() { return mTimestamp; } | |
292 | |
293 InputDeviceHandler(InputDevice device) { | |
294 assert isGameDevice(device); | |
295 mDevice = device; | |
296 mMapper = GamepadDataMapper.createDataMapper(device.getName()); | |
297 | |
298 List<MotionRange> ranges = device.getMotionRanges(); | |
299 mAxes = new int[ranges.size()]; | |
300 int i = 0; | |
301 for (MotionRange range : ranges) { | |
302 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) { | |
303 int axis = range.getAxis(); | |
304 assert axis < 256; | |
305 mAxes[i++] = axis; | |
306 } | |
307 } | |
308 } | |
309 | |
310 // Called on UI thread. | |
311 void handleMotionEvent(MotionEvent event) { | |
312 synchronized (mLock) { | |
313 mTimestamp = event.getEventTime(); | |
314 for (int i = 0; i < mAxes.length; i++) { | |
315 int axis = mAxes[i]; | |
316 mAxisValues[axis] = event.getAxisValue(axis); | |
317 } | |
318 } | |
319 } | |
320 | |
321 // Called on UI thread. | |
322 void handleKeyEvent(int keyCode, boolean isDown, long timestamp) { | |
323 synchronized (mLock) { | |
324 mTimestamp = timestamp; | |
325 assert keyCode < 256; | |
326 mButtonsPressedStates[keyCode] = isDown; | |
327 } | |
328 } | |
329 | |
330 // Called on polling thread. | |
331 WebGamepadData produceWebData() { | |
332 synchronized (mLock) { | |
333 return mMapper.map(mAxisValues, mButtonsPressedStates); | |
334 } | |
335 } | |
336 } | |
337 | |
338 private native void nativeRefreshDevice(long nativeGamepadPlatformDataFetche rAndroid, | |
339 int index, boolean connected, String id, String mapping, long timest amp, | |
340 float[] axes, float[] buttons); | |
341 } | |
OLD | NEW |