quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 1 | page.title=Supporting Controllers Across Android Versions |
| 2 | trainingnavtop=true |
| 3 | |
| 4 | @jd:body |
| 5 | |
| 6 | <!-- This is the training bar --> |
| 7 | <div id="tb-wrapper"> |
| 8 | <div id="tb"> |
| 9 | |
| 10 | <h2>This lesson teaches you to</h2> |
| 11 | <ol> |
| 12 | <li><a href="#prepare">Prepare to Abstract APIs for Game Controller |
| 13 | Suppport</a></li> |
| 14 | <li><a href="#abstraction">Add an Interface for Backward Compatibility</a></li> |
| 15 | <li><a href="#newer">Implement the Interface on Android 4.1 and Higher</a></li> |
| 16 | <li><a href="#older">Implement the Interface on Android 2.3 up to Android |
| 17 | 4.0</a></li> |
| 18 | <li><a href="#using">Use the Version-Specific Implementations</a></li> |
| 19 | </ol> |
| 20 | |
| 21 | <h2>Try it out</h2> |
| 22 | <div class="download-box"> |
| 23 | <a href="http://developer.android.com/shareables/training/ControllerSample.zip" |
| 24 | class="button">Download the sample</a> |
| 25 | <p class="filename">ControllerSample.zip</p> |
| 26 | </div> |
| 27 | |
| 28 | </div> |
| 29 | </div> |
| 30 | |
| 31 | <p>If you are supporting game controllers in your game, it's your responsibility |
| 32 | to make sure that your game responds to controllers consistently across devices |
| 33 | running on different versions of Android. This lets your game reach a wider |
| 34 | audience, and your players can enjoy a seamless gameplay experience with |
| 35 | their controllers even when they switch or upgrade their Android devices.</p> |
| 36 | |
| 37 | <p>This lesson demonstrates how to use APIs available in Android 4.1 and higher |
| 38 | in a backward compatible way, enabling your game to support the following |
| 39 | features on devices running Android 2.3 and higher:</p> |
| 40 | <ul> |
| 41 | <li>The game can detect if a new game controller is added, changed, or removed.</li> |
| 42 | <li>The game can query the capabilities of a game controller.</li> |
| 43 | <li>The game can recognize incoming motion events from a game controller.</li> |
| 44 | </ul> |
| 45 | |
| 46 | <p>The examples in this lesson are based on the reference implementation |
| 47 | provided by the sample {@code ControllerSample.zip} available for download |
| 48 | above. This sample shows how to implement the {@code InputManagerCompat} |
| 49 | interface to support different versions of Android. To compile the sample, you |
| 50 | must use Android 4.1 (API level 16) or higher. Once compiled, the sample app |
| 51 | runs on any device running Android 2.3 (API level 9) or higher as the build |
| 52 | target. |
| 53 | </p> |
| 54 | |
| 55 | <h2 id="prepare">Prepare to Abstract APIs for Game Controller Support</h2> |
| 56 | <p>Suppose you want to be able to determine if a game controller's connection |
| 57 | status has changed on devices running on Android 2.3 (API level 9). However, |
| 58 | the APIs are only available in Android 4.1 (API level 16) and higher, so you |
| 59 | need to provide an implementation that supports Android 4.1 and higher while |
| 60 | providing a fallback mechanism that supports Android 2.3 up to Android 4.0.</p> |
| 61 | |
| 62 | <p>To help you determine which features require such a fallback mechanism for |
| 63 | older versions, table 1 lists the differences in game controller support |
| 64 | between Android 2.3 (API level 9), 3.1 (API level 12), and 4.1 (API level |
| 65 | 16).</p> |
| 66 | |
| 67 | <p class="table-caption" id="game-controller-support-table"> |
| 68 | <strong>Table 1.</strong> APIs for game controller support across |
| 69 | different Android versions. |
| 70 | </p> |
| 71 | |
| 72 | <table> |
| 73 | <tbody> |
| 74 | <tr> |
| 75 | <th>Controller Information</th> |
| 76 | <th>Controller API</th> |
| 77 | <th>API level 9</th> |
| 78 | <th>API level 12</th> |
| 79 | <th>API level 16</th> |
| 80 | </tr> |
| 81 | |
| 82 | <tr> |
| 83 | <td rowspan="5">Device Identification</td> |
| 84 | <td>{@link android.hardware.input.InputManager#getInputDeviceIds()}</td> |
| 85 | <td style="text-align: center;"><big> </big></td> |
| 86 | <td style="text-align: center;"><big> </big></td> |
| 87 | <td style="text-align: center;"><big>•</big></td> |
| 88 | </tr> |
| 89 | |
| 90 | <tr> |
| 91 | <td>{@link android.hardware.input.InputManager#getInputDevice(int) |
| 92 | getInputDevice()}</td> |
| 93 | <td style="text-align: center;"> </td> |
| 94 | <td style="text-align: center;"><big> </big></td> |
| 95 | <td style="text-align: center;"><big>•</big></td> |
| 96 | </tr> |
| 97 | |
| 98 | <tr> |
| 99 | <td>{@link android.view.InputDevice#getVibrator()}</td> |
| 100 | <td style="text-align: center;"> </td> |
| 101 | <td style="text-align: center;"><big> </big></td> |
| 102 | <td style="text-align: center;"><big>•</big></td> |
| 103 | </tr> |
| 104 | |
| 105 | <td>{@link android.view.InputDevice#SOURCE_JOYSTICK}</td> |
| 106 | <td style="text-align: center;"> </td> |
| 107 | <td style="text-align: center;"><big>•</big></td> |
| 108 | <td style="text-align: center;"><big>•</big></td> |
| 109 | </tr> |
| 110 | |
| 111 | <tr> |
| 112 | <td>{@link android.view.InputDevice#SOURCE_GAMEPAD}</td> |
| 113 | <td style="text-align: center;"> </td> |
| 114 | <td style="text-align: center;"><big>•</big></td> |
| 115 | <td style="text-align: center;"><big>•</big></td> |
| 116 | </tr> |
| 117 | |
| 118 | <tr> |
| 119 | <td rowspan="3">Connection Status</td> |
| 120 | <td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded(int) onInputDeviceAdded()}</td> |
| 121 | <td style="text-align: center;"> </td> |
| 122 | <td style="text-align: center;"> </td> |
| 123 | <td style="text-align: center;"><big>•</big></td> |
| 124 | </tr> |
| 125 | |
| 126 | <tr> |
| 127 | <td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged(int) onInputDeviceChanged()}</td> |
| 128 | <td style="text-align: center;"> </td> |
| 129 | <td style="text-align: center;"> </td> |
| 130 | <td style="text-align: center;"><big>•</big></td> |
| 131 | </tr> |
| 132 | |
| 133 | <tr> |
| 134 | <td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved(int) onInputDeviceRemoved()}</td> |
| 135 | <td style="text-align: center;"> </td> |
| 136 | <td style="text-align: center;"> </td> |
| 137 | <td style="text-align: center;"><big>•</big></td> |
| 138 | </tr> |
| 139 | |
| 140 | <tr> |
| 141 | <td rowspan="4">Input Event Identification</td> |
| 142 | <td>D-pad press ( |
| 143 | {@link android.view.KeyEvent#KEYCODE_DPAD_UP}, |
| 144 | {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}, |
| 145 | {@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}, |
| 146 | {@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}, |
| 147 | {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER})</td> |
| 148 | <td style="text-align: center;"><big>•</big></td> |
| 149 | <td style="text-align: center;"><big>•</big></td> |
| 150 | <td style="text-align: center;"><big>•</big></td> |
| 151 | </tr> |
| 152 | |
| 153 | <tr> |
| 154 | <td>Gamepad button press ( |
| 155 | {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}, |
| 156 | {@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}, |
| 157 | {@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL}, |
| 158 | {@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR}, |
| 159 | {@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}, |
| 160 | {@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}, |
| 161 | {@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1}, |
| 162 | {@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1}, |
| 163 | {@link android.view.KeyEvent#KEYCODE_BUTTON_R2 BUTTON_R2}, |
| 164 | {@link android.view.KeyEvent#KEYCODE_BUTTON_L2 BUTTON_L2})</td> |
| 165 | <td style="text-align: center;"> </td> |
| 166 | <td style="text-align: center;"><big>•</big></td> |
| 167 | <td style="text-align: center;"><big>•</big></td> |
| 168 | </tr> |
| 169 | |
| 170 | <tr> |
| 171 | <td>Joystick and hat switch movement ( |
| 172 | {@link android.view.MotionEvent#AXIS_X}, |
| 173 | {@link android.view.MotionEvent#AXIS_Y}, |
| 174 | {@link android.view.MotionEvent#AXIS_Z}, |
| 175 | {@link android.view.MotionEvent#AXIS_RZ}, |
| 176 | {@link android.view.MotionEvent#AXIS_HAT_X}, |
| 177 | {@link android.view.MotionEvent#AXIS_HAT_Y})</td> |
| 178 | <td style="text-align: center;"> </td> |
| 179 | <td style="text-align: center;"><big>•</big></td> |
| 180 | <td style="text-align: center;"><big>•</big></td> |
| 181 | </tr> |
| 182 | |
| 183 | <tr> |
| 184 | <td>Analog trigger press ( |
| 185 | {@link android.view.MotionEvent#AXIS_LTRIGGER}, |
| 186 | {@link android.view.MotionEvent#AXIS_RTRIGGER})</td> |
| 187 | <td style="text-align: center;"> </td> |
| 188 | <td style="text-align: center;"><big>•</big></td> |
| 189 | <td style="text-align: center;"><big>•</big></td> |
| 190 | </tr> |
| 191 | |
| 192 | </tbody> |
| 193 | </table> |
| 194 | |
| 195 | <p>You can use abstraction to build version-aware game controller support that |
| 196 | works across platforms. This approach involves the following steps:</p> |
| 197 | <ol> |
| 198 | <li>Define an intermediary Java interface that abstracts the implementation of |
| 199 | the game controller features required by your game.</li> |
| 200 | <li>Create a proxy implementation of your interface that uses APIs in Android |
| 201 | 4.1 and higher.</li> |
| 202 | <li>Create a custom implementation of your interface that uses APIs available |
| 203 | between Android 2.3 up to Android 4.0.</li> |
| 204 | <li>Create the logic for switching between these implementations at runtime, |
| 205 | and begin using the interface in your game.</li> |
| 206 | </ol> |
| 207 | |
| 208 | <p>For an overview of how abstraction can be used to ensure that applications |
| 209 | can work in a backward compatible way across different versions of Android, see |
| 210 | <a href="{@docRoot}training/backward-compatible-ui/index.html">Creating |
| 211 | Backward-Compatible UIs</a>. |
| 212 | </p> |
| 213 | |
| 214 | <h2 id="abstraction">Add an Interface for Backward Compatibility</h2> |
| 215 | |
| 216 | <p>To provide backward compatibility, you can create a custom interface then |
| 217 | add version-specific implementations. One advantage of this approach is that it |
| 218 | lets you mirror the public interfaces on Android 4.1 (API level 16) that |
| 219 | support game controllers.</p> |
| 220 | <pre> |
| 221 | // The InputManagerCompat interface is a reference example. |
| 222 | // The full code is provided in the ControllerSample.zip sample. |
| 223 | public interface InputManagerCompat { |
| 224 | ... |
| 225 | public InputDevice getInputDevice(int id); |
| 226 | public int[] getInputDeviceIds(); |
| 227 | |
| 228 | public void registerInputDeviceListener( |
| 229 | InputManagerCompat.InputDeviceListener listener, |
| 230 | Handler handler); |
| 231 | public void unregisterInputDeviceListener( |
| 232 | InputManagerCompat.InputDeviceListener listener); |
| 233 | |
| 234 | public void onGenericMotionEvent(MotionEvent event); |
| 235 | |
| 236 | public void onPause(); |
| 237 | public void onResume(); |
| 238 | |
| 239 | public interface InputDeviceListener { |
| 240 | void onInputDeviceAdded(int deviceId); |
| 241 | void onInputDeviceChanged(int deviceId); |
| 242 | void onInputDeviceRemoved(int deviceId); |
| 243 | } |
| 244 | ... |
| 245 | } |
| 246 | </pre> |
| 247 | <p>The {@code InputManagerCompat} interface provides the following methods:</p> |
| 248 | <dl> |
| 249 | <dt>{@code getInputDevice()}</dt> |
| 250 | <dd>Mirrors {@link android.hardware.input.InputManager#getInputDevice(int) |
| 251 | getInputDevice()}. Obtains the {@link android.view.InputDevice} |
| 252 | object that represents the capabilities of a game controller.</dd> |
| 253 | <dt>{@code getInputDeviceIds()}</dt> |
| 254 | <dd>Mirrors {@link android.hardware.input.InputManager#getInputDeviceIds() |
| 255 | getInputDeviceIds()}. Returns an array of integers, each of |
| 256 | which is an ID for a different input device. This is useful if you're building |
| 257 | a game that supports multiple players and you want to detect how many |
| 258 | controllers are connected.</dd> |
| 259 | <dt>{@code registerInputDeviceListener()}</dt> |
| 260 | <dd>Mirrors {@link android.hardware.input.InputManager#registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler) |
| 261 | registerInputDeviceListener()}. Lets you register to be informed when a new |
| 262 | device is added, changed, or removed.</dd> |
| 263 | <dt>{@code unregisterInputDeviceListener()}</dt> |
| 264 | <dd>Mirrors {@link android.hardware.input.InputManager#unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener) unregisterInputDeviceListener()}. |
| 265 | Unregisters an input device listener.</dd> |
| 266 | <dt>{@code onGenericMotionEvent()}</dt> |
| 267 | <dd>Mirrors {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) |
| 268 | onGenericMotionEvent()}. Lets your game intercept and handle |
| 269 | {@link android.view.MotionEvent} objects and axis values that represent events |
| 270 | such as joystick movements and analog trigger presses.</dd> |
| 271 | <dt>{@code onPause()}</dt> |
| 272 | <dd>Stops polling for game controller events when the |
| 273 | main activity is paused, or when the game no longer has focus.</dd> |
| 274 | <dt>{@code onResume()}</dt> |
| 275 | <dd>Starts polling for game controller events when the |
| 276 | main activity is resumed, or when the game is started and runs in the |
| 277 | foreground.</dd> |
| 278 | <dt>{@code InputDeviceListener}</dt> |
| 279 | <dd>Mirrors the {@link android.hardware.input.InputManager.InputDeviceListener} |
| 280 | interface. Lets your game know when a game controller has been added, changed, or |
| 281 | removed.</dd> |
| 282 | </dl> |
| 283 | <p>Next, create implementations for {@code InputManagerCompat} that work |
| 284 | across different platform versions. If your game is running on Android 4.1 or |
| 285 | higher and calls an {@code InputManagerCompat} method, the proxy implementation |
| 286 | calls the equivalent method in {@link android.hardware.input.InputManager}. |
| 287 | However, if your game is running on Android 2.3 up to Android 4.0, the custom implementation processes calls to {@code InputManagerCompat} methods by using |
| 288 | only APIs introduced no later than Android 2.3. Regardless of which |
| 289 | version-specific implementation is used at runtime, the implementation passes |
| 290 | the call results back transparently to the game.</p> |
| 291 | |
| 292 | <img src="{@docRoot}images/training/backward-compatible-inputmanager.png" alt="" |
| 293 | id="figure1" /> |
| 294 | <p class="img-caption"> |
| 295 | <strong>Figure 1.</strong> Class diagram of interface and version-specific |
| 296 | implementations. |
| 297 | </p> |
| 298 | |
| 299 | <h2 id="newer">Implement the Interface on Android 4.1 and Higher</h2> |
| 300 | <p>{@code InputManagerCompatV16} is an implementation of the |
| 301 | {@code InputManagerCompat} interface that proxies method calls to an |
| 302 | actual {@link android.hardware.input.InputManager} and {@link |
| 303 | android.hardware.input.InputManager.InputDeviceListener}. The |
| 304 | {@link android.hardware.input.InputManager} is obtained from the system |
| 305 | {@link android.content.Context}.</p> |
| 306 | |
| 307 | <pre> |
| 308 | // The InputManagerCompatV16 class is a reference implementation. |
| 309 | // The full code is provided in the ControllerSample.zip sample. |
| 310 | public class InputManagerV16 implements InputManagerCompat { |
| 311 | |
| 312 | private final InputManager mInputManager; |
| 313 | private final Map<InputManagerCompat.InputDeviceListener, |
| 314 | V16InputDeviceListener> mListeners; |
| 315 | |
| 316 | public InputManagerV16(Context context) { |
| 317 | mInputManager = (InputManager) |
| 318 | context.getSystemService(Context.INPUT_SERVICE); |
| 319 | mListeners = new HashMap<InputManagerCompat.InputDeviceListener, |
| 320 | V16InputDeviceListener>(); |
| 321 | } |
| 322 | |
| 323 | @Override |
| 324 | public InputDevice getInputDevice(int id) { |
| 325 | return mInputManager.getInputDevice(id); |
| 326 | } |
| 327 | |
| 328 | @Override |
| 329 | public int[] getInputDeviceIds() { |
| 330 | return mInputManager.getInputDeviceIds(); |
| 331 | } |
| 332 | |
| 333 | static class V16InputDeviceListener implements |
| 334 | InputManager.InputDeviceListener { |
| 335 | final InputManagerCompat.InputDeviceListener mIDL; |
| 336 | |
| 337 | public V16InputDeviceListener(InputDeviceListener idl) { |
| 338 | mIDL = idl; |
| 339 | } |
| 340 | |
| 341 | @Override |
| 342 | public void onInputDeviceAdded(int deviceId) { |
| 343 | mIDL.onInputDeviceAdded(deviceId); |
| 344 | } |
| 345 | |
| 346 | // Do the same for device change and removal |
| 347 | ... |
| 348 | } |
| 349 | |
| 350 | @Override |
| 351 | public void registerInputDeviceListener(InputDeviceListener listener, |
| 352 | Handler handler) { |
| 353 | V16InputDeviceListener v16Listener = new |
| 354 | V16InputDeviceListener(listener); |
| 355 | mInputManager.registerInputDeviceListener(v16Listener, handler); |
| 356 | mListeners.put(listener, v16Listener); |
| 357 | } |
| 358 | |
| 359 | // Do the same for unregistering an input device listener |
| 360 | ... |
| 361 | |
| 362 | @Override |
| 363 | public void onGenericMotionEvent(MotionEvent event) { |
| 364 | // unused in V16 |
| 365 | } |
| 366 | |
| 367 | @Override |
| 368 | public void onPause() { |
| 369 | // unused in V16 |
| 370 | } |
| 371 | |
| 372 | @Override |
| 373 | public void onResume() { |
| 374 | // unused in V16 |
| 375 | } |
| 376 | |
| 377 | } |
| 378 | </pre> |
| 379 | |
| 380 | <h2 id="older">Implementing the Interface on Android 2.3 up to Android 4.0</h2> |
| 381 | |
| 382 | <p>The {@code InputManagerV9} implementation uses APIs introduced no later |
| 383 | than Android 2.3. To create an implementation of {@code |
| 384 | InputManagerCompat} that supports Android 2.3 up to Android 4.0, you can use |
| 385 | the following objects: |
| 386 | <ul> |
| 387 | <li>A {@link android.util.SparseArray} of device IDs to track the |
| 388 | game controllers that are connected to the device.</li> |
| 389 | <li>A {@link android.os.Handler} to process device events. When an app is started |
| 390 | or resumed, the {@link android.os.Handler} receives a message to start polling |
| 391 | for game controller disconnection. The {@link android.os.Handler} will start a |
| 392 | loop to check each known connected game controller and see if a device ID is |
| 393 | returned. A {@code null} return value indicates that the game controller is |
| 394 | disconnected. The {@link android.os.Handler} stops polling when the app is |
| 395 | paused.</li> |
| 396 | <li>A {@link java.util.Map} of {@code InputManagerCompat.InputDeviceListener} |
| 397 | objects. You will use the listeners to update the connection status of tracked |
| 398 | game controllers.</li> |
| 399 | </ul> |
| 400 | |
| 401 | <pre> |
| 402 | // The InputManagerCompatV9 class is a reference implementation. |
| 403 | // The full code is provided in the ControllerSample.zip sample. |
| 404 | public class InputManagerV9 implements InputManagerCompat { |
| 405 | private final SparseArray<long[]> mDevices; |
| 406 | private final Map<InputDeviceListener, Handler> mListeners; |
| 407 | private final Handler mDefaultHandler; |
| 408 | … |
| 409 | |
| 410 | public InputManagerV9() { |
| 411 | mDevices = new SparseArray<long[]>(); |
| 412 | mListeners = new HashMap<InputDeviceListener, Handler>(); |
| 413 | mDefaultHandler = new PollingMessageHandler(this); |
| 414 | } |
| 415 | } |
| 416 | </pre> |
| 417 | |
| 418 | <p>Implement a {@code PollingMessageHandler} object that extends |
| 419 | {@link android.os.Handler}, and override the |
| 420 | {@link android.os.Handler#handleMessage(android.os.Message) handleMessage()} |
| 421 | method. This method checks if an attached game controller has been |
| 422 | disconnected and notifies registered listeners.</p> |
| 423 | |
| 424 | <pre> |
| 425 | private static class PollingMessageHandler extends Handler { |
| 426 | private final WeakReference<InputManagerV9> mInputManager; |
| 427 | |
| 428 | PollingMessageHandler(InputManagerV9 im) { |
| 429 | mInputManager = new WeakReference<InputManagerV9>(im); |
| 430 | } |
| 431 | |
| 432 | @Override |
| 433 | public void handleMessage(Message msg) { |
| 434 | super.handleMessage(msg); |
| 435 | switch (msg.what) { |
| 436 | case MESSAGE_TEST_FOR_DISCONNECT: |
| 437 | InputManagerV9 imv = mInputManager.get(); |
| 438 | if (null != imv) { |
| 439 | long time = SystemClock.elapsedRealtime(); |
| 440 | int size = imv.mDevices.size(); |
| 441 | for (int i = 0; i < size; i++) { |
| 442 | long[] lastContact = imv.mDevices.valueAt(i); |
| 443 | if (null != lastContact) { |
| 444 | if (time - lastContact[0] > CHECK_ELAPSED_TIME) { |
| 445 | // check to see if the device has been |
| 446 | // disconnected |
| 447 | int id = imv.mDevices.keyAt(i); |
| 448 | if (null == InputDevice.getDevice(id)) { |
| 449 | // Notify the registered listeners |
| 450 | // that the game controller is disconnected |
| 451 | ... |
| 452 | imv.mDevices.remove(id); |
| 453 | } else { |
| 454 | lastContact[0] = time; |
| 455 | } |
| 456 | } |
| 457 | } |
| 458 | } |
| 459 | sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, |
| 460 | CHECK_ELAPSED_TIME); |
| 461 | } |
| 462 | break; |
| 463 | } |
| 464 | } |
| 465 | } |
| 466 | </pre> |
| 467 | |
| 468 | <p>To start and stop polling for game controller disconnection, override |
| 469 | these methods:</p> |
| 470 | <pre> |
| 471 | private static final int MESSAGE_TEST_FOR_DISCONNECT = 101; |
| 472 | private static final long CHECK_ELAPSED_TIME = 3000L; |
| 473 | |
| 474 | @Override |
| 475 | public void onPause() { |
| 476 | mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT); |
| 477 | } |
| 478 | |
| 479 | @Override |
| 480 | public void onResume() { |
| 481 | mDefaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, |
| 482 | CHECK_ELAPSED_TIME); |
| 483 | } |
| 484 | </pre> |
| 485 | |
| 486 | <p>To detect that an input device has been added, override the |
| 487 | {@code onGenericMotionEvent()} method. When the system reports a motion event, |
| 488 | check if this event came from a device ID that is already tracked, or from a |
| 489 | new device ID. If the device ID is new, notify registered listeners.</p> |
| 490 | |
| 491 | <pre> |
| 492 | @Override |
| 493 | public void onGenericMotionEvent(MotionEvent event) { |
| 494 | // detect new devices |
| 495 | int id = event.getDeviceId(); |
| 496 | long[] timeArray = mDevices.get(id); |
| 497 | if (null == timeArray) { |
| 498 | // Notify the registered listeners that a game controller is added |
| 499 | ... |
| 500 | timeArray = new long[1]; |
| 501 | mDevices.put(id, timeArray); |
| 502 | } |
| 503 | long time = SystemClock.elapsedRealtime(); |
| 504 | timeArray[0] = time; |
| 505 | } |
| 506 | </pre> |
| 507 | |
| 508 | <p>Notification of listeners is implemented by using the |
| 509 | {@link android.os.Handler} object to send a {@code DeviceEvent} |
| 510 | {@link java.lang.Runnable} object to the message queue. The {@code DeviceEvent} |
| 511 | contains a reference to an {@code InputManagerCompat.InputDeviceListener}. When |
| 512 | the {@code DeviceEvent} runs, the appropriate callback method of the listener |
| 513 | is called to signal if the game controller was added, changed, or removed. |
| 514 | </p> |
| 515 | |
| 516 | <pre> |
| 517 | @Override |
| 518 | public void registerInputDeviceListener(InputDeviceListener listener, |
| 519 | Handler handler) { |
| 520 | mListeners.remove(listener); |
| 521 | if (handler == null) { |
| 522 | handler = mDefaultHandler; |
| 523 | } |
| 524 | mListeners.put(listener, handler); |
| 525 | } |
| 526 | |
| 527 | @Override |
| 528 | public void unregisterInputDeviceListener(InputDeviceListener listener) { |
| 529 | mListeners.remove(listener); |
| 530 | } |
| 531 | |
| 532 | private void notifyListeners(int why, int deviceId) { |
| 533 | // the state of some device has changed |
| 534 | if (!mListeners.isEmpty()) { |
| 535 | for (InputDeviceListener listener : mListeners.keySet()) { |
| 536 | Handler handler = mListeners.get(listener); |
| 537 | DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId, |
| 538 | listener); |
| 539 | handler.post(odc); |
| 540 | } |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | private static class DeviceEvent implements Runnable { |
| 545 | private int mMessageType; |
| 546 | private int mId; |
| 547 | private InputDeviceListener mListener; |
| 548 | private static Queue<DeviceEvent> sObjectQueue = |
| 549 | new ArrayDeque<DeviceEvent>(); |
| 550 | ... |
| 551 | |
| 552 | static DeviceEvent getDeviceEvent(int messageType, int id, |
| 553 | InputDeviceListener listener) { |
| 554 | DeviceEvent curChanged = sObjectQueue.poll(); |
| 555 | if (null == curChanged) { |
| 556 | curChanged = new DeviceEvent(); |
| 557 | } |
| 558 | curChanged.mMessageType = messageType; |
| 559 | curChanged.mId = id; |
| 560 | curChanged.mListener = listener; |
| 561 | return curChanged; |
| 562 | } |
| 563 | |
| 564 | @Override |
| 565 | public void run() { |
| 566 | switch (mMessageType) { |
| 567 | case ON_DEVICE_ADDED: |
| 568 | mListener.onInputDeviceAdded(mId); |
| 569 | break; |
| 570 | case ON_DEVICE_CHANGED: |
| 571 | mListener.onInputDeviceChanged(mId); |
| 572 | break; |
| 573 | case ON_DEVICE_REMOVED: |
| 574 | mListener.onInputDeviceRemoved(mId); |
| 575 | break; |
| 576 | default: |
| 577 | // Handle unknown message type |
| 578 | ... |
| 579 | break; |
| 580 | } |
| 581 | // Put this runnable back in the queue |
| 582 | sObjectQueue.offer(this); |
| 583 | } |
| 584 | } |
| 585 | </pre> |
| 586 | |
| 587 | <p>You now have two implementations of {@code InputManagerCompat}: one that |
| 588 | works on devices running Android 4.1 and higher, and another |
| 589 | that works on devices running Android 2.3 up to Android 4.0.</p> |
| 590 | |
| 591 | <h2 id="using">Use the Version-Specific Implementation</h2> |
| 592 | <p>The version-specific switching logic is implemented in a class that acts as |
| 593 | a <a href="http://en.wikipedia.org/wiki/Factory_(software_concept)" |
| 594 | class="external-link" target="_blank">factory</a>.</p> |
| 595 | <pre> |
| 596 | public static class Factory { |
| 597 | public static InputManagerCompat getInputManager(Context context) { |
| 598 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
| 599 | return new InputManagerV16(context); |
| 600 | } else { |
| 601 | return new InputManagerV9(); |
| 602 | } |
| 603 | } |
| 604 | } |
| 605 | </pre> |
| 606 | <p>Now you can simply instantiate an {@code InputManagerCompat} object and |
| 607 | register an {@code InputManagerCompat.InputDeviceListener} in your main |
| 608 | {@link android.view.View}. Because of the version-switching logic you set |
| 609 | up, your game automatically uses the implementation that's appropriate for the |
| 610 | version of Android the device is running.</p> |
| 611 | <pre> |
| 612 | public class GameView extends View implements InputDeviceListener { |
| 613 | private InputManagerCompat mInputManager; |
| 614 | ... |
| 615 | |
| 616 | public GameView(Context context, AttributeSet attrs) { |
| 617 | mInputManager = |
| 618 | InputManagerCompat.Factory.getInputManager(this.getContext()); |
| 619 | mInputManager.registerInputDeviceListener(this, null); |
| 620 | ... |
| 621 | } |
| 622 | } |
| 623 | </pre> |
| 624 | <p>Next, override the |
| 625 | {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) |
| 626 | onGenericMotionEvent()} method in your main view, as described in |
| 627 | <a href="controller-input.html#analog">Handle a MotionEvent from a Game |
| 628 | Controller</a>. Your game should now be able to process game controller events |
| 629 | consistently on devices running Android 2.3 (API level 9) and higher. |
| 630 | <p> |
| 631 | <pre> |
| 632 | @Override |
| 633 | public boolean onGenericMotionEvent(MotionEvent event) { |
| 634 | mInputManager.onGenericMotionEvent(event); |
| 635 | |
| 636 | // Handle analog input from the controller as normal |
| 637 | ... |
| 638 | return super.onGenericMotionEvent(event); |
| 639 | } |
| 640 | </pre> |
| 641 | <p>You can find a complete implementation of this compatibility code in the |
| 642 | {@code GameView} class provided in the sample {@code ControllerSample.zip} |
| 643 | available for download above.</p> |