Index: content/public/android/java/src/org/chromium/content/browser/input/GamepadAdapter.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/GamepadAdapter.java b/content/public/android/java/src/org/chromium/content/browser/input/GamepadAdapter.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fcf63a692fcf56ccd8fa4a2a1a834871ba59ea07 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/input/GamepadAdapter.java |
@@ -0,0 +1,305 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.content.browser.input; |
+ |
+import android.content.Context; |
+import android.hardware.input.InputManager; |
+import android.util.SparseArray; |
+import android.view.InputDevice; |
+import android.view.InputDevice.MotionRange; |
+import android.view.KeyEvent; |
+import android.view.MotionEvent; |
+ |
+import org.chromium.base.CalledByNative; |
+import org.chromium.base.JNINamespace; |
+import org.chromium.base.ThreadUtils; |
+ |
+import java.util.List; |
+ |
+@JNINamespace("content") |
+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.
|
+ public String id; |
+ public String mapping; |
+ public float[] axes; |
+ public float[] buttons; |
+} |
+ |
+/** |
+ * Java counterpart of GamepadPlatformDataFetcherAndroid. |
+ * Manages game input devices and feed Gamepad API with input data. |
+ * GamepadPlatformDataFetcherAndroid is merely a wrepper around this. |
+ * Native callable methods called by GamepadPlatformDataFetcherAndroid on the poller thread |
+ * which is a native thread without a java looper. Events are processed on the UI thread. |
+ */ |
+@JNINamespace("content") |
+public class GamepadAdapter implements InputManager.InputDeviceListener { |
+ |
+ 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.
|
+ |
+ private static GamepadAdapter instance; |
+ |
+ private final InputManager mInputManager; |
+ private InputDeviceHandler[] mDeviceHandlers; |
+ private final Object mDeviceHandlersLock = new Object(); |
+ private boolean mIsFetched; |
+ private boolean mIsPaused; |
+ private long mNativeDataFetcher; |
+ |
+ public static void initialize(Context context) { |
+ if (instance == null) |
+ instance = new GamepadAdapter(context); |
+ } |
+ |
+ 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
|
+ assert instance != null; |
+ return instance; |
+ } |
+ |
+ private GamepadAdapter(Context context) { |
+ assert ThreadUtils.runningOnUiThread(); |
+ mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); |
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.
|
+ mIsFetched = false; |
+ mIsPaused = false; |
Ted C
2014/03/08 01:06:00
I just noticed that this is the same calls as resu
|
+ enumerateDevices(); |
Ted C
2014/03/07 20:18:09
I would probably call this initializeDevices
kbalazs
2014/03/12 00:17:36
Done.
|
+ mInputManager.registerInputDeviceListener(this, null); |
+ } |
+ |
+ 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
|
+ 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.
|
+ return; |
+ mIsPaused = true; |
+ synchronized (mDeviceHandlersLock) { |
+ 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.
|
+ mDeviceHandlers[i] = null; |
+ } |
+ } |
+ mInputManager.unregisterInputDeviceListener(this); |
+ } |
+ |
+ public void resume() { |
+ if (!mIsPaused) |
+ return; |
+ mIsPaused = false; |
+ enumerateDevices(); |
+ mInputManager.registerInputDeviceListener(this, null); |
+ } |
+ |
+ private void enumerateDevices() { |
+ 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.
|
+ 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.
|
+ int activeDevices = 0; |
+ for (int i = 0; i < ids.length && activeDevices < NUM_WEB_GAMEPADS; ++i) { |
+ InputDevice device = mInputManager.getInputDevice(ids[i]); |
+ if (isGameDevice(device)) { |
+ handlers[activeDevices++] = new InputDeviceHandler(device); |
+ } |
+ } |
+ synchronized (mDeviceHandlersLock) { |
+ mDeviceHandlers = handlers; |
+ } |
+ } |
+ |
+ @Override |
+ 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.
|
+ ThreadUtils.assertOnUiThread(); |
+ InputDevice device = mInputManager.getInputDevice(deviceID); |
+ if (!isGameDevice(device)) |
+ return; |
+ int index = nextAvailableIndex(); |
+ if (index == -1) |
+ return; |
+ synchronized (mDeviceHandlersLock) { |
+ mDeviceHandlers[index] = new InputDeviceHandler(device); |
+ } |
+ } |
+ |
+ @Override |
+ public void onInputDeviceRemoved(int deviceID) { |
+ ThreadUtils.assertOnUiThread(); |
+ int index = indexForDeviceID(deviceID); |
+ if (index == -1) |
+ return; |
+ synchronized (mDeviceHandlersLock) { |
+ mDeviceHandlers[index] = null; |
+ } |
+ } |
+ |
+ @Override |
+ public void onInputDeviceChanged(int deviceID) { |
+ ThreadUtils.assertOnUiThread(); |
+ int index = indexForDeviceID(deviceID); |
+ if (index == -1) { |
+ index = nextAvailableIndex(); |
+ if (index == -1) return; |
+ } |
+ InputDevice device = mInputManager.getInputDevice(deviceID); |
+ synchronized (mDeviceHandlersLock) { |
+ mDeviceHandlers[index] = null; |
+ if (isGameDevice(device)) { |
+ mDeviceHandlers[index] = new InputDeviceHandler(device); |
+ } |
+ } |
+ } |
+ |
+ public boolean handleMotionEvent(MotionEvent event) { |
+ if (!mIsFetched) |
+ return false; |
+ InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); |
+ if (handler == null) |
+ return false; |
+ if (!isGameEvent(event)) |
+ return false; |
+ handler.handleMotionEvent(event); |
+ return true; |
+ } |
+ |
+ public boolean handleKeyEvent(KeyEvent event) { |
+ if (!mIsFetched) |
+ return false; |
+ if (event.getAction() != KeyEvent.ACTION_DOWN && |
+ 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.
|
+ return false; |
+ } |
+ InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); |
+ if (handler == null) |
+ return false; |
+ int keyCode = event.getKeyCode(); |
+ if (!isGameKey(keyCode)) |
+ return false; |
+ boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; |
+ handler.handleKeyEvent(keyCode, isDown, event.getEventTime()); |
+ return true; |
+ } |
+ |
+ @CalledByNative |
+ 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
|
+ assert instance != null; |
+ instance.mNativeDataFetcher = nativeDataFetcher; |
+ instance.mIsFetched = true; |
+ 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.
|
+ } |
+ |
+ // Called on polling thread. |
+ @CalledByNative |
+ private void getGamepadData() { |
+ synchronized (mDeviceHandlersLock) { |
+ for (int i = 0; i < NUM_WEB_GAMEPADS; ++i) { |
+ if (mDeviceHandlers[i] == null) { |
+ nativeRefreshDevice(mNativeDataFetcher, i, false, null, null, 0, null, null); |
+ } else { |
+ InputDeviceHandler handler = mDeviceHandlers[i]; |
+ WebGamepadData data = handler.produceWebData(); |
+ nativeRefreshDevice(mNativeDataFetcher, i, true, |
+ data.id, data.mapping, handler.getTimestamp(), |
+ data.axes, data.buttons); |
+ } |
+ } |
+ } |
+ } |
+ |
+ @CalledByNative |
+ public void setFetched(boolean fetched) { |
+ mIsFetched = fetched; |
+ } |
+ |
+ int nextAvailableIndex() { |
+ for (int i = 0; i < mDeviceHandlers.length; ++i) { |
+ if (mDeviceHandlers[i] == null) |
+ return i; |
+ } |
+ return -1; |
+ } |
+ |
+ int indexForDeviceID(int deviceID) { |
Ted C
2014/03/07 20:18:09
indexForDeviceId
|
+ for (int i = 0; i < mDeviceHandlers.length; ++i) { |
+ if (mDeviceHandlers[i] != null && |
+ mDeviceHandlers[i].getInputDevice().getId() == deviceID) |
Ted C
2014/03/07 20:18:09
+4 indent and needs braces
|
+ return i; |
+ } |
+ return -1; |
+ } |
+ |
+ InputDeviceHandler handlerForDeviceId(int deviceID) { |
+ int index = indexForDeviceID(deviceID); |
+ return index == -1 ? null : mDeviceHandlers[index]; |
+ } |
+ |
+ private static boolean isGameDevice(InputDevice device) { |
+ return (device.getSources() & InputDevice.SOURCE_JOYSTICK) != 0; |
+ } |
+ |
+ private static boolean isGameEvent(MotionEvent event) { |
+ return (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0 && |
+ event.getAction() == MotionEvent.ACTION_MOVE; |
+ } |
+ |
+ private static boolean isGameKey(int keyCode) { |
+ switch (keyCode) { |
+ case KeyEvent.KEYCODE_DPAD_UP: |
+ case KeyEvent.KEYCODE_DPAD_DOWN: |
+ case KeyEvent.KEYCODE_DPAD_LEFT: |
+ case KeyEvent.KEYCODE_DPAD_RIGHT: |
+ case KeyEvent.KEYCODE_DPAD_CENTER: |
+ return true; |
+ default: |
+ return KeyEvent.isGamepadButton(keyCode); |
+ } |
+ } |
+ |
+ private static class InputDeviceHandler { |
+ private final InputDevice mDevice; |
+ private final SparseArray<Float> mAxes; |
+ 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
|
+ private final GamepadDataMapper mMapper; |
+ private long mTimestamp; |
+ private final Object mLock = new Object(); |
+ |
+ InputDevice getInputDevice() { return mDevice; } |
+ long getTimestamp() { return mTimestamp; } |
+ |
+ public InputDeviceHandler(InputDevice device) { |
+ assert isGameDevice(device); |
+ mDevice = device; |
+ mMapper = GamepadDataMapper.createDataMapper(device.getName()); |
+ List<MotionRange> ranges = device.getMotionRanges(); |
+ mAxes = new SparseArray<Float>(ranges.size()); |
+ for (MotionRange range : ranges) { |
+ if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { |
+ mAxes.put(range.getAxis(), new Float(0)); |
+ } |
+ } |
+ } |
+ |
+ // Called on UI thread. |
+ void handleMotionEvent(MotionEvent event) { |
+ synchronized (mLock) { |
+ mTimestamp = event.getEventTime(); |
+ for (int i = 0; i < mAxes.size(); ++i) { |
+ final float value = event.getAxisValue(mAxes.keyAt(i)); |
+ 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
|
+ } |
+ } |
+ } |
+ |
+ // Called on UI thread. |
+ void handleKeyEvent(int keyCode, boolean isDown, long timestamp) { |
+ synchronized (mLock) { |
+ mTimestamp = timestamp; |
+ mButtons.put(keyCode, isDown); |
+ } |
+ } |
+ |
+ // Called on polling thread. |
+ WebGamepadData produceWebData() { |
+ synchronized (mLock) { |
+ return mMapper.map(mAxes, mButtons); |
Ted C
2014/03/07 20:18:09
Is this also called every often? It looks like th
|
+ } |
+ } |
+ } |
+ |
+ private native void nativeRefreshDevice(long nativeGamepadPlatformDataFetcherAndroid, |
+ int index, boolean connected, String id, String mapping, long timestamp, |
+ float[] axes, float[] buttons); |
+} |