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

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: incorporated 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.util.SparseArray;
10 import android.view.InputDevice;
11 import android.view.InputDevice.MotionRange;
12 import android.view.KeyEvent;
13 import android.view.MotionEvent;
14
15 import org.chromium.base.CalledByNative;
16 import org.chromium.base.JNINamespace;
17 import org.chromium.base.ThreadUtils;
18
19 import java.util.List;
20
21 @JNINamespace("content")
22 class WebGamepadData {
Ted C 2014/03/07 20:18:09 I'd prefer this to be a separate class as it is re
kbalazs 2014/03/12 00:17:36 You meant a separate file, right? :) Done.
23 public String id;
24 public String mapping;
25 public float[] axes;
26 public float[] buttons;
27 }
28
29 /**
30 * Java counterpart of GamepadPlatformDataFetcherAndroid.
31 * Manages game input devices and feed Gamepad API with input data.
32 * GamepadPlatformDataFetcherAndroid is merely a wrepper around this.
33 * Native callable methods called by GamepadPlatformDataFetcherAndroid on the po ller thread
34 * which is a native thread without a java looper. Events are processed on the U I thread.
35 */
36 @JNINamespace("content")
37 public class GamepadAdapter implements InputManager.InputDeviceListener {
38
39 private static final int NUM_WEB_GAMEPADS = 4;
Ted C 2014/03/07 20:18:09 where did this number come from?
kbalazs 2014/03/07 22:40:22 From the spec :) Also from blink::WebGamepads.
40
41 private static GamepadAdapter instance;
42
43 private final InputManager mInputManager;
44 private InputDeviceHandler[] mDeviceHandlers;
45 private final Object mDeviceHandlersLock = new Object();
46 private boolean mIsFetched;
47 private boolean mIsPaused;
48 private long mNativeDataFetcher;
49
50 public static void initialize(Context context) {
51 if (instance == null)
52 instance = new GamepadAdapter(context);
53 }
54
55 public static GamepadAdapter getInstance() {
Ted C 2014/03/07 20:18:09 Any reason this has to be an instance? We are try
kbalazs 2014/03/07 22:40:22 I'm not sure a 1-1 relation is a really good idea.
Ted C 2014/03/08 01:06:00 You are definitely correct that only a single view
56 assert instance != null;
57 return instance;
58 }
59
60 private GamepadAdapter(Context context) {
61 assert ThreadUtils.runningOnUiThread();
62 mInputManager = (InputManager) context.getSystemService(Context.INPUT_SE RVICE);
boliu 2014/03/10 18:27:04 Can this ever be null (say if webview is in a serv
kbalazs 2014/03/12 00:17:36 Done, added checks.
63 mIsFetched = false;
64 mIsPaused = false;
Ted C 2014/03/08 01:06:00 I just noticed that this is the same calls as resu
65 enumerateDevices();
Ted C 2014/03/07 20:18:09 I would probably call this initializeDevices
kbalazs 2014/03/12 00:17:36 Done.
66 mInputManager.registerInputDeviceListener(this, null);
67 }
68
69 public void pause() {
Ted C 2014/03/07 20:18:09 For all non-private methods, you should have javad
kbalazs 2014/03/07 22:40:22 You are right in that if there is more than one ac
Ted C 2014/03/08 01:06:00 Relying on pause and resume is somewhat dangerous
boliu 2014/03/10 18:27:04 What Ted said. Webview is a view, so Activity lif
kbalazs 2014/03/12 00:17:36 javadoc added.
kbalazs 2014/03/12 00:17:36 Relying on onAttachedToWindow and onDetachedFromWi
70 if (mIsPaused)
Ted C 2014/03/07 20:18:09 in java, braces are always required if the stateme
kbalazs 2014/03/12 00:17:36 Done.
71 return;
72 mIsPaused = true;
73 synchronized (mDeviceHandlersLock) {
74 for (int i = 0; i < NUM_WEB_GAMEPADS; ++i) {
Ted C 2014/03/07 20:18:09 in java land, we use i++ (applies many places)
kbalazs 2014/03/12 00:17:36 Done.
75 mDeviceHandlers[i] = null;
76 }
77 }
78 mInputManager.unregisterInputDeviceListener(this);
79 }
80
81 public void resume() {
82 if (!mIsPaused)
83 return;
84 mIsPaused = false;
85 enumerateDevices();
86 mInputManager.registerInputDeviceListener(this, null);
87 }
88
89 private void enumerateDevices() {
90 InputDeviceHandler[] handlers = new InputDeviceHandler[NUM_WEB_GAMEPADS] ;
Ted C 2014/03/07 20:18:09 I would add the assert runningOnUiThread to here t
kbalazs 2014/03/12 00:17:36 Done.
91 int[] ids = mInputManager.getInputDeviceIds();
Ted C 2014/03/07 20:18:09 the api doesn't clarify if null is a valid return
kbalazs 2014/03/12 00:17:36 Done.
92 int activeDevices = 0;
93 for (int i = 0; i < ids.length && activeDevices < NUM_WEB_GAMEPADS; ++i) {
94 InputDevice device = mInputManager.getInputDevice(ids[i]);
95 if (isGameDevice(device)) {
96 handlers[activeDevices++] = new InputDeviceHandler(device);
97 }
98 }
99 synchronized (mDeviceHandlersLock) {
100 mDeviceHandlers = handlers;
101 }
102 }
103
104 @Override
105 public void onInputDeviceAdded(int deviceID) {
Ted C 2014/03/07 20:18:09 I think you're supposed to use a lower d in ID. h
kbalazs 2014/03/12 00:17:36 Done.
106 ThreadUtils.assertOnUiThread();
107 InputDevice device = mInputManager.getInputDevice(deviceID);
108 if (!isGameDevice(device))
109 return;
110 int index = nextAvailableIndex();
111 if (index == -1)
112 return;
113 synchronized (mDeviceHandlersLock) {
114 mDeviceHandlers[index] = new InputDeviceHandler(device);
115 }
116 }
117
118 @Override
119 public void onInputDeviceRemoved(int deviceID) {
120 ThreadUtils.assertOnUiThread();
121 int index = indexForDeviceID(deviceID);
122 if (index == -1)
123 return;
124 synchronized (mDeviceHandlersLock) {
125 mDeviceHandlers[index] = null;
126 }
127 }
128
129 @Override
130 public void onInputDeviceChanged(int deviceID) {
131 ThreadUtils.assertOnUiThread();
132 int index = indexForDeviceID(deviceID);
133 if (index == -1) {
134 index = nextAvailableIndex();
135 if (index == -1) return;
136 }
137 InputDevice device = mInputManager.getInputDevice(deviceID);
138 synchronized (mDeviceHandlersLock) {
139 mDeviceHandlers[index] = null;
140 if (isGameDevice(device)) {
141 mDeviceHandlers[index] = new InputDeviceHandler(device);
142 }
143 }
144 }
145
146 public boolean handleMotionEvent(MotionEvent event) {
147 if (!mIsFetched)
148 return false;
149 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId());
150 if (handler == null)
151 return false;
152 if (!isGameEvent(event))
153 return false;
154 handler.handleMotionEvent(event);
155 return true;
156 }
157
158 public boolean handleKeyEvent(KeyEvent event) {
159 if (!mIsFetched)
160 return false;
161 if (event.getAction() != KeyEvent.ACTION_DOWN &&
162 event.getAction() != KeyEvent.ACTION_UP) {
Ted C 2014/03/07 20:18:09 +4 indent
kbalazs 2014/03/07 22:40:22 This is apparently 4, could you elaborate on how t
Ted C 2014/03/08 01:06:00 +4 indent was meant to say it needs to be indented
kbalazs 2014/03/11 20:38:34 I see. For the record I don't find this rule neith
Ted C 2014/03/11 20:42:47 It's in the referenced android style guide: http:/
kbalazs 2014/03/12 00:17:36 Ah, ok, I missed it. Done.
163 return false;
164 }
165 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId());
166 if (handler == null)
167 return false;
168 int keyCode = event.getKeyCode();
169 if (!isGameKey(keyCode))
170 return false;
171 boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
172 handler.handleKeyEvent(keyCode, isDown, event.getEventTime());
173 return true;
174 }
175
176 @CalledByNative
177 public static GamepadAdapter attach(long nativeDataFetcher) {
Ted C 2014/03/07 20:18:09 why public?
kbalazs 2014/03/07 22:40:22 If I remember correctly the jni generator only all
Ted C 2014/03/08 01:06:00 Ah, yeah the jni generator definitely allows you t
kbalazs 2014/03/12 00:17:36 Now I remember, the presubmit complained about pri
178 assert instance != null;
179 instance.mNativeDataFetcher = nativeDataFetcher;
180 instance.mIsFetched = true;
181 return instance;
jdduke (slow) 2014/03/07 23:33:27 Could we rename mIsFetched to something like |mHas
kbalazs 2014/03/12 00:17:36 Done.
182 }
183
184 // Called on polling thread.
185 @CalledByNative
186 private void getGamepadData() {
187 synchronized (mDeviceHandlersLock) {
188 for (int i = 0; i < NUM_WEB_GAMEPADS; ++i) {
189 if (mDeviceHandlers[i] == null) {
190 nativeRefreshDevice(mNativeDataFetcher, i, false, null, null , 0, null, null);
191 } else {
192 InputDeviceHandler handler = mDeviceHandlers[i];
193 WebGamepadData data = handler.produceWebData();
194 nativeRefreshDevice(mNativeDataFetcher, i, true,
195 data.id, data.mapping, handler.getTimestamp(),
196 data.axes, data.buttons);
197 }
198 }
199 }
200 }
201
202 @CalledByNative
203 public void setFetched(boolean fetched) {
204 mIsFetched = fetched;
205 }
206
207 int nextAvailableIndex() {
208 for (int i = 0; i < mDeviceHandlers.length; ++i) {
209 if (mDeviceHandlers[i] == null)
210 return i;
211 }
212 return -1;
213 }
214
215 int indexForDeviceID(int deviceID) {
Ted C 2014/03/07 20:18:09 indexForDeviceId
216 for (int i = 0; i < mDeviceHandlers.length; ++i) {
217 if (mDeviceHandlers[i] != null &&
218 mDeviceHandlers[i].getInputDevice().getId() == deviceID)
Ted C 2014/03/07 20:18:09 +4 indent and needs braces
219 return i;
220 }
221 return -1;
222 }
223
224 InputDeviceHandler handlerForDeviceId(int deviceID) {
225 int index = indexForDeviceID(deviceID);
226 return index == -1 ? null : mDeviceHandlers[index];
227 }
228
229 private static boolean isGameDevice(InputDevice device) {
230 return (device.getSources() & InputDevice.SOURCE_JOYSTICK) != 0;
231 }
232
233 private static boolean isGameEvent(MotionEvent event) {
234 return (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0 &&
235 event.getAction() == MotionEvent.ACTION_MOVE;
236 }
237
238 private static boolean isGameKey(int keyCode) {
239 switch (keyCode) {
240 case KeyEvent.KEYCODE_DPAD_UP:
241 case KeyEvent.KEYCODE_DPAD_DOWN:
242 case KeyEvent.KEYCODE_DPAD_LEFT:
243 case KeyEvent.KEYCODE_DPAD_RIGHT:
244 case KeyEvent.KEYCODE_DPAD_CENTER:
245 return true;
246 default:
247 return KeyEvent.isGamepadButton(keyCode);
248 }
249 }
250
251 private static class InputDeviceHandler {
252 private final InputDevice mDevice;
253 private final SparseArray<Float> mAxes;
254 private final SparseArray<Boolean> mButtons = new SparseArray<Boolean>() ;
Ted C 2014/03/07 20:18:09 Should be able to use SparseBooleanArray here. I
kbalazs 2014/03/12 00:17:36 I still wanted something like an associative array
255 private final GamepadDataMapper mMapper;
256 private long mTimestamp;
257 private final Object mLock = new Object();
258
259 InputDevice getInputDevice() { return mDevice; }
260 long getTimestamp() { return mTimestamp; }
261
262 public InputDeviceHandler(InputDevice device) {
263 assert isGameDevice(device);
264 mDevice = device;
265 mMapper = GamepadDataMapper.createDataMapper(device.getName());
266 List<MotionRange> ranges = device.getMotionRanges();
267 mAxes = new SparseArray<Float>(ranges.size());
268 for (MotionRange range : ranges) {
269 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
270 mAxes.put(range.getAxis(), new Float(0));
271 }
272 }
273 }
274
275 // Called on UI thread.
276 void handleMotionEvent(MotionEvent event) {
277 synchronized (mLock) {
278 mTimestamp = event.getEventTime();
279 for (int i = 0; i < mAxes.size(); ++i) {
280 final float value = event.getAxisValue(mAxes.keyAt(i));
281 mAxes.setValueAt(i, value);
Ted C 2014/03/07 20:18:09 Hmm...this will result in a new capital Float obje
kbalazs 2014/03/07 22:40:22 IMHO an associative array is quite convenient here
jdduke (slow) 2014/03/07 23:33:27 Any amount of garbage created per frame is undesir
282 }
283 }
284 }
285
286 // Called on UI thread.
287 void handleKeyEvent(int keyCode, boolean isDown, long timestamp) {
288 synchronized (mLock) {
289 mTimestamp = timestamp;
290 mButtons.put(keyCode, isDown);
291 }
292 }
293
294 // Called on polling thread.
295 WebGamepadData produceWebData() {
296 synchronized (mLock) {
297 return mMapper.map(mAxes, mButtons);
Ted C 2014/03/07 20:18:09 Is this also called every often? It looks like th
298 }
299 }
300 }
301
302 private native void nativeRefreshDevice(long nativeGamepadPlatformDataFetche rAndroid,
303 int index, boolean connected, String id, String mapping, long timest amp,
304 float[] axes, float[] buttons);
305 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698