Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(101)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/GamepadAdapter.java

Issue 165483003: Gamepad API for Android (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix forgotten things Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698