Chromium Code Reviews| 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.List; | |
| 19 | |
| 20 /** | |
| 21 * Java counterpart of GamepadPlatformDataFetcherAndroid. | |
| 22 * Manages game input devices and feed Gamepad API with input data. | |
| 23 * GamepadPlatformDataFetcherAndroid is merely a wrepper around this. | |
| 24 * Native callable methods called by GamepadPlatformDataFetcherAndroid on the po ller thread | |
| 25 * which is a native thread without a java looper. Events are processed on the U I thread. | |
| 26 */ | |
| 27 @JNINamespace("content") | |
| 28 public class GamepadAdapter implements InputManager.InputDeviceListener { | |
| 29 | |
| 30 private static final int NUM_WEB_GAMEPADS = 4; | |
| 31 | |
| 32 private static GamepadAdapter instance; | |
| 33 | |
| 34 private InputManager mInputManager; | |
| 35 private InputDeviceHandler[] mDeviceHandlers; | |
| 36 private final Object mDeviceHandlersLock = new Object(); | |
| 37 private boolean mDataRequested; | |
| 38 private boolean mIsPaused; | |
| 39 private long mNativeDataFetcher; | |
| 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 if (mInputManager != null) { | |
|
boliu
2014/03/12 00:58:46
You'd need to null check everywhere mInputManager
kbalazs
2014/03/12 23:33:50
Done.
| |
| 62 initializeDevices(); | |
| 63 mInputManager.registerInputDeviceListener(this, null); | |
| 64 } | |
| 65 } | |
| 66 } | |
| 67 | |
| 68 private static boolean isAttached() { | |
| 69 return instance != null && instance.mAttachedToWindowCounter > 0; | |
| 70 } | |
| 71 | |
| 72 /** | |
| 73 * Notifies GamepadAdapter that a {@link ContentView} is detached from it's window. | |
| 74 */ | |
| 75 public static void onDetachedFromWindow() { | |
| 76 assert ThreadUtils.runningOnUiThread(); | |
| 77 assert isAttached(); | |
| 78 instance.detachedFromWindow(); | |
| 79 } | |
| 80 | |
| 81 private void detachedFromWindow() { | |
| 82 if (--mAttachedToWindowCounter == 0) { | |
| 83 synchronized (mDeviceHandlersLock) { | |
| 84 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) { | |
| 85 mDeviceHandlers[i] = null; | |
| 86 } | |
| 87 } | |
| 88 mInputManager.unregisterInputDeviceListener(this); | |
| 89 mInputManager = null; | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 private void initializeDevices() { | |
| 94 assert ThreadUtils.runningOnUiThread(); | |
| 95 InputDeviceHandler[] handlers = new InputDeviceHandler[NUM_WEB_GAMEPADS] ; | |
| 96 int[] ids = mInputManager.getInputDeviceIds(); | |
| 97 if (ids == null) return; | |
| 98 | |
| 99 int activeDevices = 0; | |
| 100 for (int i = 0; i < ids.length && activeDevices < NUM_WEB_GAMEPADS; i++) { | |
| 101 InputDevice device = mInputManager.getInputDevice(ids[i]); | |
| 102 if (isGameDevice(device)) { | |
| 103 handlers[activeDevices++] = new InputDeviceHandler(device); | |
| 104 } | |
| 105 } | |
| 106 synchronized (mDeviceHandlersLock) { | |
| 107 mDeviceHandlers = handlers; | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 // --------------------------------------------------- | |
| 112 // Implementation of InputManager.InputDeviceListener. | |
| 113 @Override | |
| 114 public void onInputDeviceAdded(int deviceId) { | |
| 115 ThreadUtils.assertOnUiThread(); | |
| 116 InputDevice device = mInputManager.getInputDevice(deviceId); | |
| 117 if (!isGameDevice(device)) | |
| 118 return; | |
| 119 int index = nextAvailableIndex(); | |
| 120 if (index == -1) | |
| 121 return; | |
| 122 synchronized (mDeviceHandlersLock) { | |
| 123 mDeviceHandlers[index] = new InputDeviceHandler(device); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 @Override | |
| 128 public void onInputDeviceRemoved(int deviceId) { | |
| 129 ThreadUtils.assertOnUiThread(); | |
| 130 int index = indexForDeviceId(deviceId); | |
| 131 if (index == -1) | |
| 132 return; | |
| 133 synchronized (mDeviceHandlersLock) { | |
| 134 mDeviceHandlers[index] = null; | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 @Override | |
| 139 public void onInputDeviceChanged(int deviceId) { | |
| 140 ThreadUtils.assertOnUiThread(); | |
| 141 int index = indexForDeviceId(deviceId); | |
| 142 if (index == -1) { | |
| 143 index = nextAvailableIndex(); | |
| 144 if (index == -1) return; | |
| 145 } | |
| 146 InputDevice device = mInputManager.getInputDevice(deviceId); | |
| 147 synchronized (mDeviceHandlersLock) { | |
| 148 mDeviceHandlers[index] = null; | |
| 149 if (isGameDevice(device)) { | |
| 150 mDeviceHandlers[index] = new InputDeviceHandler(device); | |
| 151 } | |
| 152 } | |
| 153 } | |
| 154 // --------------------------------------------------- | |
| 155 | |
| 156 /** | |
| 157 * Handles motion events from gamepad devices. | |
| 158 * | |
| 159 * @return True if the event has been consumed. | |
| 160 */ | |
| 161 public static boolean onMotionEvent(MotionEvent event) { | |
| 162 assert isAttached(); | |
| 163 return instance.handleMotionEvent(event); | |
| 164 } | |
| 165 | |
| 166 private boolean handleMotionEvent(MotionEvent event) { | |
| 167 if (!mDataRequested) return false; | |
|
jdduke (slow)
2014/03/12 01:06:19
I'm a little concerned about the event flow when |
kbalazs
2014/03/12 23:33:50
Good point, trying to handle this now.
| |
| 168 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); | |
| 169 if (handler == null) return false; | |
| 170 if (!isGameEvent(event)) return false; | |
| 171 | |
| 172 handler.handleMotionEvent(event); | |
| 173 return true; | |
| 174 } | |
| 175 | |
| 176 /** | |
| 177 * Handles key events from gamepad devices. | |
| 178 * | |
| 179 * @return True if the event has been consumed. | |
| 180 */ | |
| 181 public static boolean onKeyEvent(KeyEvent event) { | |
| 182 assert isAttached(); | |
| 183 return instance.handleKeyEvent(event); | |
| 184 } | |
| 185 | |
| 186 private boolean handleKeyEvent(KeyEvent event) { | |
| 187 if (!mDataRequested) return false; | |
| 188 if (event.getAction() != KeyEvent.ACTION_DOWN | |
| 189 && event.getAction() != KeyEvent.ACTION_UP) { | |
| 190 return false; | |
| 191 } | |
| 192 InputDeviceHandler handler = handlerForDeviceId(event.getDeviceId()); | |
| 193 if (handler == null) return false; | |
| 194 int keyCode = event.getKeyCode(); | |
| 195 if (!isGameKey(keyCode)) return false; | |
| 196 | |
| 197 boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; | |
| 198 handler.handleKeyEvent(keyCode, isDown, event.getEventTime()); | |
| 199 return true; | |
| 200 } | |
| 201 | |
| 202 @CalledByNative | |
| 203 static void setDataRequested(long nativeDataFetcher, boolean requested) { | |
| 204 initializeInstance(); | |
| 205 instance.mNativeDataFetcher = nativeDataFetcher; | |
|
jdduke (slow)
2014/03/12 01:06:19
Hmm, we should not be caching the |nativeDataFetch
kbalazs
2014/03/12 23:33:50
Done.
| |
| 206 instance.mDataRequested = requested; | |
| 207 } | |
| 208 | |
| 209 // Called on polling thread. | |
| 210 @CalledByNative | |
| 211 static void getGamepadData() { | |
| 212 assert instance != null; | |
| 213 instance.collectGamepadData(); | |
| 214 } | |
| 215 | |
| 216 private void collectGamepadData() { | |
| 217 assert mNativeDataFetcher != 0; | |
| 218 mDataRequested = true; | |
|
jdduke (slow)
2014/03/12 01:06:19
This variable is accessed on the UI thread, it sho
kbalazs
2014/03/12 23:33:50
There are in fact a race with the UI thread about
| |
| 219 synchronized (mDeviceHandlersLock) { | |
| 220 for (int i = 0; i < NUM_WEB_GAMEPADS; i++) { | |
| 221 if (mDeviceHandlers[i] == null) { | |
|
jdduke (slow)
2014/03/12 01:06:19
Just so I understand this correctly, if |collectGa
kbalazs
2014/03/12 23:33:50
Done.
| |
| 222 nativeRefreshDevice(mNativeDataFetcher, i, false, null, null , 0, null, null); | |
| 223 } else { | |
| 224 InputDeviceHandler handler = mDeviceHandlers[i]; | |
| 225 WebGamepadData data = handler.produceWebData(); | |
| 226 nativeRefreshDevice(mNativeDataFetcher, i, true, data.id, da ta.mapping, | |
| 227 handler.getTimestamp(), data.axes, data.buttons); | |
| 228 } | |
| 229 } | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 private int nextAvailableIndex() { | |
| 234 for (int i = 0; i < mDeviceHandlers.length; i++) { | |
| 235 if (mDeviceHandlers[i] == null) | |
| 236 return i; | |
| 237 } | |
| 238 return -1; | |
| 239 } | |
| 240 | |
| 241 private int indexForDeviceId(int deviceId) { | |
| 242 for (int i = 0; i < mDeviceHandlers.length; i++) { | |
| 243 if (mDeviceHandlers[i] != null && | |
| 244 mDeviceHandlers[i].getInputDevice().getId() == deviceId) | |
| 245 return i; | |
| 246 } | |
| 247 return -1; | |
| 248 } | |
| 249 | |
| 250 private InputDeviceHandler handlerForDeviceId(int deviceId) { | |
| 251 int index = indexForDeviceId(deviceId); | |
| 252 return index == -1 ? null : mDeviceHandlers[index]; | |
| 253 } | |
| 254 | |
| 255 private static boolean isGameDevice(InputDevice device) { | |
| 256 return (device.getSources() & InputDevice.SOURCE_JOYSTICK) != 0; | |
| 257 } | |
| 258 | |
| 259 private static boolean isGameEvent(MotionEvent event) { | |
| 260 return (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0 | |
| 261 && event.getAction() == MotionEvent.ACTION_MOVE; | |
| 262 } | |
| 263 | |
| 264 private static boolean isGameKey(int keyCode) { | |
| 265 switch (keyCode) { | |
| 266 case KeyEvent.KEYCODE_DPAD_UP: | |
| 267 case KeyEvent.KEYCODE_DPAD_DOWN: | |
| 268 case KeyEvent.KEYCODE_DPAD_LEFT: | |
| 269 case KeyEvent.KEYCODE_DPAD_RIGHT: | |
| 270 case KeyEvent.KEYCODE_DPAD_CENTER: | |
| 271 return true; | |
| 272 default: | |
| 273 return KeyEvent.isGamepadButton(keyCode); | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 private static class InputDeviceHandler { | |
| 278 private final InputDevice mDevice; | |
| 279 private long mTimestamp; | |
| 280 private final int[] mAxes; | |
| 281 | |
| 282 // Apparently all axis id's and keycodes are less then 256. Given this t he most effective | |
| 283 // representation of an associative array is simply an array. | |
| 284 private final float[] mAxisValues = new float[256]; | |
| 285 private final boolean[] mButtonsPressedStates = new boolean[256]; | |
| 286 | |
| 287 private final GamepadDataMapper mMapper; | |
| 288 private final Object mLock = new Object(); | |
| 289 | |
| 290 InputDevice getInputDevice() { return mDevice; } | |
| 291 long getTimestamp() { return mTimestamp; } | |
| 292 | |
| 293 InputDeviceHandler(InputDevice device) { | |
| 294 assert isGameDevice(device); | |
| 295 mDevice = device; | |
| 296 mMapper = GamepadDataMapper.createDataMapper(device.getName()); | |
| 297 | |
| 298 List<MotionRange> ranges = device.getMotionRanges(); | |
| 299 mAxes = new int[ranges.size()]; | |
| 300 int i = 0; | |
| 301 for (MotionRange range : ranges) { | |
| 302 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) { | |
| 303 int axis = range.getAxis(); | |
| 304 assert axis < 256; | |
| 305 mAxes[i++] = axis; | |
| 306 } | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 // Called on UI thread. | |
| 311 void handleMotionEvent(MotionEvent event) { | |
| 312 synchronized (mLock) { | |
| 313 mTimestamp = event.getEventTime(); | |
| 314 for (int i = 0; i < mAxes.length; i++) { | |
| 315 int axis = mAxes[i]; | |
| 316 mAxisValues[axis] = event.getAxisValue(axis); | |
| 317 } | |
| 318 } | |
| 319 } | |
| 320 | |
| 321 // Called on UI thread. | |
| 322 void handleKeyEvent(int keyCode, boolean isDown, long timestamp) { | |
| 323 synchronized (mLock) { | |
| 324 mTimestamp = timestamp; | |
| 325 assert keyCode < 256; | |
| 326 mButtonsPressedStates[keyCode] = isDown; | |
| 327 } | |
| 328 } | |
| 329 | |
| 330 // Called on polling thread. | |
| 331 WebGamepadData produceWebData() { | |
| 332 synchronized (mLock) { | |
| 333 return mMapper.map(mAxisValues, mButtonsPressedStates); | |
| 334 } | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 private native void nativeRefreshDevice(long nativeGamepadPlatformDataFetche rAndroid, | |
| 339 int index, boolean connected, String id, String mapping, long timest amp, | |
| 340 float[] axes, float[] buttons); | |
| 341 } | |
| OLD | NEW |