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.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 } | |
OLD | NEW |