1dff746b02
Change-Id: I40b79aee2302f1e32a9e8bc11d0b0415dcbe7705
643 lines
24 KiB
Plaintext
643 lines
24 KiB
Plaintext
page.title=Supporting Controllers Across Android Versions
|
|
trainingnavtop=true
|
|
|
|
@jd:body
|
|
|
|
<!-- This is the training bar -->
|
|
<div id="tb-wrapper">
|
|
<div id="tb">
|
|
|
|
<h2>This lesson teaches you to</h2>
|
|
<ol>
|
|
<li><a href="#prepare">Prepare to Abstract APIs for Game Controller
|
|
Suppport</a></li>
|
|
<li><a href="#abstraction">Add an Interface for Backward Compatibility</a></li>
|
|
<li><a href="#newer">Implement the Interface on Android 4.1 and Higher</a></li>
|
|
<li><a href="#older">Implement the Interface on Android 2.3 up to Android
|
|
4.0</a></li>
|
|
<li><a href="#using">Use the Version-Specific Implementations</a></li>
|
|
</ol>
|
|
|
|
<h2>Try it out</h2>
|
|
<div class="download-box">
|
|
<a href="http://developer.android.com/shareables/training/ControllerSample.zip"
|
|
class="button">Download the sample</a>
|
|
<p class="filename">ControllerSample.zip</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<p>If you are supporting game controllers in your game, it's your responsibility
|
|
to make sure that your game responds to controllers consistently across devices
|
|
running on different versions of Android. This lets your game reach a wider
|
|
audience, and your players can enjoy a seamless gameplay experience with
|
|
their controllers even when they switch or upgrade their Android devices.</p>
|
|
|
|
<p>This lesson demonstrates how to use APIs available in Android 4.1 and higher
|
|
in a backward compatible way, enabling your game to support the following
|
|
features on devices running Android 2.3 and higher:</p>
|
|
<ul>
|
|
<li>The game can detect if a new game controller is added, changed, or removed.</li>
|
|
<li>The game can query the capabilities of a game controller.</li>
|
|
<li>The game can recognize incoming motion events from a game controller.</li>
|
|
</ul>
|
|
|
|
<p>The examples in this lesson are based on the reference implementation
|
|
provided by the sample {@code ControllerSample.zip} available for download
|
|
above. This sample shows how to implement the {@code InputManagerCompat}
|
|
interface to support different versions of Android. To compile the sample, you
|
|
must use Android 4.1 (API level 16) or higher. Once compiled, the sample app
|
|
runs on any device running Android 2.3 (API level 9) or higher as the build
|
|
target.
|
|
</p>
|
|
|
|
<h2 id="prepare">Prepare to Abstract APIs for Game Controller Support</h2>
|
|
<p>Suppose you want to be able to determine if a game controller's connection
|
|
status has changed on devices running on Android 2.3 (API level 9). However,
|
|
the APIs are only available in Android 4.1 (API level 16) and higher, so you
|
|
need to provide an implementation that supports Android 4.1 and higher while
|
|
providing a fallback mechanism that supports Android 2.3 up to Android 4.0.</p>
|
|
|
|
<p>To help you determine which features require such a fallback mechanism for
|
|
older versions, table 1 lists the differences in game controller support
|
|
between Android 2.3 (API level 9), 3.1 (API level 12), and 4.1 (API level
|
|
16).</p>
|
|
|
|
<p class="table-caption" id="game-controller-support-table">
|
|
<strong>Table 1.</strong> APIs for game controller support across
|
|
different Android versions.
|
|
</p>
|
|
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<th>Controller Information</th>
|
|
<th>Controller API</th>
|
|
<th>API level 9</th>
|
|
<th>API level 12</th>
|
|
<th>API level 16</th>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td rowspan="5">Device Identification</td>
|
|
<td>{@link android.hardware.input.InputManager#getInputDeviceIds()}</td>
|
|
<td style="text-align: center;"><big> </big></td>
|
|
<td style="text-align: center;"><big> </big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>{@link android.hardware.input.InputManager#getInputDevice(int)
|
|
getInputDevice()}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big> </big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>{@link android.view.InputDevice#getVibrator()}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big> </big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<td>{@link android.view.InputDevice#SOURCE_JOYSTICK}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>{@link android.view.InputDevice#SOURCE_GAMEPAD}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td rowspan="3">Connection Status</td>
|
|
<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded(int) onInputDeviceAdded()}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged(int) onInputDeviceChanged()}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved(int) onInputDeviceRemoved()}</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td rowspan="4">Input Event Identification</td>
|
|
<td>D-pad press (
|
|
{@link android.view.KeyEvent#KEYCODE_DPAD_UP},
|
|
{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN},
|
|
{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT},
|
|
{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT},
|
|
{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER})</td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>Gamepad button press (
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_R2 BUTTON_R2},
|
|
{@link android.view.KeyEvent#KEYCODE_BUTTON_L2 BUTTON_L2})</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>Joystick and hat switch movement (
|
|
{@link android.view.MotionEvent#AXIS_X},
|
|
{@link android.view.MotionEvent#AXIS_Y},
|
|
{@link android.view.MotionEvent#AXIS_Z},
|
|
{@link android.view.MotionEvent#AXIS_RZ},
|
|
{@link android.view.MotionEvent#AXIS_HAT_X},
|
|
{@link android.view.MotionEvent#AXIS_HAT_Y})</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>Analog trigger press (
|
|
{@link android.view.MotionEvent#AXIS_LTRIGGER},
|
|
{@link android.view.MotionEvent#AXIS_RTRIGGER})</td>
|
|
<td style="text-align: center;"> </td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
<td style="text-align: center;"><big>•</big></td>
|
|
</tr>
|
|
|
|
</tbody>
|
|
</table>
|
|
|
|
<p>You can use abstraction to build version-aware game controller support that
|
|
works across platforms. This approach involves the following steps:</p>
|
|
<ol>
|
|
<li>Define an intermediary Java interface that abstracts the implementation of
|
|
the game controller features required by your game.</li>
|
|
<li>Create a proxy implementation of your interface that uses APIs in Android
|
|
4.1 and higher.</li>
|
|
<li>Create a custom implementation of your interface that uses APIs available
|
|
between Android 2.3 up to Android 4.0.</li>
|
|
<li>Create the logic for switching between these implementations at runtime,
|
|
and begin using the interface in your game.</li>
|
|
</ol>
|
|
|
|
<p>For an overview of how abstraction can be used to ensure that applications
|
|
can work in a backward compatible way across different versions of Android, see
|
|
<a href="{@docRoot}training/backward-compatible-ui/index.html">Creating
|
|
Backward-Compatible UIs</a>.
|
|
</p>
|
|
|
|
<h2 id="abstraction">Add an Interface for Backward Compatibility</h2>
|
|
|
|
<p>To provide backward compatibility, you can create a custom interface then
|
|
add version-specific implementations. One advantage of this approach is that it
|
|
lets you mirror the public interfaces on Android 4.1 (API level 16) that
|
|
support game controllers.</p>
|
|
<pre>
|
|
// The InputManagerCompat interface is a reference example.
|
|
// The full code is provided in the ControllerSample.zip sample.
|
|
public interface InputManagerCompat {
|
|
...
|
|
public InputDevice getInputDevice(int id);
|
|
public int[] getInputDeviceIds();
|
|
|
|
public void registerInputDeviceListener(
|
|
InputManagerCompat.InputDeviceListener listener,
|
|
Handler handler);
|
|
public void unregisterInputDeviceListener(
|
|
InputManagerCompat.InputDeviceListener listener);
|
|
|
|
public void onGenericMotionEvent(MotionEvent event);
|
|
|
|
public void onPause();
|
|
public void onResume();
|
|
|
|
public interface InputDeviceListener {
|
|
void onInputDeviceAdded(int deviceId);
|
|
void onInputDeviceChanged(int deviceId);
|
|
void onInputDeviceRemoved(int deviceId);
|
|
}
|
|
...
|
|
}
|
|
</pre>
|
|
<p>The {@code InputManagerCompat} interface provides the following methods:</p>
|
|
<dl>
|
|
<dt>{@code getInputDevice()}</dt>
|
|
<dd>Mirrors {@link android.hardware.input.InputManager#getInputDevice(int)
|
|
getInputDevice()}. Obtains the {@link android.view.InputDevice}
|
|
object that represents the capabilities of a game controller.</dd>
|
|
<dt>{@code getInputDeviceIds()}</dt>
|
|
<dd>Mirrors {@link android.hardware.input.InputManager#getInputDeviceIds()
|
|
getInputDeviceIds()}. Returns an array of integers, each of
|
|
which is an ID for a different input device. This is useful if you're building
|
|
a game that supports multiple players and you want to detect how many
|
|
controllers are connected.</dd>
|
|
<dt>{@code registerInputDeviceListener()}</dt>
|
|
<dd>Mirrors {@link android.hardware.input.InputManager#registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler)
|
|
registerInputDeviceListener()}. Lets you register to be informed when a new
|
|
device is added, changed, or removed.</dd>
|
|
<dt>{@code unregisterInputDeviceListener()}</dt>
|
|
<dd>Mirrors {@link android.hardware.input.InputManager#unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener) unregisterInputDeviceListener()}.
|
|
Unregisters an input device listener.</dd>
|
|
<dt>{@code onGenericMotionEvent()}</dt>
|
|
<dd>Mirrors {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent)
|
|
onGenericMotionEvent()}. Lets your game intercept and handle
|
|
{@link android.view.MotionEvent} objects and axis values that represent events
|
|
such as joystick movements and analog trigger presses.</dd>
|
|
<dt>{@code onPause()}</dt>
|
|
<dd>Stops polling for game controller events when the
|
|
main activity is paused, or when the game no longer has focus.</dd>
|
|
<dt>{@code onResume()}</dt>
|
|
<dd>Starts polling for game controller events when the
|
|
main activity is resumed, or when the game is started and runs in the
|
|
foreground.</dd>
|
|
<dt>{@code InputDeviceListener}</dt>
|
|
<dd>Mirrors the {@link android.hardware.input.InputManager.InputDeviceListener}
|
|
interface. Lets your game know when a game controller has been added, changed, or
|
|
removed.</dd>
|
|
</dl>
|
|
<p>Next, create implementations for {@code InputManagerCompat} that work
|
|
across different platform versions. If your game is running on Android 4.1 or
|
|
higher and calls an {@code InputManagerCompat} method, the proxy implementation
|
|
calls the equivalent method in {@link android.hardware.input.InputManager}.
|
|
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
|
|
only APIs introduced no later than Android 2.3. Regardless of which
|
|
version-specific implementation is used at runtime, the implementation passes
|
|
the call results back transparently to the game.</p>
|
|
|
|
<img src="{@docRoot}images/training/backward-compatible-inputmanager.png" alt=""
|
|
id="figure1" />
|
|
<p class="img-caption">
|
|
<strong>Figure 1.</strong> Class diagram of interface and version-specific
|
|
implementations.
|
|
</p>
|
|
|
|
<h2 id="newer">Implement the Interface on Android 4.1 and Higher</h2>
|
|
<p>{@code InputManagerCompatV16} is an implementation of the
|
|
{@code InputManagerCompat} interface that proxies method calls to an
|
|
actual {@link android.hardware.input.InputManager} and {@link
|
|
android.hardware.input.InputManager.InputDeviceListener}. The
|
|
{@link android.hardware.input.InputManager} is obtained from the system
|
|
{@link android.content.Context}.</p>
|
|
|
|
<pre>
|
|
// The InputManagerCompatV16 class is a reference implementation.
|
|
// The full code is provided in the ControllerSample.zip sample.
|
|
public class InputManagerV16 implements InputManagerCompat {
|
|
|
|
private final InputManager mInputManager;
|
|
private final Map<InputManagerCompat.InputDeviceListener,
|
|
V16InputDeviceListener> mListeners;
|
|
|
|
public InputManagerV16(Context context) {
|
|
mInputManager = (InputManager)
|
|
context.getSystemService(Context.INPUT_SERVICE);
|
|
mListeners = new HashMap<InputManagerCompat.InputDeviceListener,
|
|
V16InputDeviceListener>();
|
|
}
|
|
|
|
@Override
|
|
public InputDevice getInputDevice(int id) {
|
|
return mInputManager.getInputDevice(id);
|
|
}
|
|
|
|
@Override
|
|
public int[] getInputDeviceIds() {
|
|
return mInputManager.getInputDeviceIds();
|
|
}
|
|
|
|
static class V16InputDeviceListener implements
|
|
InputManager.InputDeviceListener {
|
|
final InputManagerCompat.InputDeviceListener mIDL;
|
|
|
|
public V16InputDeviceListener(InputDeviceListener idl) {
|
|
mIDL = idl;
|
|
}
|
|
|
|
@Override
|
|
public void onInputDeviceAdded(int deviceId) {
|
|
mIDL.onInputDeviceAdded(deviceId);
|
|
}
|
|
|
|
// Do the same for device change and removal
|
|
...
|
|
}
|
|
|
|
@Override
|
|
public void registerInputDeviceListener(InputDeviceListener listener,
|
|
Handler handler) {
|
|
V16InputDeviceListener v16Listener = new
|
|
V16InputDeviceListener(listener);
|
|
mInputManager.registerInputDeviceListener(v16Listener, handler);
|
|
mListeners.put(listener, v16Listener);
|
|
}
|
|
|
|
// Do the same for unregistering an input device listener
|
|
...
|
|
|
|
@Override
|
|
public void onGenericMotionEvent(MotionEvent event) {
|
|
// unused in V16
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
// unused in V16
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
// unused in V16
|
|
}
|
|
|
|
}
|
|
</pre>
|
|
|
|
<h2 id="older">Implementing the Interface on Android 2.3 up to Android 4.0</h2>
|
|
|
|
<p>The {@code InputManagerV9} implementation uses APIs introduced no later
|
|
than Android 2.3. To create an implementation of {@code
|
|
InputManagerCompat} that supports Android 2.3 up to Android 4.0, you can use
|
|
the following objects:
|
|
<ul>
|
|
<li>A {@link android.util.SparseArray} of device IDs to track the
|
|
game controllers that are connected to the device.</li>
|
|
<li>A {@link android.os.Handler} to process device events. When an app is started
|
|
or resumed, the {@link android.os.Handler} receives a message to start polling
|
|
for game controller disconnection. The {@link android.os.Handler} will start a
|
|
loop to check each known connected game controller and see if a device ID is
|
|
returned. A {@code null} return value indicates that the game controller is
|
|
disconnected. The {@link android.os.Handler} stops polling when the app is
|
|
paused.</li>
|
|
<li>A {@link java.util.Map} of {@code InputManagerCompat.InputDeviceListener}
|
|
objects. You will use the listeners to update the connection status of tracked
|
|
game controllers.</li>
|
|
</ul>
|
|
|
|
<pre>
|
|
// The InputManagerCompatV9 class is a reference implementation.
|
|
// The full code is provided in the ControllerSample.zip sample.
|
|
public class InputManagerV9 implements InputManagerCompat {
|
|
private final SparseArray<long[]> mDevices;
|
|
private final Map<InputDeviceListener, Handler> mListeners;
|
|
private final Handler mDefaultHandler;
|
|
…
|
|
|
|
public InputManagerV9() {
|
|
mDevices = new SparseArray<long[]>();
|
|
mListeners = new HashMap<InputDeviceListener, Handler>();
|
|
mDefaultHandler = new PollingMessageHandler(this);
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>Implement a {@code PollingMessageHandler} object that extends
|
|
{@link android.os.Handler}, and override the
|
|
{@link android.os.Handler#handleMessage(android.os.Message) handleMessage()}
|
|
method. This method checks if an attached game controller has been
|
|
disconnected and notifies registered listeners.</p>
|
|
|
|
<pre>
|
|
private static class PollingMessageHandler extends Handler {
|
|
private final WeakReference<InputManagerV9> mInputManager;
|
|
|
|
PollingMessageHandler(InputManagerV9 im) {
|
|
mInputManager = new WeakReference<InputManagerV9>(im);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
super.handleMessage(msg);
|
|
switch (msg.what) {
|
|
case MESSAGE_TEST_FOR_DISCONNECT:
|
|
InputManagerV9 imv = mInputManager.get();
|
|
if (null != imv) {
|
|
long time = SystemClock.elapsedRealtime();
|
|
int size = imv.mDevices.size();
|
|
for (int i = 0; i < size; i++) {
|
|
long[] lastContact = imv.mDevices.valueAt(i);
|
|
if (null != lastContact) {
|
|
if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
|
|
// check to see if the device has been
|
|
// disconnected
|
|
int id = imv.mDevices.keyAt(i);
|
|
if (null == InputDevice.getDevice(id)) {
|
|
// Notify the registered listeners
|
|
// that the game controller is disconnected
|
|
...
|
|
imv.mDevices.remove(id);
|
|
} else {
|
|
lastContact[0] = time;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
|
|
CHECK_ELAPSED_TIME);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>To start and stop polling for game controller disconnection, override
|
|
these methods:</p>
|
|
<pre>
|
|
private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
|
|
private static final long CHECK_ELAPSED_TIME = 3000L;
|
|
|
|
@Override
|
|
public void onPause() {
|
|
mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
mDefaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
|
|
CHECK_ELAPSED_TIME);
|
|
}
|
|
</pre>
|
|
|
|
<p>To detect that an input device has been added, override the
|
|
{@code onGenericMotionEvent()} method. When the system reports a motion event,
|
|
check if this event came from a device ID that is already tracked, or from a
|
|
new device ID. If the device ID is new, notify registered listeners.</p>
|
|
|
|
<pre>
|
|
@Override
|
|
public void onGenericMotionEvent(MotionEvent event) {
|
|
// detect new devices
|
|
int id = event.getDeviceId();
|
|
long[] timeArray = mDevices.get(id);
|
|
if (null == timeArray) {
|
|
// Notify the registered listeners that a game controller is added
|
|
...
|
|
timeArray = new long[1];
|
|
mDevices.put(id, timeArray);
|
|
}
|
|
long time = SystemClock.elapsedRealtime();
|
|
timeArray[0] = time;
|
|
}
|
|
</pre>
|
|
|
|
<p>Notification of listeners is implemented by using the
|
|
{@link android.os.Handler} object to send a {@code DeviceEvent}
|
|
{@link java.lang.Runnable} object to the message queue. The {@code DeviceEvent}
|
|
contains a reference to an {@code InputManagerCompat.InputDeviceListener}. When
|
|
the {@code DeviceEvent} runs, the appropriate callback method of the listener
|
|
is called to signal if the game controller was added, changed, or removed.
|
|
</p>
|
|
|
|
<pre>
|
|
@Override
|
|
public void registerInputDeviceListener(InputDeviceListener listener,
|
|
Handler handler) {
|
|
mListeners.remove(listener);
|
|
if (handler == null) {
|
|
handler = mDefaultHandler;
|
|
}
|
|
mListeners.put(listener, handler);
|
|
}
|
|
|
|
@Override
|
|
public void unregisterInputDeviceListener(InputDeviceListener listener) {
|
|
mListeners.remove(listener);
|
|
}
|
|
|
|
private void notifyListeners(int why, int deviceId) {
|
|
// the state of some device has changed
|
|
if (!mListeners.isEmpty()) {
|
|
for (InputDeviceListener listener : mListeners.keySet()) {
|
|
Handler handler = mListeners.get(listener);
|
|
DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId,
|
|
listener);
|
|
handler.post(odc);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class DeviceEvent implements Runnable {
|
|
private int mMessageType;
|
|
private int mId;
|
|
private InputDeviceListener mListener;
|
|
private static Queue<DeviceEvent> sObjectQueue =
|
|
new ArrayDeque<DeviceEvent>();
|
|
...
|
|
|
|
static DeviceEvent getDeviceEvent(int messageType, int id,
|
|
InputDeviceListener listener) {
|
|
DeviceEvent curChanged = sObjectQueue.poll();
|
|
if (null == curChanged) {
|
|
curChanged = new DeviceEvent();
|
|
}
|
|
curChanged.mMessageType = messageType;
|
|
curChanged.mId = id;
|
|
curChanged.mListener = listener;
|
|
return curChanged;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
switch (mMessageType) {
|
|
case ON_DEVICE_ADDED:
|
|
mListener.onInputDeviceAdded(mId);
|
|
break;
|
|
case ON_DEVICE_CHANGED:
|
|
mListener.onInputDeviceChanged(mId);
|
|
break;
|
|
case ON_DEVICE_REMOVED:
|
|
mListener.onInputDeviceRemoved(mId);
|
|
break;
|
|
default:
|
|
// Handle unknown message type
|
|
...
|
|
break;
|
|
}
|
|
// Put this runnable back in the queue
|
|
sObjectQueue.offer(this);
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>You now have two implementations of {@code InputManagerCompat}: one that
|
|
works on devices running Android 4.1 and higher, and another
|
|
that works on devices running Android 2.3 up to Android 4.0.</p>
|
|
|
|
<h2 id="using">Use the Version-Specific Implementation</h2>
|
|
<p>The version-specific switching logic is implemented in a class that acts as
|
|
a <a href="http://en.wikipedia.org/wiki/Factory_(software_concept)"
|
|
class="external-link" target="_blank">factory</a>.</p>
|
|
<pre>
|
|
public static class Factory {
|
|
public static InputManagerCompat getInputManager(Context context) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
|
return new InputManagerV16(context);
|
|
} else {
|
|
return new InputManagerV9();
|
|
}
|
|
}
|
|
}
|
|
</pre>
|
|
<p>Now you can simply instantiate an {@code InputManagerCompat} object and
|
|
register an {@code InputManagerCompat.InputDeviceListener} in your main
|
|
{@link android.view.View}. Because of the version-switching logic you set
|
|
up, your game automatically uses the implementation that's appropriate for the
|
|
version of Android the device is running.</p>
|
|
<pre>
|
|
public class GameView extends View implements InputDeviceListener {
|
|
private InputManagerCompat mInputManager;
|
|
...
|
|
|
|
public GameView(Context context, AttributeSet attrs) {
|
|
mInputManager =
|
|
InputManagerCompat.Factory.getInputManager(this.getContext());
|
|
mInputManager.registerInputDeviceListener(this, null);
|
|
...
|
|
}
|
|
}
|
|
</pre>
|
|
<p>Next, override the
|
|
{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent)
|
|
onGenericMotionEvent()} method in your main view, as described in
|
|
<a href="controller-input.html#analog">Handle a MotionEvent from a Game
|
|
Controller</a>. Your game should now be able to process game controller events
|
|
consistently on devices running Android 2.3 (API level 9) and higher.
|
|
<p>
|
|
<pre>
|
|
@Override
|
|
public boolean onGenericMotionEvent(MotionEvent event) {
|
|
mInputManager.onGenericMotionEvent(event);
|
|
|
|
// Handle analog input from the controller as normal
|
|
...
|
|
return super.onGenericMotionEvent(event);
|
|
}
|
|
</pre>
|
|
<p>You can find a complete implementation of this compatibility code in the
|
|
{@code GameView} class provided in the sample {@code ControllerSample.zip}
|
|
available for download above.</p> |