Index: content/public/android/java/src/org/chromium/content/browser/GamepadList.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/GamepadList.java b/content/public/android/java/src/org/chromium/content/browser/GamepadList.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..47a89f15ff1c0c5ffcdf694da9f8ab460d95edf8 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/GamepadList.java |
@@ -0,0 +1,319 @@ |
+/* |
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. |
+ * |
+ * Redistribution and use in source and binary forms, with or without |
+ * modification, are permitted provided that the following conditions |
+ * are met: |
+ * * Redistributions of source code must retain the above copyright |
+ * notice, this list of conditions and the following disclaimer. |
+ * * Redistributions in binary form must reproduce the above copyright |
+ * notice, this list of conditions and the following disclaimer in the |
+ * documentation and/or other materials provided with the distribution. |
+ * * Neither the name of NVIDIA CORPORATION nor the names of its |
+ * contributors may be used to endorse or promote products derived |
+ * from this software without specific prior written permission. |
+ * |
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+*/ |
+ |
+package org.chromium.content.browser; |
+ |
+import android.content.Context; |
+import android.hardware.input.InputManager; |
+import android.hardware.input.InputManager.InputDeviceListener; |
+import android.view.InputDevice; |
+import android.view.InputEvent; |
+import android.view.KeyEvent; |
+import android.view.MotionEvent; |
+ |
+import org.chromium.base.CalledByNative; |
+ |
+/* |
+ * Class to manage connected gamepad devices list. |
+ */ |
+class GamepadList implements InputDeviceListener { |
+ private static final int MAX_GAMEPADS = 4; |
+ private GamepadDevice[] mGamepadDevices = new GamepadDevice[MAX_GAMEPADS]; |
+ protected boolean mHaveDevicesBeenInteractedWith = false; |
+ private boolean IS_GAMEPAD_ACCESSED = false; |
+ private InputManager inputManager; |
+ |
+ private GamepadList(Context context) { |
+ |
+ inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); |
+ // Get list of all the attached input devices. |
+ int[] deviceIds = inputManager.getInputDeviceIds(); |
+ |
+ for (int i = 0; i < deviceIds.length; i++) { |
+ InputDevice inputDevice = InputDevice.getDevice(deviceIds[i]); |
+ // Check for gamepad device |
+ if (isGamepadDevice(inputDevice)) { |
+ // Register a new gamepad device. |
+ registerGamepad(inputDevice); |
+ } |
+ } |
+ // Register an input device listener. |
+ inputManager.registerInputDeviceListener(this, null); |
+ } |
+ |
+ private static GamepadList gamepadList = null; |
+ |
+ public boolean haveDevicesBeenInteractedWith() { return mHaveDevicesBeenInteractedWith; } |
+ |
+ // Override InputDeviceListener methods |
+ @Override |
+ public void onInputDeviceChanged(int deviceId) { |
+ } |
+ |
+ @Override |
+ public void onInputDeviceRemoved(int deviceId) { |
+ unregisterGamepad(deviceId); |
+ } |
+ |
+ @Override |
+ public void onInputDeviceAdded(int deviceId) { |
+ InputDevice inputDevice = InputDevice.getDevice(deviceId); |
+ if (isGamepadDevice(inputDevice)) |
+ registerGamepad(inputDevice); |
+ } |
+ |
+ public static GamepadList createInstance(Context context) { |
+ if (gamepadList != null) { |
+ return gamepadList; |
+ } else { |
+ gamepadList = new GamepadList(context); |
+ return gamepadList; |
+ } |
+ } |
+ |
+ @CalledByNative |
+ public static GamepadList getInstance() { |
+ if (gamepadList != null) { |
+ return gamepadList; |
+ } else { |
+ return null; |
+ } |
+ } |
+ |
+ @CalledByNative |
+ private void notifyForGamepadsAccess(boolean isaccesspaused) { |
+ IS_GAMEPAD_ACCESSED = !isaccesspaused; |
+ } |
+ |
+ @CalledByNative |
+ private GamepadList getGamepadList() { |
+ // Gamepads must only appear in the list if they are currently connected to the user agent, |
+ // and at least one device has been interacted with by the user. If no devices have been |
+ // interacted with, devices must not appear in the list to avoid a malicious page from |
+ // fingerprinting the user. |
+ if (haveDevicesBeenInteractedWith()) { |
+ return gamepadList; |
+ } |
+ else { |
+ return null; |
+ } |
+ } |
+ |
+ @CalledByNative |
+ private int getDeviceCount() { |
+ int count = 0; |
+ for (int i = 0; i < getCount(); ++i) { |
+ if (getDevice(i) != null) { |
+ count++; |
+ } |
+ } |
+ return count; |
+ } |
+ |
+ @CalledByNative |
+ private String getDeviceName(int index) { |
+ return getDevice(index).getName(); |
+ } |
+ |
+ @CalledByNative |
+ private long getDeviceTimestamp(int index) { |
+ return getDevice(index).getTimestamp(); |
+ } |
+ |
+ @CalledByNative |
+ private float[] getDeviceAxes(int index) { |
+ return getDevice(index).getAxes(); |
+ } |
+ |
+ @CalledByNative |
+ private float[] getDeviceButtons(int index) { |
+ return getDevice(index).getButtons(); |
+ } |
+ |
+ @CalledByNative |
+ private boolean isDeviceConnected(int index) { |
+ if (index < MAX_GAMEPADS && getDevice(index) != null) { |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ @CalledByNative |
+ private boolean isKnownGamepadLayout(int index) { |
+ return getDevice(index).isKnownGamepadLayout(); |
+ } |
+ |
+ public GamepadDevice getDeviceById(int deviceId) { |
+ for (int i = 0; i < getCount(); ++i) { |
+ GamepadDevice gamepad = mGamepadDevices[i]; |
+ if (gamepad != null && gamepad.getId() == deviceId) { |
+ return gamepad; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ public GamepadDevice getDevice(int index) { |
+ // Maximum 4 Gamepads can be connected at a time starting at index zero. |
+ assert index >= 0 && index <= getCount() - 1; |
+ return mGamepadDevices[index]; |
+ } |
+ |
+ public int getCount() { |
+ return MAX_GAMEPADS; |
+ } |
+ |
+ public boolean updateForEvent(KeyEvent event) { |
+ GamepadDevice gamepad = getGamepadForEvent(event); |
+ if (gamepad != null) { |
+ mHaveDevicesBeenInteractedWith = true; |
+ return gamepad.updateForEvent(event); |
+ } |
+ return false; |
+ } |
+ |
+ public boolean updateForEvent(MotionEvent event) { |
+ GamepadDevice gamepad = getGamepadForEvent(event); |
+ if (gamepad != null) { |
+ mHaveDevicesBeenInteractedWith = true; |
+ return gamepad.updateForEvent(event); |
+ } |
+ return false; |
+ } |
+ |
+ public void onPause() { |
+ inputManager.unregisterInputDeviceListener(this); |
+ } |
+ |
+ public void onResume() { |
+ // Re-initialize gamepad list. |
+ // Check if any registered gamepad is disconnected when webview was in pause state. |
+ for (int i = 0; i < getCount(); ++i) { |
+ if (mGamepadDevices[i] != null) { |
+ InputDevice device = InputDevice.getDevice(mGamepadDevices[i].getId()); |
+ if (device == null) { |
+ // Remove disconnected gamepad. |
+ mGamepadDevices[i] = null; |
+ } |
+ } |
+ } |
+ // Check if any new gamepad is connected when webview was in pause state. |
+ int[] deviceIds = inputManager.getInputDeviceIds(); |
+ for (int i = 0; i < deviceIds.length; ++i) { |
+ InputDevice device = InputDevice.getDevice(deviceIds[i]); |
+ if (isGamepadDevice(device)) { |
+ GamepadDevice gamepad = getDeviceById(deviceIds[i]); |
+ if (gamepad == null) { |
+ // Found unregistered gamepad, could be a new device connected when |
+ // webview was in puase state or disconnected and re-connected same device. |
+ registerGamepad(device); |
+ } |
+ } |
+ } |
+ inputManager.registerInputDeviceListener(this, null); |
+ } |
+ |
+ protected int getNextAvailableIndex() { |
+ // When multiple gamepads are connected to a user agent, indices must be assigned on a |
+ // first-come first-serve basis, starting at zero. If a gamepad is disconnected, previously |
+ // assigned indices must not be reassigned to gamepads that continue to be connected. |
+ // However, if a gamepad is disconnected, and subsequently the same or a different |
+ // gamepad is then connected, index entries must be reused. |
+ |
+ for (int i = 0; i < getCount(); ++i) { |
+ if (getDevice(i) == null) { |
+ return i; |
+ } |
+ } |
+ // Reached maximum gamepads limit. |
+ return -1; |
+ } |
+ |
+ protected boolean registerGamepad(InputDevice inputDevice) { |
+ int index = getNextAvailableIndex(); |
+ if (index == -1) { |
+ return false; // invalid index |
+ } |
+ |
+ GamepadDevice gamepad = new GamepadDevice(index, inputDevice); |
+ mGamepadDevices[index] = gamepad; |
+ return true; |
+ } |
+ |
+ protected void unregisterGamepad(int deviceId) { |
+ GamepadDevice gamepadDevice = getDeviceById(deviceId); |
+ if (gamepadDevice == null) { |
+ return; // Not a registered device. |
+ } |
+ int index = gamepadDevice.getIndex(); |
+ mGamepadDevices[index] = null; |
+ } |
+ |
+ private boolean isGamepadDevice(InputDevice inputDevice) { |
+ int sources = inputDevice.getSources(); |
+ if ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK ) { |
+ //Found gamepad device |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ private GamepadDevice getGamepadForEvent(InputEvent event) { |
+ int deviceId = event.getDeviceId(); |
+ GamepadDevice gamepad = getDeviceById(deviceId); |
+ return gamepad; |
+ } |
+ |
+ public boolean isGamepadAccessed() { |
+ return IS_GAMEPAD_ACCESSED; |
+ } |
+ |
+ public boolean isGamepadEvent(MotionEvent event) { |
+ if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) { |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ // Returns true if event's keycode corresponds to a gamepad key. |
+ // Specific handling for dpad keys is required because |
+ // KeyEvent.isGamepadButton doesn't consider dpad keys. |
+ public boolean isGamepadEvent(KeyEvent event) { |
+ int keyCode = event.getKeyCode(); |
+ switch (keyCode) { |
+ case KeyEvent.KEYCODE_DPAD_UP: |
+ case KeyEvent.KEYCODE_DPAD_DOWN: |
+ case KeyEvent.KEYCODE_DPAD_LEFT: |
+ case KeyEvent.KEYCODE_DPAD_RIGHT: |
+ return true; |
+ default: |
+ return KeyEvent.isGamepadButton(keyCode); |
+ } |
+ } |
+ |
+} |