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

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: address comments 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.Arrays;
19 import java.util.List;
20
21 /**
22 * Java counterpart of GamepadPlatformDataFetcherAndroid.
23 * Manages game input devices and feed Gamepad API with input data.
24 * GamepadPlatformDataFetcherAndroid is merely a wrepper around this.
25 * Native callable methods called by GamepadPlatformDataFetcherAndroid on the po ller thread
26 * which is a native thread without a java looper. Events are processed on the U I thread.
27 */
28 @JNINamespace("content")
29 public class GamepadAdapter implements InputManager.InputDeviceListener {
30
31 private static final int NUM_WEB_GAMEPADS = 4;
32
33 private static GamepadAdapter instance;
34
35 private InputManager mInputManager;
36 private InputDeviceHandler[] mDeviceHandlers;
37 private final Object mDeviceHandlersLock = new Object();
38 private boolean mDataRequested;
39 private boolean mIsPaused;
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 initializeDevices();
62 mInputManager.registerInputDeviceListener(this, null);
63 }
64 }
65
66 private static boolean isAttached() {
67 return instance != null && instance.mAttachedToWindowCounter > 0;
68 }
69
70 /**
71 * Notifies GamepadAdapter that a {@link ContentView} is detached from it's window.
72 */
73 public static void onDetachedFromWindow() {
74 assert ThreadUtils.runningOnUiThread();
75 assert isAttached();
76 instance.detachedFromWindow();
77 }
78
79 private void detachedFromWindow() {
80 if (--mAttachedToWindowCounter == 0) {
81 synchronized (mDeviceHandlersLock) {
82 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) {
83 mDeviceHandlers[i] = null;
84 }
85 }
86 mInputManager.unregisterInputDeviceListener(this);
87 mInputManager = null;
88 }
89 }
90
91 private void initializeDevices() {
92 assert ThreadUtils.runningOnUiThread();
93 InputDeviceHandler[] handlers = new InputDeviceHandler[NUM_WEB_GAMEPADS] ;
94 int[] ids = mInputManager.getInputDeviceIds();
95 if (ids == null) return;
96
97 int activeDevices = 0;
98 for (int i = 0; i < ids.length && activeDevices < NUM_WEB_GAMEPADS; i++) {
99 InputDevice device = mInputManager.getInputDevice(ids[i]);
100 if (isGameDevice(device)) {
101 handlers[activeDevices++] = new InputDeviceHandler(device);
102 }
103 }
104 synchronized (mDeviceHandlersLock) {
105 mDeviceHandlers = handlers;
106 }
107 }
108
109 // ---------------------------------------------------
110 // Implementation of InputManager.InputDeviceListener.
111 @Override
112 public void onInputDeviceAdded(int deviceId) {
113 ThreadUtils.assertOnUiThread();
114 InputDevice device = mInputManager.getInputDevice(deviceId);
115 if (!isGameDevice(device))
116 return;
117 int index = nextAvailableIndex();
118 if (index == -1)
119 return;
120 synchronized (mDeviceHandlersLock) {
121 mDeviceHandlers[index] = new InputDeviceHandler(device);
122 }
123 }
124
125 @Override
126 public void onInputDeviceRemoved(int deviceId) {
127 ThreadUtils.assertOnUiThread();
128 int index = indexForDeviceId(deviceId);
129 if (index == -1)
130 return;
131 synchronized (mDeviceHandlersLock) {
132 mDeviceHandlers[index] = null;
133 }
134 }
135
136 @Override
137 public void onInputDeviceChanged(int deviceId) {
138 ThreadUtils.assertOnUiThread();
139 int index = indexForDeviceId(deviceId);
140 if (index == -1) {
141 index = nextAvailableIndex();
142 if (index == -1) return;
143 }
144 InputDevice device = mInputManager.getInputDevice(deviceId);
145 synchronized (mDeviceHandlersLock) {
146 mDeviceHandlers[index] = null;
147 if (isGameDevice(device)) {
148 mDeviceHandlers[index] = new InputDeviceHandler(device);
149 }
150 }
151 }
152 // ---------------------------------------------------
153
154 /**
155 * Handles motion events from gamepad devices.
156 *
157 * @return True if the event has been consumed.
158 */
159 public static boolean onMotionEvent(MotionEvent event) {
160 assert isAttached();
161 return instance.handleMotionEvent(event);
162 }
163
164 private boolean handleMotionEvent(MotionEvent event) {
165 if (!mDataRequested) return false;
166 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId());
167 if (handler == null) return false;
168 if (!isGameEvent(event)) return false;
169
170 handler.handleMotionEvent(event);
171 return true;
172 }
173
174 /**
175 * Handles key events from gamepad devices.
176 *
177 * @return True if the event has been consumed.
178 */
179 public static boolean onKeyEvent(KeyEvent event) {
180 assert isAttached();
181 return instance.handleKeyEvent(event);
182 }
183
184 private boolean handleKeyEvent(KeyEvent event) {
185 if (!mDataRequested) return false;
186 if (event.getAction() != KeyEvent.ACTION_DOWN
187 && event.getAction() != KeyEvent.ACTION_UP) {
188 return false;
189 }
190 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId());
191 if (handler == null) return false;
192 int keyCode = event.getKeyCode();
193 if (!isGameKey(keyCode)) return false;
194
195 boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
196 handler.handleKeyEvent(keyCode, isDown, event.getEventTime());
197 return true;
198 }
199
200 @CalledByNative
201 static void setDataRequested(boolean requested) {
202 initializeInstance();
203 instance.mDataRequested = requested;
204 }
205
206 // Called on polling thread.
207 @CalledByNative
208 static void getGamepadData(long gamepads) {
209 assert instance != null;
210 instance.reportGamepadData(gamepads);
211 }
212
213 private void reportGamepadData(long gamepads) {
214 if (!mDataRequested) {
215 // Clear input to avoid anomalies because we don't watch events when data is not
216 // requested. We can miss the release of a button and falsely report that it's pushed
217 // when data is requested again. We do this here after setting mData Requested to
218 // true instead in setDataRequested when setting it to false because this
219 // way no locking is needed. This can race with onMotionEvent or onK eyEvent
220 // but it couldn't result in inconsistent data.
221 mDataRequested = true;
222 clearInput();
223 }
224
225 synchronized (mDeviceHandlersLock) {
226 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) {
227 if (mDeviceHandlers[i] == null) {
228 nativeRefreshGamepad(gamepads, i, false, null, null, 0, null , null);
229 } else {
230 InputDeviceHandler handler = mDeviceHandlers[i];
231 WebGamepadData data = handler.produceWebData();
232 nativeRefreshGamepad(gamepads, i, true, data.id, data.mappin g,
233 handler.getTimestamp(), data.axes, data.buttons);
234 }
235 }
236 }
237 }
238
239 void clearInput() {
240 for (int i = 0; i < mDeviceHandlers.length; i++) {
241 if (mDeviceHandlers[i] != null) {
242 mDeviceHandlers[i].clearInput();
243 }
244 }
245 }
246
247 private int nextAvailableIndex() {
248 for (int i = 0; i < mDeviceHandlers.length; i++) {
249 if (mDeviceHandlers[i] == null) return i;
250 }
251 return -1;
252 }
253
254 private int indexForDeviceId(int deviceId) {
255 for (int i = 0; i < mDeviceHandlers.length; i++) {
256 if (mDeviceHandlers[i] != null &&
257 mDeviceHandlers[i].getInputDevice().getId() == deviceId)
258 return i;
259 }
260 return -1;
261 }
262
263 private InputDeviceHandler handlerForDeviceId(int deviceId) {
264 int index = indexForDeviceId(deviceId);
265 return index == -1 ? null : mDeviceHandlers[index];
266 }
267
268 private static boolean isGameDevice(InputDevice device) {
269 return (device.getSources() & InputDevice.SOURCE_JOYSTICK) != 0;
270 }
271
272 private static boolean isGameEvent(MotionEvent event) {
273 return (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0
274 && event.getAction() == MotionEvent.ACTION_MOVE;
275 }
276
277 private static boolean isGameKey(int keyCode) {
278 switch (keyCode) {
279 case KeyEvent.KEYCODE_DPAD_UP:
280 case KeyEvent.KEYCODE_DPAD_DOWN:
281 case KeyEvent.KEYCODE_DPAD_LEFT:
282 case KeyEvent.KEYCODE_DPAD_RIGHT:
283 case KeyEvent.KEYCODE_DPAD_CENTER:
284 return true;
285 default:
286 return KeyEvent.isGamepadButton(keyCode);
287 }
288 }
289
290 private static class InputDeviceHandler {
291 private final InputDevice mDevice;
292 private long mTimestamp;
293 private final int[] mAxes;
294
295 // Apparently all axis id's and keycodes are less then 256. Given this t he most effective
296 // representation of an associative array is simply an array.
297 private final float[] mAxisValues = new float[256];
298 private final boolean[] mButtonsPressedStates = new boolean[256];
299
300 private final GamepadDataMapper mMapper;
301 private final Object mLock = new Object();
302
303 InputDevice getInputDevice() { return mDevice; }
304 long getTimestamp() { return mTimestamp; }
305
306 InputDeviceHandler(InputDevice device) {
307 assert isGameDevice(device);
308 mDevice = device;
309 mMapper = GamepadDataMapper.createDataMapper(device.getName());
310
311 List<MotionRange> ranges = device.getMotionRanges();
312 mAxes = new int[ranges.size()];
313 int i = 0;
314 for (MotionRange range : ranges) {
315 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
316 int axis = range.getAxis();
317 assert axis < 256;
318 mAxes[i++] = axis;
319 }
320 }
321 }
322
323 // Called on UI thread.
324 void handleMotionEvent(MotionEvent event) {
325 synchronized (mLock) {
326 mTimestamp = event.getEventTime();
327 for (int i = 0; i < mAxes.length; i++) {
328 int axis = mAxes[i];
329 mAxisValues[axis] = event.getAxisValue(axis);
330 }
331 }
332 }
333
334 // Called on UI thread.
335 void handleKeyEvent(int keyCode, boolean isDown, long timestamp) {
336 synchronized (mLock) {
337 mTimestamp = timestamp;
338 assert keyCode < 256;
339 mButtonsPressedStates[keyCode] = isDown;
340 }
341 }
342
343 // Called on polling thread.
344 WebGamepadData produceWebData() {
345 synchronized (mLock) {
346 return mMapper.map(mAxisValues, mButtonsPressedStates);
347 }
348 }
349
350 // Called on polling thread.
351 void clearInput() {
352 synchronized (mLock) {
353 Arrays.fill(mAxisValues, 0);
354 Arrays.fill(mButtonsPressedStates, false);
355 }
356 }
357 }
358
359 private native void nativeRefreshGamepad(long gamepads, int index, boolean c onnected,
360 String id, String mapping, long timestamp, float[] axes, float[] but tons);
361 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698