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.view.InputDevice; |
| 10 import android.view.InputDevice.MotionRange; |
| 11 import android.view.KeyEvent; |
| 12 import android.view.MotionEvent; |
| 13 |
| 14 import org.chromium.base.CalledByNative; |
| 15 import org.chromium.base.JNINamespace; |
| 16 import org.chromium.base.ThreadUtils; |
| 17 |
| 18 import java.util.Arrays; |
| 19 import java.util.List; |
| 20 |
| 21 /** |
| 22 * Java counterpart of GamepadPlatformDataFetcherAndroid. |
| 23 * Manages game input devices and feed Gamepad API with input data. |
| 24 * GamepadPlatformDataFetcherAndroid is merely a wrepper around this. |
| 25 * Native callable methods called by GamepadPlatformDataFetcherAndroid on the po
ller thread |
| 26 * which is a native thread without a java looper. Events are processed on the U
I thread. |
| 27 */ |
| 28 @JNINamespace("content") |
| 29 public class GamepadAdapter implements InputManager.InputDeviceListener { |
| 30 |
| 31 private static final int NUM_WEB_GAMEPADS = 4; |
| 32 |
| 33 private static GamepadAdapter instance; |
| 34 |
| 35 private InputManager mInputManager; |
| 36 private InputDeviceHandler[] mDeviceHandlers; |
| 37 private final Object mDeviceHandlersLock = new Object(); |
| 38 private boolean mDataRequested; |
| 39 private boolean mIsPaused; |
| 40 private int mAttachedToWindowCounter; |
| 41 |
| 42 private static void initializeInstance() { |
| 43 if (instance == null) { |
| 44 instance = new GamepadAdapter(); |
| 45 } |
| 46 } |
| 47 |
| 48 /** |
| 49 * Notifies GamepadAdapter that a {@link ContentView} is attached to a windo
w so it should |
| 50 * be prepared for input. Must be called before {@link onMotionEvent} or {@l
ink onKeyEvent}. |
| 51 */ |
| 52 public static void onAttachedToWindow(Context context) { |
| 53 assert ThreadUtils.runningOnUiThread(); |
| 54 initializeInstance(); |
| 55 instance.attachedToWindow(context); |
| 56 } |
| 57 |
| 58 private void attachedToWindow(Context context) { |
| 59 if (mAttachedToWindowCounter++ == 0) { |
| 60 mInputManager = (InputManager) context.getSystemService(Context.INPU
T_SERVICE); |
| 61 initializeDevices(); |
| 62 mInputManager.registerInputDeviceListener(this, null); |
| 63 } |
| 64 } |
| 65 |
| 66 private static boolean isAttached() { |
| 67 return instance != null && instance.mAttachedToWindowCounter > 0; |
| 68 } |
| 69 |
| 70 /** |
| 71 * Notifies GamepadAdapter that a {@link ContentView} is detached from it's
window. |
| 72 */ |
| 73 public static void onDetachedFromWindow() { |
| 74 assert ThreadUtils.runningOnUiThread(); |
| 75 assert isAttached(); |
| 76 instance.detachedFromWindow(); |
| 77 } |
| 78 |
| 79 private void detachedFromWindow() { |
| 80 if (--mAttachedToWindowCounter == 0) { |
| 81 synchronized (mDeviceHandlersLock) { |
| 82 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) { |
| 83 mDeviceHandlers[i] = null; |
| 84 } |
| 85 } |
| 86 mInputManager.unregisterInputDeviceListener(this); |
| 87 mInputManager = null; |
| 88 } |
| 89 } |
| 90 |
| 91 private void initializeDevices() { |
| 92 assert ThreadUtils.runningOnUiThread(); |
| 93 InputDeviceHandler[] handlers = new InputDeviceHandler[NUM_WEB_GAMEPADS]
; |
| 94 int[] ids = mInputManager.getInputDeviceIds(); |
| 95 if (ids == null) return; |
| 96 |
| 97 int activeDevices = 0; |
| 98 for (int i = 0; i < ids.length && activeDevices < NUM_WEB_GAMEPADS; i++)
{ |
| 99 InputDevice device = mInputManager.getInputDevice(ids[i]); |
| 100 if (isGameDevice(device)) { |
| 101 handlers[activeDevices++] = new InputDeviceHandler(device); |
| 102 } |
| 103 } |
| 104 synchronized (mDeviceHandlersLock) { |
| 105 mDeviceHandlers = handlers; |
| 106 } |
| 107 } |
| 108 |
| 109 // --------------------------------------------------- |
| 110 // Implementation of InputManager.InputDeviceListener. |
| 111 @Override |
| 112 public void onInputDeviceAdded(int deviceId) { |
| 113 ThreadUtils.assertOnUiThread(); |
| 114 InputDevice device = mInputManager.getInputDevice(deviceId); |
| 115 if (!isGameDevice(device)) |
| 116 return; |
| 117 int index = nextAvailableIndex(); |
| 118 if (index == -1) |
| 119 return; |
| 120 synchronized (mDeviceHandlersLock) { |
| 121 mDeviceHandlers[index] = new InputDeviceHandler(device); |
| 122 } |
| 123 } |
| 124 |
| 125 @Override |
| 126 public void onInputDeviceRemoved(int deviceId) { |
| 127 ThreadUtils.assertOnUiThread(); |
| 128 int index = indexForDeviceId(deviceId); |
| 129 if (index == -1) |
| 130 return; |
| 131 synchronized (mDeviceHandlersLock) { |
| 132 mDeviceHandlers[index] = null; |
| 133 } |
| 134 } |
| 135 |
| 136 @Override |
| 137 public void onInputDeviceChanged(int deviceId) { |
| 138 ThreadUtils.assertOnUiThread(); |
| 139 int index = indexForDeviceId(deviceId); |
| 140 if (index == -1) { |
| 141 index = nextAvailableIndex(); |
| 142 if (index == -1) return; |
| 143 } |
| 144 InputDevice device = mInputManager.getInputDevice(deviceId); |
| 145 synchronized (mDeviceHandlersLock) { |
| 146 mDeviceHandlers[index] = null; |
| 147 if (isGameDevice(device)) { |
| 148 mDeviceHandlers[index] = new InputDeviceHandler(device); |
| 149 } |
| 150 } |
| 151 } |
| 152 // --------------------------------------------------- |
| 153 |
| 154 /** |
| 155 * Handles motion events from gamepad devices. |
| 156 * |
| 157 * @return True if the event has been consumed. |
| 158 */ |
| 159 public static boolean onMotionEvent(MotionEvent event) { |
| 160 assert isAttached(); |
| 161 return instance.handleMotionEvent(event); |
| 162 } |
| 163 |
| 164 private boolean handleMotionEvent(MotionEvent event) { |
| 165 if (!mDataRequested) return false; |
| 166 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); |
| 167 if (handler == null) return false; |
| 168 if (!isGameEvent(event)) return false; |
| 169 |
| 170 handler.handleMotionEvent(event); |
| 171 return true; |
| 172 } |
| 173 |
| 174 /** |
| 175 * Handles key events from gamepad devices. |
| 176 * |
| 177 * @return True if the event has been consumed. |
| 178 */ |
| 179 public static boolean onKeyEvent(KeyEvent event) { |
| 180 assert isAttached(); |
| 181 return instance.handleKeyEvent(event); |
| 182 } |
| 183 |
| 184 private boolean handleKeyEvent(KeyEvent event) { |
| 185 if (!mDataRequested) return false; |
| 186 if (event.getAction() != KeyEvent.ACTION_DOWN |
| 187 && event.getAction() != KeyEvent.ACTION_UP) { |
| 188 return false; |
| 189 } |
| 190 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); |
| 191 if (handler == null) return false; |
| 192 int keyCode = event.getKeyCode(); |
| 193 if (!isGameKey(keyCode)) return false; |
| 194 |
| 195 boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; |
| 196 handler.handleKeyEvent(keyCode, isDown, event.getEventTime()); |
| 197 return true; |
| 198 } |
| 199 |
| 200 @CalledByNative |
| 201 static void setDataRequested(boolean requested) { |
| 202 initializeInstance(); |
| 203 instance.mDataRequested = requested; |
| 204 } |
| 205 |
| 206 // Called on polling thread. |
| 207 @CalledByNative |
| 208 static void getGamepadData(long gamepads) { |
| 209 assert instance != null; |
| 210 instance.reportGamepadData(gamepads); |
| 211 } |
| 212 |
| 213 private void reportGamepadData(long gamepads) { |
| 214 if (!mDataRequested) { |
| 215 // Clear input to avoid anomalies because we don't watch events when
data is not |
| 216 // requested. We can miss the release of a button and falsely report
that it's pushed |
| 217 // when data is requested again. We do this here after setting mData
Requested to |
| 218 // true instead in setDataRequested when setting it to false because
this |
| 219 // way no locking is needed. This can race with onMotionEvent or onK
eyEvent |
| 220 // but it couldn't result in inconsistent data. |
| 221 mDataRequested = true; |
| 222 clearInput(); |
| 223 } |
| 224 |
| 225 synchronized (mDeviceHandlersLock) { |
| 226 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) { |
| 227 if (mDeviceHandlers[i] == null) { |
| 228 nativeRefreshGamepad(gamepads, i, false, null, null, 0, null
, null); |
| 229 } else { |
| 230 InputDeviceHandler handler = mDeviceHandlers[i]; |
| 231 WebGamepadData data = handler.produceWebData(); |
| 232 nativeRefreshGamepad(gamepads, i, true, data.id, data.mappin
g, |
| 233 handler.getTimestamp(), data.axes, data.buttons); |
| 234 } |
| 235 } |
| 236 } |
| 237 } |
| 238 |
| 239 void clearInput() { |
| 240 for (int i = 0; i < mDeviceHandlers.length; i++) { |
| 241 if (mDeviceHandlers[i] != null) { |
| 242 mDeviceHandlers[i].clearInput(); |
| 243 } |
| 244 } |
| 245 } |
| 246 |
| 247 private int nextAvailableIndex() { |
| 248 for (int i = 0; i < mDeviceHandlers.length; i++) { |
| 249 if (mDeviceHandlers[i] == null) return i; |
| 250 } |
| 251 return -1; |
| 252 } |
| 253 |
| 254 private int indexForDeviceId(int deviceId) { |
| 255 for (int i = 0; i < mDeviceHandlers.length; i++) { |
| 256 if (mDeviceHandlers[i] != null && |
| 257 mDeviceHandlers[i].getInputDevice().getId() == deviceId) |
| 258 return i; |
| 259 } |
| 260 return -1; |
| 261 } |
| 262 |
| 263 private InputDeviceHandler handlerForDeviceId(int deviceId) { |
| 264 int index = indexForDeviceId(deviceId); |
| 265 return index == -1 ? null : mDeviceHandlers[index]; |
| 266 } |
| 267 |
| 268 private static boolean isGameDevice(InputDevice device) { |
| 269 return (device.getSources() & InputDevice.SOURCE_JOYSTICK) != 0; |
| 270 } |
| 271 |
| 272 private static boolean isGameEvent(MotionEvent event) { |
| 273 return (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0 |
| 274 && event.getAction() == MotionEvent.ACTION_MOVE; |
| 275 } |
| 276 |
| 277 private static boolean isGameKey(int keyCode) { |
| 278 switch (keyCode) { |
| 279 case KeyEvent.KEYCODE_DPAD_UP: |
| 280 case KeyEvent.KEYCODE_DPAD_DOWN: |
| 281 case KeyEvent.KEYCODE_DPAD_LEFT: |
| 282 case KeyEvent.KEYCODE_DPAD_RIGHT: |
| 283 case KeyEvent.KEYCODE_DPAD_CENTER: |
| 284 return true; |
| 285 default: |
| 286 return KeyEvent.isGamepadButton(keyCode); |
| 287 } |
| 288 } |
| 289 |
| 290 private static class InputDeviceHandler { |
| 291 private final InputDevice mDevice; |
| 292 private long mTimestamp; |
| 293 private final int[] mAxes; |
| 294 |
| 295 // Apparently all axis id's and keycodes are less then 256. Given this t
he most effective |
| 296 // representation of an associative array is simply an array. |
| 297 private final float[] mAxisValues = new float[256]; |
| 298 private final boolean[] mButtonsPressedStates = new boolean[256]; |
| 299 |
| 300 private final GamepadDataMapper mMapper; |
| 301 private final Object mLock = new Object(); |
| 302 |
| 303 InputDevice getInputDevice() { return mDevice; } |
| 304 long getTimestamp() { return mTimestamp; } |
| 305 |
| 306 InputDeviceHandler(InputDevice device) { |
| 307 assert isGameDevice(device); |
| 308 mDevice = device; |
| 309 mMapper = GamepadDataMapper.createDataMapper(device.getName()); |
| 310 |
| 311 List<MotionRange> ranges = device.getMotionRanges(); |
| 312 mAxes = new int[ranges.size()]; |
| 313 int i = 0; |
| 314 for (MotionRange range : ranges) { |
| 315 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
) { |
| 316 int axis = range.getAxis(); |
| 317 assert axis < 256; |
| 318 mAxes[i++] = axis; |
| 319 } |
| 320 } |
| 321 } |
| 322 |
| 323 // Called on UI thread. |
| 324 void handleMotionEvent(MotionEvent event) { |
| 325 synchronized (mLock) { |
| 326 mTimestamp = event.getEventTime(); |
| 327 for (int i = 0; i < mAxes.length; i++) { |
| 328 int axis = mAxes[i]; |
| 329 mAxisValues[axis] = event.getAxisValue(axis); |
| 330 } |
| 331 } |
| 332 } |
| 333 |
| 334 // Called on UI thread. |
| 335 void handleKeyEvent(int keyCode, boolean isDown, long timestamp) { |
| 336 synchronized (mLock) { |
| 337 mTimestamp = timestamp; |
| 338 assert keyCode < 256; |
| 339 mButtonsPressedStates[keyCode] = isDown; |
| 340 } |
| 341 } |
| 342 |
| 343 // Called on polling thread. |
| 344 WebGamepadData produceWebData() { |
| 345 synchronized (mLock) { |
| 346 return mMapper.map(mAxisValues, mButtonsPressedStates); |
| 347 } |
| 348 } |
| 349 |
| 350 // Called on polling thread. |
| 351 void clearInput() { |
| 352 synchronized (mLock) { |
| 353 Arrays.fill(mAxisValues, 0); |
| 354 Arrays.fill(mButtonsPressedStates, false); |
| 355 } |
| 356 } |
| 357 } |
| 358 |
| 359 private native void nativeRefreshGamepad(long gamepads, int index, boolean c
onnected, |
| 360 String id, String mapping, long timestamp, float[] axes, float[] but
tons); |
| 361 } |
OLD | NEW |