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