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

Unified 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, 10 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 side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld 408576698