1page.title=Supporting Controllers Across Android Versions 2trainingnavtop=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 13Suppport</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 174.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" 24class="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 32to make sure that your game responds to controllers consistently across devices 33running on different versions of Android. This lets your game reach a wider 34audience, and your players can enjoy a seamless gameplay experience with 35their 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 38in a backward compatible way, enabling your game to support the following 39features 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 47provided by the sample {@code ControllerSample.zip} available for download 48above. This sample shows how to implement the {@code InputManagerCompat} 49interface to support different versions of Android. To compile the sample, you 50must use Android 4.1 (API level 16) or higher. Once compiled, the sample app 51runs on any device running Android 2.3 (API level 9) or higher as the build 52target. 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 57status has changed on devices running on Android 2.3 (API level 9). However, 58the APIs are only available in Android 4.1 (API level 16) and higher, so you 59need to provide an implementation that supports Android 4.1 and higher while 60providing 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 69different 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) 92getInputDevice()}</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 196works across platforms. This approach involves the following steps:</p> 197<ol> 198<li>Define an intermediary Java interface that abstracts the implementation of 199the game controller features required by your game.</li> 200<li>Create a proxy implementation of your interface that uses APIs in Android 2014.1 and higher.</li> 202<li>Create a custom implementation of your interface that uses APIs available 203between Android 2.3 up to Android 4.0.</li> 204<li>Create the logic for switching between these implementations at runtime, 205and 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 209can work in a backward compatible way across different versions of Android, see 210<a href="{@docRoot}training/backward-compatible-ui/index.html">Creating 211Backward-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 217add version-specific implementations. One advantage of this approach is that it 218lets you mirror the public interfaces on Android 4.1 (API level 16) that 219support game controllers.</p> 220<pre> 221// The InputManagerCompat interface is a reference example. 222// The full code is provided in the ControllerSample.zip sample. 223public 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) 251getInputDevice()}. Obtains the {@link android.view.InputDevice} 252object that represents the capabilities of a game controller.</dd> 253<dt>{@code getInputDeviceIds()}</dt> 254<dd>Mirrors {@link android.hardware.input.InputManager#getInputDeviceIds() 255getInputDeviceIds()}. Returns an array of integers, each of 256which is an ID for a different input device. This is useful if you're building 257a game that supports multiple players and you want to detect how many 258controllers 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) 261registerInputDeviceListener()}. Lets you register to be informed when a new 262device 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()}. 265Unregisters an input device listener.</dd> 266<dt>{@code onGenericMotionEvent()}</dt> 267<dd>Mirrors {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) 268onGenericMotionEvent()}. Lets your game intercept and handle 269{@link android.view.MotionEvent} objects and axis values that represent events 270such as joystick movements and analog trigger presses.</dd> 271<dt>{@code onPause()}</dt> 272<dd>Stops polling for game controller events when the 273main 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 276main activity is resumed, or when the game is started and runs in the 277foreground.</dd> 278<dt>{@code InputDeviceListener}</dt> 279<dd>Mirrors the {@link android.hardware.input.InputManager.InputDeviceListener} 280interface. Lets your game know when a game controller has been added, changed, or 281removed.</dd> 282</dl> 283<p>Next, create implementations for {@code InputManagerCompat} that work 284across different platform versions. If your game is running on Android 4.1 or 285higher and calls an {@code InputManagerCompat} method, the proxy implementation 286calls the equivalent method in {@link android.hardware.input.InputManager}. 287However, 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 288only APIs introduced no later than Android 2.3. Regardless of which 289version-specific implementation is used at runtime, the implementation passes 290the call results back transparently to the game.</p> 291 292<img src="{@docRoot}images/training/backward-compatible-inputmanager.png" alt="" 293id="figure1" /> 294<p class="img-caption"> 295 <strong>Figure 1.</strong> Class diagram of interface and version-specific 296implementations. 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 302actual {@link android.hardware.input.InputManager} and {@link 303android.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. 310public 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 383than Android 2.3. To create an implementation of {@code 384InputManagerCompat} that supports Android 2.3 up to Android 4.0, you can use 385the following objects: 386<ul> 387<li>A {@link android.util.SparseArray} of device IDs to track the 388game controllers that are connected to the device.</li> 389<li>A {@link android.os.Handler} to process device events. When an app is started 390or resumed, the {@link android.os.Handler} receives a message to start polling 391for game controller disconnection. The {@link android.os.Handler} will start a 392loop to check each known connected game controller and see if a device ID is 393returned. A {@code null} return value indicates that the game controller is 394disconnected. The {@link android.os.Handler} stops polling when the app is 395paused.</li> 396<li>A {@link java.util.Map} of {@code InputManagerCompat.InputDeviceListener} 397objects. You will use the listeners to update the connection status of tracked 398game 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. 404public 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()} 421method. This method checks if an attached game controller has been 422disconnected and notifies registered listeners.</p> 423 424<pre> 425private 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 469these methods:</p> 470<pre> 471private static final int MESSAGE_TEST_FOR_DISCONNECT = 101; 472private static final long CHECK_ELAPSED_TIME = 3000L; 473 474@Override 475public void onPause() { 476 mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT); 477} 478 479@Override 480public 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, 488check if this event came from a device ID that is already tracked, or from a 489new device ID. If the device ID is new, notify registered listeners.</p> 490 491<pre> 492@Override 493public 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} 511contains a reference to an {@code InputManagerCompat.InputDeviceListener}. When 512the {@code DeviceEvent} runs, the appropriate callback method of the listener 513is called to signal if the game controller was added, changed, or removed. 514</p> 515 516<pre> 517@Override 518public 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 528public void unregisterInputDeviceListener(InputDeviceListener listener) { 529 mListeners.remove(listener); 530} 531 532private 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 544private 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 588works on devices running Android 4.1 and higher, and another 589that 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 593a <a href="http://en.wikipedia.org/wiki/Factory_(software_concept)" 594class="external-link" target="_blank">factory</a>.</p> 595<pre> 596public 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 607register an {@code InputManagerCompat.InputDeviceListener} in your main 608{@link android.view.View}. Because of the version-switching logic you set 609up, your game automatically uses the implementation that's appropriate for the 610version of Android the device is running.</p> 611<pre> 612public 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) 626onGenericMotionEvent()} method in your main view, as described in 627<a href="controller-input.html#analog">Handle a MotionEvent from a Game 628Controller</a>. Your game should now be able to process game controller events 629consistently on devices running Android 2.3 (API level 9) and higher. 630<p> 631<pre> 632@Override 633public 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} 643available for download above.</p>