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.

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 3.1 and higher:

- The game can detect if a new game controller is added, changed, or removed.
- The game can query the capabilities of a game controller.
- The game can recognize incoming motion events from a game controller.

## Prepare to abstract APIs for game controller support

Suppose you want to be able to determine if a game controller's connection
status has changed on devices running on Android 3.1 (API level 12). 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 3.1 up to Android 4.0.

To help you determine which features require a fallback mechanism for lower
versions, Table 1 lists the differences in game controller support between
Android 3.1 (API level 12) and 4.1 (API level 16).


**Table 1.** APIs for game controller support across
different Android versions.

| Controller Information | Controller API | API level 12 | API level 16 |
|---|---|---|---|
| Device Identification | [getInputDeviceIds()](https://developer.android.com/reference/android/hardware/input/InputManager#getInputDeviceIds()) |   | • |
| Device Identification | [getInputDevice()](https://developer.android.com/reference/android/hardware/input/InputManager#getInputDevice(int)) |   | • |
| Device Identification | [getVibrator()](https://developer.android.com/reference/android/view/InputDevice#getVibrator()) |   | • |
| Device Identification | [SOURCE_JOYSTICK](https://developer.android.com/reference/android/view/InputDevice#SOURCE_JOYSTICK) | • | • |
| Device Identification | [SOURCE_GAMEPAD](https://developer.android.com/reference/android/view/InputDevice#SOURCE_GAMEPAD) | • | • |
| Connection Status | [onInputDeviceAdded()](https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener#onInputDeviceAdded(int)) |   | • |
| Connection Status | [onInputDeviceChanged()](https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener#onInputDeviceChanged(int)) |   | • |
| Connection Status | [onInputDeviceRemoved()](https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener#onInputDeviceRemoved(int)) |   | • |
| Input Event Identification | D-pad press ( [KEYCODE_DPAD_UP](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_UP), [KEYCODE_DPAD_DOWN](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_DOWN), [KEYCODE_DPAD_LEFT](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_LEFT), [KEYCODE_DPAD_RIGHT](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_RIGHT), [KEYCODE_DPAD_CENTER](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_CENTER)) | • | • |
| Input Event Identification | Gamepad button press ( [BUTTON_A](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_A), [BUTTON_B](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_B), [BUTTON_THUMBL](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_THUMBL), [BUTTON_THUMBR](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_THUMBR), [BUTTON_SELECT](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_SELECT), [BUTTON_START](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_START), [BUTTON_R1](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_R1), [BUTTON_L1](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_L1), [BUTTON_R2](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_R2), [BUTTON_L2](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_L2)) | • | • |
| Input Event Identification | Joystick and hat switch movement ( [AXIS_X](https://developer.android.com/reference/android/view/MotionEvent#AXIS_X), [AXIS_Y](https://developer.android.com/reference/android/view/MotionEvent#AXIS_Y), [AXIS_Z](https://developer.android.com/reference/android/view/MotionEvent#AXIS_Z), [AXIS_RZ](https://developer.android.com/reference/android/view/MotionEvent#AXIS_RZ), [AXIS_HAT_X](https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_X), [AXIS_HAT_Y](https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_Y)) | • | • |
| Input Event Identification | Analog trigger press ( [AXIS_LTRIGGER](https://developer.android.com/reference/android/view/MotionEvent#AXIS_LTRIGGER), [AXIS_RTRIGGER](https://developer.android.com/reference/android/view/MotionEvent#AXIS_RTRIGGER)) | • | • |

You can use abstraction to build version-aware game controller support that
works across platforms. This approach involves the following steps:

1. Define an intermediary Java interface that abstracts the implementation of the game controller features required by your game.
2. Create a proxy implementation of your interface that uses APIs in Android 4.1 and higher.
3. Create a custom implementation of your interface that uses APIs available between Android 3.1 up to Android 4.0.
4. Create the logic for switching between these implementations at runtime, and begin using the interface in your game.

For an overview of how abstraction can be used to verify that applications can
work in a backward compatible way across different versions of Android, see
[Creating Backward-Compatible UIs](https://developer.android.com/training/backward-compatible-ui).

## Add an interface for backward compatibility

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.  

### Kotlin

    // The InputManagerCompat interface is a reference example.
    // The full code is provided in the ControllerSample.zip sample.
    interface InputManagerCompat {
        val inputDeviceIds: IntArray
        fun getInputDevice(id: Int): InputDevice

        fun registerInputDeviceListener(
                listener: InputManager.InputDeviceListener,
                handler: Handler?
        )

        fun unregisterInputDeviceListener(listener:InputManager.InputDeviceListener)

        fun onGenericMotionEvent(event: MotionEvent)

        fun onPause()
        fun onResume()

        interface InputDeviceListener {
            fun onInputDeviceAdded(deviceId: Int)
            fun onInputDeviceChanged(deviceId: Int)
            fun onInputDeviceRemoved(deviceId: Int)
        }
    }

### Java

    // 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);
        }
        ...
    }

The `InputManagerCompat` interface provides the following methods:

`getInputDevice()`
:   Mirrors [getInputDevice()](https://developer.android.com/reference/android/hardware/input/InputManager#getInputDevice(int)). Obtains the [InputDevice](https://developer.android.com/reference/android/view/InputDevice)
    object that represents the capabilities of a game controller.

`getInputDeviceIds()`
:   Mirrors [getInputDeviceIds()](https://developer.android.com/reference/android/hardware/input/InputManager#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.

`registerInputDeviceListener()`
:   Mirrors [registerInputDeviceListener()](https://developer.android.com/reference/android/hardware/input/InputManager#registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler)). Lets you register to be informed when a new
    device is added, changed, or removed.

`unregisterInputDeviceListener()`
:   Mirrors [unregisterInputDeviceListener()](https://developer.android.com/reference/android/hardware/input/InputManager#unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener)).
    Unregisters an input device listener.

`onGenericMotionEvent()`
:   Mirrors [onGenericMotionEvent()](https://developer.android.com/reference/android/view/View#onGenericMotionEvent(android.view.MotionEvent)). Lets your game intercept and handle
    [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) objects and axis values that represent events
    such as joystick movements and analog trigger presses.

`onPause()`
:   Stops polling for game controller events when the
    main activity is paused, or when the game no longer has focus.

`onResume()`
:   Starts polling for game controller events when the
    main activity is resumed, or when the game is started and runs in the
    foreground.

`InputDeviceListener`
:   Mirrors the [InputManager.InputDeviceListener](https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener)
    interface. Lets your game know when a game controller has been added, changed, or
    removed.

Next, create implementations for `InputManagerCompat` that work
across different platform versions. If your game is running on Android 4.1 or
higher and calls an `InputManagerCompat` method, the proxy implementation
calls the equivalent method in [InputManager](https://developer.android.com/reference/android/hardware/input/InputManager).
However, if your game is running on Android 3.1 up to Android 4.0, the custom implementation
processes calls to `InputManagerCompat` methods by using
only APIs introduced no later than Android 3.1. Regardless of which
version-specific implementation is used at runtime, the implementation passes
the call results back transparently to the game.
![Class diagram of interface and version-specific implementations.](https://developer.android.com/static/images/training/backward-compatible-inputmanager.png) **Figure 1.**: Class diagram of interface and version-specific implementations.

## Implement the interface on Android 4.1 and higher

`InputManagerCompatV16` is an implementation of the `InputManagerCompat`
interface that proxies method calls to an actual
[`InputManager`](https://developer.android.com/reference/android/hardware/input/InputManager) and
[`InputManager.InputDeviceListener`](https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener).
The [`InputManager`](https://developer.android.com/reference/android/hardware/input/InputManager) is
obtained from the system [`Context`](https://developer.android.com/reference/android/content/Context).  

### Kotlin

    // The InputManagerCompatV16 class is a reference implementation.
    // The full code is provided in the ControllerSample.zip sample.
    public class InputManagerV16(
            context: Context,
            private val inputManager: InputManager =
                context.getSystemService(Context.INPUT_SERVICE) as InputManager,
            private val listeners:
                MutableMap<InputManager.InputDeviceListener, V16InputDeviceListener> = mutableMapOf()
    ) : InputManagerCompat {
        override val inputDeviceIds: IntArray = inputManager.inputDeviceIds

        override fun getInputDevice(id: Int): InputDevice = inputManager.getInputDevice(id)

        override fun registerInputDeviceListener(
                listener: InputManager.InputDeviceListener,
                handler: Handler?
        ) {
            V16InputDeviceListener(listener).also { v16listener ->
                inputManager.registerInputDeviceListener(v16listener, handler)
                listeners += listener to v16listener
            }
        }

        // Do the same for unregistering an input device listener
        ...

        override fun onGenericMotionEvent(event: MotionEvent) {
            // unused in V16
        }

        override fun onPause() {
            // unused in V16
        }

        override fun onResume() {
            // unused in V16
        }

    }

    class V16InputDeviceListener(
            private val idl: InputManager.InputDeviceListener
    ) : InputManager.InputDeviceListener {

        override fun onInputDeviceAdded(deviceId: Int) {
            idl.onInputDeviceAdded(deviceId)
        }
        // Do the same for device change and removal
        ...
    }

### Java

    // 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 inputManager;
        private final Map<InputManagerCompat.InputDeviceListener,
                V16InputDeviceListener> listeners;

        public InputManagerV16(Context context) {
            inputManager = (InputManager)
                    context.getSystemService(Context.INPUT_SERVICE);
            listeners = new HashMap<InputManagerCompat.InputDeviceListener,
                    V16InputDeviceListener>();
        }

        @Override
        public InputDevice getInputDevice(int id) {
            return inputManager.getInputDevice(id);
        }

        @Override
        public int[] getInputDeviceIds() {
            return inputManager.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);
            inputManager.registerInputDeviceListener(v16Listener, handler);
            listeners.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
        }

    }

## Implement the interface on Android 3.1 up to Android 4.0

To create an implementation of `InputManagerCompat` that supports Android 3.1 up
to Android 4.0, you can use the following objects:

- A [`SparseArray`](https://developer.android.com/reference/android/util/SparseArray) of device IDs to track the game controllers that are connected to the device.
- A [`Handler`](https://developer.android.com/reference/android/os/Handler) to process device events. When an app is started or resumed, the [`Handler`](https://developer.android.com/reference/android/os/Handler) receives a message to start polling for game controller disconnection. The [`Handler`](https://developer.android.com/reference/android/os/Handler) will start a loop to check each known connected game controller and see if a device ID is returned. A `null` return value indicates that the game controller is disconnected. The [`Handler`](https://developer.android.com/reference/android/os/Handler) stops polling when the app is paused.
- A [`Map`](https://developer.android.com/reference/java/util/Map) of
  `InputManagerCompat.InputDeviceListener` objects. You will use the listeners
  to update the connection status of tracked game controllers.

### Kotlin

    // The InputManagerCompatV9 class is a reference implementation.
    // The full code is provided in the ControllerSample.zip sample.
    class InputManagerV9(
            val devices: SparseArray<Array<Long>> = SparseArray(),
            private val listeners:
            MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
    ) : InputManagerCompat {
        private val defaultHandler: Handler = PollingMessageHandler(this)
        ...
    }

### Java

    // 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[]> devices;
        private final Map<InputDeviceListener, Handler> listeners;
        private final Handler defaultHandler;
        ...

        public InputManagerV9() {
            devices = new SparseArray<long[]>();
            listeners = new HashMap<InputDeviceListener, Handler>();
            defaultHandler = new PollingMessageHandler(this);
        }
    }

Implement a `PollingMessageHandler` object that extends
[Handler](https://developer.android.com/reference/android/os/Handler), and override the
[handleMessage()](https://developer.android.com/reference/android/os/Handler#handleMessage(android.os.Message))
method. This method checks if an attached game controller has been
disconnected and notifies registered listeners.  

### Kotlin

    private class PollingMessageHandler(
            inputManager: InputManagerV9,
            private val mInputManager: WeakReference<InputManagerV9> = WeakReference(inputManager)
    ) : Handler() {

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                MESSAGE_TEST_FOR_DISCONNECT -> {
                    mInputManager.get()?.also { imv ->
                        val time = SystemClock.elapsedRealtime()
                        val size = imv.devices.size()
                        for (i in 0 until size) {
                            imv.devices.valueAt(i)?.also { lastContact ->
                                if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                    // check to see if the device has been
                                    // disconnected
                                    val id = imv.devices.keyAt(i)
                                    if (null == InputDevice.getDevice(id)) {
                                        // Notify the registered listeners
                                        // that the game controller is disconnected
                                        imv.devices.remove(id)
                                    } else {
                                        lastContact[0] = time
                                    }
                                }
                            }
                        }
                        sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
                    }
                }
            }
        }
    }

### Java

    private static class PollingMessageHandler extends Handler {
        private final WeakReference<InputManagerV9> inputManager;

        PollingMessageHandler(InputManagerV9 im) {
            inputManager = new WeakReference<InputManagerV9>(im);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MESSAGE_TEST_FOR_DISCONNECT:
                    InputManagerV9 imv = inputManager.get();
                    if (null != imv) {
                        long time = SystemClock.elapsedRealtime();
                        int size = imv.devices.size();
                        for (int i = 0; i < size; i++) {
                            long[] lastContact = imv.devices.valueAt(i);
                            if (null != lastContact) {
                                if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                    // check to see if the device has been
                                    // disconnected
                                    int id = imv.devices.keyAt(i);
                                    if (null == InputDevice.getDevice(id)) {
                                        // Notify the registered listeners
                                        // that the game controller is disconnected
                                        imv.devices.remove(id);
                                    } else {
                                        lastContact[0] = time;
                                    }
                                }
                            }
                        }
                        sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
                                CHECK_ELAPSED_TIME);
                    }
                    break;
            }
        }
    }

To start and stop polling for game controller disconnection, override
these methods:  

### Kotlin

    private const val MESSAGE_TEST_FOR_DISCONNECT = 101
    private const val CHECK_ELAPSED_TIME = 3000L

    class InputManagerV9(
            val devices: SparseArray<Array<Long>> = SparseArray(),
            private val listeners:
            MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
    ) : InputManagerCompat {
        ...
        override fun onPause() {
            defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT)
        }

        override fun onResume() {
            defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
        }
        ...
    }

### Java

    private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
    private static final long CHECK_ELAPSED_TIME = 3000L;

    @Override
    public void onPause() {
        defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
    }

    @Override
    public void onResume() {
        defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
                CHECK_ELAPSED_TIME);
    }

To detect that an input device has been added, override the
`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.  

### Kotlin

    override fun onGenericMotionEvent(event: MotionEvent) {
        // detect new devices
        val id = event.deviceId
        val timeArray: Array<Long> = mDevices.get(id) ?: run {
            // Notify the registered listeners that a game controller is added
            ...
            arrayOf<Long>().also {
                mDevices.put(id, it)
            }
        }
        timeArray[0] = SystemClock.elapsedRealtime()
    }

### Java

    @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;
    }

Notification of listeners is implemented by using the
[`Handler`](https://developer.android.com/reference/android/os/Handler) object to send a `DeviceEvent`
[`Runnable`](https://developer.android.com/reference/java/lang/Runnable) object to the message queue.
The `DeviceEvent` contains a reference to an
`InputManagerCompat.InputDeviceListener`. When the `DeviceEvent` runs, the
appropriate callback method of the listener is called to signal if the game
controller was added, changed, or removed.  

### Kotlin

    class InputManagerV9(
            val devices: SparseArray<Array<Long>> = SparseArray(),
            private val listeners:
            MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
    ) : InputManagerCompat {
        ...
        override fun registerInputDeviceListener(
                listener: InputManager.InputDeviceListener,
                handler: Handler?
        ) {
            listeners[listener] = handler ?: defaultHandler
        }

        override fun unregisterInputDeviceListener(listener: InputManager.InputDeviceListener) {
            listeners.remove(listener)
        }

        private fun notifyListeners(why: Int, deviceId: Int) {
            // the state of some device has changed
            listeners.forEach { listener, handler ->
                DeviceEvent.getDeviceEvent(why, deviceId, listener).also {
                    handler?.post(it)
                }
            }
        }
        ...
    }

    private val sObjectQueue: Queue<DeviceEvent> = ArrayDeque<DeviceEvent>()

    private class DeviceEvent(
            private var mMessageType: Int,
            private var mId: Int,
            private var mListener: InputManager.InputDeviceListener
    ) : Runnable {

        companion object {
            fun getDeviceEvent(messageType: Int, id: Int, listener: InputManager.InputDeviceListener) =
                    sObjectQueue.poll()?.apply {
                        mMessageType = messageType
                        mId = id
                        mListener = listener
                    } ?: DeviceEvent(messageType, id, listener)

        }

        override fun run() {
            when(mMessageType) {
                ON_DEVICE_ADDED -> mListener.onInputDeviceAdded(mId)
                ON_DEVICE_CHANGED -> mListener.onInputDeviceChanged(mId)
                ON_DEVICE_REMOVED -> mListener.onInputDeviceChanged(mId)
                else -> {
                    // Handle unknown message type
                }
            }
        }

    }

### Java

    @Override
    public void registerInputDeviceListener(InputDeviceListener listener,
            Handler handler) {
        listeners.remove(listener);
        if (handler == null) {
            handler = defaultHandler;
        }
        listeners.put(listener, handler);
    }

    @Override
    public void unregisterInputDeviceListener(InputDeviceListener listener) {
        listeners.remove(listener);
    }

    private void notifyListeners(int why, int deviceId) {
        // the state of some device has changed
        if (!listeners.isEmpty()) {
            for (InputDeviceListener listener : listeners.keySet()) {
                Handler handler = listeners.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);
        }
    }

You now have two implementations of `InputManagerCompat`: one that
works on devices running Android 4.1 and higher, and another
that works on devices running Android 3.1 up to Android 4.0.

## Use the version-specific implementation

The version-specific switching logic is implemented in a class that acts as
a [factory](https://en.wikipedia.org/wiki/Factory_(software_concept)).  

### Kotlin

    object Factory {
        fun getInputManager(context: Context): InputManagerCompat =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    InputManagerV16(context)
                } else {
                    InputManagerV9()
                }
    }

### Java

    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();
            }
        }
    }

Now you can instantiate an `InputManagerCompat` object and register an
`InputManagerCompat.InputDeviceListener` in your main
[`View`](https://developer.android.com/reference/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.  

### Kotlin

    class GameView(context: Context) : View(context), InputManager.InputDeviceListener {
        private val inputManager: InputManagerCompat = Factory.getInputManager(context).apply {
            registerInputDeviceListener(this@GameView, null)
            ...
        }
        ...
    }

### Java

    public class GameView extends View implements InputDeviceListener {
        private InputManagerCompat inputManager;
        ...

        public GameView(Context context, AttributeSet attrs) {
            inputManager =
                    InputManagerCompat.Factory.getInputManager(this.getContext());
            inputManager.registerInputDeviceListener(this, null);
            ...
        }
    }

Next, override the [`onGenericMotionEvent()`](https://developer.android.com/reference/android/view/View#onGenericMotionEvent(android.view.MotionEvent)) method in your main view, as
described in [Handle a MotionEvent from a Game
Controller](https://developer.android.com/games/sdk/game-controller/controller-input) and higher). Your game
should now be able to process game controller events consistently on devices
running Android 3.1 (API level).  

### Kotlin

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {
        inputManager.onGenericMotionEvent(event)

        // Handle analog input from the controller as normal
        ...
        return super.onGenericMotionEvent(event)
    }

### Java

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
        inputManager.onGenericMotionEvent(event);

        // Handle analog input from the controller as normal
        ...
        return super.onGenericMotionEvent(event);
    }

You can find a complete implementation of this compatibility code in the
`GameView` class provided in the sample `ControllerSample.zip` available for
download.