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.annotation.SuppressLint; | |
8 import android.content.Context; | |
9 import android.hardware.input.InputManager; | |
10 import android.hardware.input.InputManager.InputDeviceListener; | |
11 import android.view.InputDevice; | |
12 import android.view.InputEvent; | |
13 import android.view.KeyEvent; | |
14 import android.view.MotionEvent; | |
15 | |
16 import org.chromium.base.ThreadUtils; | |
17 import org.chromium.base.annotations.CalledByNative; | |
18 import org.chromium.base.annotations.JNINamespace; | |
19 import org.chromium.content.browser.ContentView; | |
20 | |
21 /** | |
22 * Class to manage connected gamepad devices list. | |
23 * | |
24 * It is a Java counterpart of GamepadPlatformDataFetcherAndroid and feeds Gamep
ad API with input | |
25 * data. | |
26 */ | |
27 @JNINamespace("content") | |
28 public class GamepadList { | |
29 private static final int MAX_GAMEPADS = 4; | |
30 | |
31 private final Object mLock = new Object(); | |
32 | |
33 private final GamepadDevice[] mGamepadDevices = new GamepadDevice[MAX_GAMEPA
DS]; | |
34 private InputManager mInputManager; | |
35 private int mAttachedToWindowCounter; | |
36 private boolean mIsGamepadAPIActive; | |
37 private InputDeviceListener mInputDeviceListener; | |
38 | |
39 private GamepadList() { | |
40 mInputDeviceListener = new InputDeviceListener() { | |
41 // Override InputDeviceListener methods | |
42 @Override | |
43 public void onInputDeviceChanged(int deviceId) { | |
44 onInputDeviceChangedImpl(deviceId); | |
45 } | |
46 | |
47 @Override | |
48 public void onInputDeviceRemoved(int deviceId) { | |
49 onInputDeviceRemovedImpl(deviceId); | |
50 } | |
51 | |
52 @Override | |
53 public void onInputDeviceAdded(int deviceId) { | |
54 onInputDeviceAddedImpl(deviceId); | |
55 } | |
56 }; | |
57 } | |
58 | |
59 private void initializeDevices() { | |
60 // Get list of all the attached input devices. | |
61 int[] deviceIds = mInputManager.getInputDeviceIds(); | |
62 for (int i = 0; i < deviceIds.length; i++) { | |
63 InputDevice inputDevice = InputDevice.getDevice(deviceIds[i]); | |
64 // Check for gamepad device | |
65 if (isGamepadDevice(inputDevice)) { | |
66 // Register a new gamepad device. | |
67 registerGamepad(inputDevice); | |
68 } | |
69 } | |
70 } | |
71 | |
72 /** | |
73 * Notifies the GamepadList that a {@link ContentView} is attached to a wind
ow and it should | |
74 * prepare itself for gamepad input. It must be called before {@link onGener
icMotionEvent} and | |
75 * {@link dispatchKeyEvent}. | |
76 */ | |
77 public static void onAttachedToWindow(Context context) { | |
78 assert ThreadUtils.runningOnUiThread(); | |
79 getInstance().attachedToWindow(context); | |
80 } | |
81 | |
82 private void attachedToWindow(Context context) { | |
83 if (mAttachedToWindowCounter++ == 0) { | |
84 mInputManager = (InputManager) context.getSystemService(Context.INPU
T_SERVICE); | |
85 synchronized (mLock) { | |
86 initializeDevices(); | |
87 } | |
88 // Register an input device listener. | |
89 mInputManager.registerInputDeviceListener(mInputDeviceListener, null
); | |
90 } | |
91 } | |
92 | |
93 /** | |
94 * Notifies the GamepadList that a {@link ContentView} is detached from it's
window. | |
95 */ | |
96 @SuppressLint("MissingSuperCall") | |
97 public static void onDetachedFromWindow() { | |
98 assert ThreadUtils.runningOnUiThread(); | |
99 getInstance().detachedFromWindow(); | |
100 } | |
101 | |
102 private void detachedFromWindow() { | |
103 if (--mAttachedToWindowCounter == 0) { | |
104 synchronized (mLock) { | |
105 for (int i = 0; i < MAX_GAMEPADS; ++i) { | |
106 mGamepadDevices[i] = null; | |
107 } | |
108 } | |
109 mInputManager.unregisterInputDeviceListener(mInputDeviceListener); | |
110 mInputManager = null; | |
111 } | |
112 } | |
113 | |
114 // ------------------------------------------------------------ | |
115 | |
116 private void onInputDeviceChangedImpl(int deviceId) {} | |
117 | |
118 private void onInputDeviceRemovedImpl(int deviceId) { | |
119 synchronized (mLock) { | |
120 unregisterGamepad(deviceId); | |
121 } | |
122 } | |
123 | |
124 private void onInputDeviceAddedImpl(int deviceId) { | |
125 InputDevice inputDevice = InputDevice.getDevice(deviceId); | |
126 if (!isGamepadDevice(inputDevice)) return; | |
127 synchronized (mLock) { | |
128 registerGamepad(inputDevice); | |
129 } | |
130 } | |
131 | |
132 // ------------------------------------------------------------ | |
133 | |
134 private static GamepadList getInstance() { | |
135 return LazyHolder.INSTANCE; | |
136 } | |
137 | |
138 private int getDeviceCount() { | |
139 int count = 0; | |
140 for (int i = 0; i < MAX_GAMEPADS; i++) { | |
141 if (getDevice(i) != null) { | |
142 count++; | |
143 } | |
144 } | |
145 return count; | |
146 } | |
147 | |
148 private boolean isDeviceConnected(int index) { | |
149 if (index < MAX_GAMEPADS && getDevice(index) != null) { | |
150 return true; | |
151 } | |
152 return false; | |
153 } | |
154 | |
155 private GamepadDevice getDeviceById(int deviceId) { | |
156 for (int i = 0; i < MAX_GAMEPADS; i++) { | |
157 GamepadDevice gamepad = mGamepadDevices[i]; | |
158 if (gamepad != null && gamepad.getId() == deviceId) { | |
159 return gamepad; | |
160 } | |
161 } | |
162 return null; | |
163 } | |
164 | |
165 private GamepadDevice getDevice(int index) { | |
166 // Maximum 4 Gamepads can be connected at a time starting at index zero. | |
167 assert index >= 0 && index < MAX_GAMEPADS; | |
168 return mGamepadDevices[index]; | |
169 } | |
170 | |
171 /** | |
172 * Handles key events from the gamepad devices. | |
173 * @return True if the event has been consumed. | |
174 */ | |
175 public static boolean dispatchKeyEvent(KeyEvent event) { | |
176 if (!isGamepadEvent(event)) return false; | |
177 return getInstance().handleKeyEvent(event); | |
178 } | |
179 | |
180 private boolean handleKeyEvent(KeyEvent event) { | |
181 synchronized (mLock) { | |
182 if (!mIsGamepadAPIActive) return false; | |
183 GamepadDevice gamepad = getGamepadForEvent(event); | |
184 if (gamepad == null) return false; | |
185 return gamepad.handleKeyEvent(event); | |
186 } | |
187 } | |
188 | |
189 /** | |
190 * Handles motion events from the gamepad devices. | |
191 * @return True if the event has been consumed. | |
192 */ | |
193 public static boolean onGenericMotionEvent(MotionEvent event) { | |
194 if (!isGamepadEvent(event)) return false; | |
195 return getInstance().handleMotionEvent(event); | |
196 } | |
197 | |
198 private boolean handleMotionEvent(MotionEvent event) { | |
199 synchronized (mLock) { | |
200 if (!mIsGamepadAPIActive) return false; | |
201 GamepadDevice gamepad = getGamepadForEvent(event); | |
202 if (gamepad == null) return false; | |
203 return gamepad.handleMotionEvent(event); | |
204 } | |
205 } | |
206 | |
207 private int getNextAvailableIndex() { | |
208 // When multiple gamepads are connected to a user agent, indices must be
assigned on a | |
209 // first-come first-serve basis, starting at zero. If a gamepad is disco
nnected, previously | |
210 // assigned indices must not be reassigned to gamepads that continue to
be connected. | |
211 // However, if a gamepad is disconnected, and subsequently the same or a
different | |
212 // gamepad is then connected, index entries must be reused. | |
213 | |
214 for (int i = 0; i < MAX_GAMEPADS; ++i) { | |
215 if (getDevice(i) == null) { | |
216 return i; | |
217 } | |
218 } | |
219 // Reached maximum gamepads limit. | |
220 return -1; | |
221 } | |
222 | |
223 private boolean registerGamepad(InputDevice inputDevice) { | |
224 int index = getNextAvailableIndex(); | |
225 if (index == -1) return false; // invalid index | |
226 | |
227 GamepadDevice gamepad = new GamepadDevice(index, inputDevice); | |
228 mGamepadDevices[index] = gamepad; | |
229 return true; | |
230 } | |
231 | |
232 private void unregisterGamepad(int deviceId) { | |
233 GamepadDevice gamepadDevice = getDeviceById(deviceId); | |
234 if (gamepadDevice == null) return; // Not a registered device. | |
235 int index = gamepadDevice.getIndex(); | |
236 mGamepadDevices[index] = null; | |
237 } | |
238 | |
239 private static boolean isGamepadDevice(InputDevice inputDevice) { | |
240 if (inputDevice == null) return false; | |
241 return ((inputDevice.getSources() | |
242 & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); | |
243 } | |
244 | |
245 private GamepadDevice getGamepadForEvent(InputEvent event) { | |
246 return getDeviceById(event.getDeviceId()); | |
247 } | |
248 | |
249 /** | |
250 * @return True if HTML5 gamepad API is active. | |
251 */ | |
252 public static boolean isGamepadAPIActive() { | |
253 return getInstance().mIsGamepadAPIActive; | |
254 } | |
255 | |
256 /** | |
257 * @return True if the motion event corresponds to a gamepad event. | |
258 */ | |
259 public static boolean isGamepadEvent(MotionEvent event) { | |
260 return ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice
.SOURCE_JOYSTICK); | |
261 } | |
262 | |
263 /** | |
264 * @return True if event's keycode corresponds to a gamepad key. | |
265 */ | |
266 public static boolean isGamepadEvent(KeyEvent event) { | |
267 int keyCode = event.getKeyCode(); | |
268 switch (keyCode) { | |
269 // Specific handling for dpad keys is required because | |
270 // KeyEvent.isGamepadButton doesn't consider dpad keys. | |
271 case KeyEvent.KEYCODE_DPAD_UP: | |
272 case KeyEvent.KEYCODE_DPAD_DOWN: | |
273 case KeyEvent.KEYCODE_DPAD_LEFT: | |
274 case KeyEvent.KEYCODE_DPAD_RIGHT: | |
275 return true; | |
276 default: | |
277 return KeyEvent.isGamepadButton(keyCode); | |
278 } | |
279 } | |
280 | |
281 @CalledByNative | |
282 static void updateGamepadData(long webGamepadsPtr) { | |
283 getInstance().grabGamepadData(webGamepadsPtr); | |
284 } | |
285 | |
286 private void grabGamepadData(long webGamepadsPtr) { | |
287 synchronized (mLock) { | |
288 for (int i = 0; i < MAX_GAMEPADS; i++) { | |
289 final GamepadDevice device = getDevice(i); | |
290 if (device != null) { | |
291 device.updateButtonsAndAxesMapping(); | |
292 nativeSetGamepadData(webGamepadsPtr, i, device.isStandardGam
epad(), true, | |
293 device.getName(), device.getTimestamp(), device.getA
xes(), | |
294 device.getButtons()); | |
295 } else { | |
296 nativeSetGamepadData(webGamepadsPtr, i, false, false, null,
0, null, null); | |
297 } | |
298 } | |
299 } | |
300 } | |
301 | |
302 @CalledByNative | |
303 static void setGamepadAPIActive(boolean isActive) { | |
304 getInstance().setIsGamepadActive(isActive); | |
305 } | |
306 | |
307 private void setIsGamepadActive(boolean isGamepadActive) { | |
308 synchronized (mLock) { | |
309 mIsGamepadAPIActive = isGamepadActive; | |
310 if (isGamepadActive) { | |
311 for (int i = 0; i < MAX_GAMEPADS; i++) { | |
312 GamepadDevice gamepadDevice = getDevice(i); | |
313 if (gamepadDevice == null) continue; | |
314 gamepadDevice.clearData(); | |
315 } | |
316 } | |
317 } | |
318 } | |
319 | |
320 private native void nativeSetGamepadData(long webGamepadsPtr, int index, boo
lean mapping, | |
321 boolean connected, String devicename, long timestamp, float[] axes,
float[] buttons); | |
322 | |
323 private static class LazyHolder { | |
324 private static final GamepadList INSTANCE = new GamepadList(); | |
325 } | |
326 | |
327 } | |
OLD | NEW |