| 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 {
|
| + 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;
|
| +
|
| + 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() {
|
| + assert instance != null;
|
| + return instance;
|
| + }
|
| +
|
| + private GamepadAdapter(Context context) {
|
| + assert ThreadUtils.runningOnUiThread();
|
| + mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
| + mIsFetched = false;
|
| + mIsPaused = false;
|
| + enumerateDevices();
|
| + mInputManager.registerInputDeviceListener(this, null);
|
| + }
|
| +
|
| + public void pause() {
|
| + if (mIsPaused)
|
| + return;
|
| + mIsPaused = true;
|
| + synchronized (mDeviceHandlersLock) {
|
| + for (int i = 0; i < NUM_WEB_GAMEPADS; ++i) {
|
| + 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];
|
| + int[] ids = mInputManager.getInputDeviceIds();
|
| + 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) {
|
| + 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) {
|
| + 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) {
|
| + assert instance != null;
|
| + instance.mNativeDataFetcher = nativeDataFetcher;
|
| + instance.mIsFetched = true;
|
| + return instance;
|
| + }
|
| +
|
| + // 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) {
|
| + for (int i = 0; i < mDeviceHandlers.length; ++i) {
|
| + if (mDeviceHandlers[i] != null &&
|
| + mDeviceHandlers[i].getInputDevice().getId() == deviceID)
|
| + 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>();
|
| + 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);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // 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);
|
| + }
|
| + }
|
| + }
|
| +
|
| + private native void nativeRefreshDevice(long nativeGamepadPlatformDataFetcherAndroid,
|
| + int index, boolean connected, String id, String mapping, long timestamp,
|
| + float[] axes, float[] buttons);
|
| +}
|
|
|