Mendukung pengontrol di seluruh versi Android

Jika game Anda mendukung pengontrol game, Anda bertanggung jawab untuk memastikan bahwa game merespons pengontrol secara konsisten di seluruh perangkat yang menjalankan versi Android yang berbeda. Dengan begitu, game Anda dapat menjangkau audiens yang lebih luas, dan pemain dapat menikmati pengalaman gameplay terbaik dengan pengontrolnya, bahkan saat mereka mengganti atau mengupgrade perangkat Android.

Tutorial ini menunjukkan cara menggunakan API yang tersedia di Android 4.1 dan versi yang lebih tinggi dengan cara yang kompatibel dengan versi sebelumnya sehingga game Anda dapat mendukung fitur berikut di perangkat yang menjalankan Android 3.1 dan versi yang lebih tinggi:

  • Game dapat mendeteksi saat ada pengontrol game baru yang ditambahkan, diubah, atau dihapus.
  • Game dapat mengkueri kemampuan pengontrol game.
  • Game dapat mengenali peristiwa gerakan yang akan terjadi dari pengontrol game.

Contoh di tutorial ini didasarkan pada implementasi referensi yang diberikan oleh controh ControllerSample.zip yang tersedia untuk didownload di atas. Contoh ini menunjukkan cara mengimplementasikan antarmuka InputManagerCompat untuk mendukung berbagai versi Android. Untuk mengompilasi contoh, Anda harus menggunakan Android 4.1 (API level 16) atau versi yang lebih tinggi. Setelah dikompilasi, aplikasi contoh berjalan di semua perangkat yang menjalankan Android 3.1 (API level 12) atau versi yang lebih tinggi sebagai target build.

Menyiapkan abstraksi API untuk dukungan pengontrol game

Misalnya, Anda ingin agar dapat menentukan saat status sambungan pengontrol game telah berubah di perangkat yang berjalan di Android 3.1 (API level 12). Namun, API hanya tersedia di Android 4.1 (API level 16) dan versi yang lebih tinggi, jadi Anda perlu memberikan implementasi yang mendukung Android 4.1 dan versi yang lebih tinggi, selagi memberikan mekanisme penggantian yang mendukung Android 3.1 hingga Android 4.0.

Untuk membantu Anda menentukan fitur mana yang memerlukan mekanisme penggantian semacam itu untuk versi yang lebih lama, tabel 1 mencantumkan perbedaan dalam dukungan pengontrol game antara Android 3.1 (API level 12) dan 4.1 (API level 16).

Tabel 1. API untuk dukungan pengontrol game di versi Android yang berbeda.

Informasi Pengontrol Controller API API level 12 API level 16
Identifikasi Perangkat getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
Status Sambungan onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Identifikasi Peristiwa Input Penekanan D-pad ( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER)
Penekanan tombol gamepad ( BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1, BUTTON_L1, BUTTON_R2, BUTTON_L2)
Pergerakan joystick dan hat switch ( AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y)
Penekanan pemicu analog ( AXIS_LTRIGGER, AXIS_RTRIGGER)

Anda dapat menggunakan abstraksi untuk membuat dukungan pengontrol game berbasis versi yang dapat berfungsi di seluruh platform. Pendekatan ini mencakup langkah-langkah berikut:

  1. Tentukan antarmuka Java perantara yang mengabstraksikan implementasi fitur pengontrol game yang diperlukan oleh game Anda.
  2. Buat implementasi proxy antarmuka Anda menggunakan API di Android versi 4.1 dan versi yang lebih tinggi.
  3. Buat implementasi kustom antarmuka Anda menggunakan API yang tersedia di antara Android 3.1 hingga Android 4.0.
  4. Buat logika untuk peralihan antar-implementasi ini pada waktu proses, dan mulai gunakan antarmuka tersebut di game Anda.

Untuk ringkasan terkait penggunaan abstraksi untuk memastikan bahwa aplikasi kompatibel dengan semua versi Android sebelumnya, buka Membuat UI yang Kompatibel dengan Versi Sebelumnya.

Menambahkan antarmuka untuk kompatibilitas mundur

Untuk memberikan kompatibilitas mundur, Anda dapat membuat antarmuka kustom, kemudian menambahkan implementasi khusus versi. Salah satu keuntungan pendekatan ini adalah, pendekatan ini memungkinkan Anda mencerminkan antarmuka publik di Android 4.1 (API level 16) yang mendukung pengontrol game.

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

Antarmuka InputManagerCompat memberikan metode berikut:

getInputDevice()
Mencerminkan getInputDevice(). Mendapatkan objek InputDevice yang mewakili kemampuan pengontrol game.
getInputDeviceIds()
Mencerminkan getInputDeviceIds(). Menampilkan array integer, yang masing-masing merupakan ID untuk perangkat input yang berbeda. Metode ini berguna jika Anda membuat game yang mendukung multiplayer dan Anda ingin mendeteksi jumlah pengontrol yang tersambung.
registerInputDeviceListener()
Mencerminkan registerInputDeviceListener(). Memungkinkan Anda mendaftar agar diberi tahu saat ada perangkat baru yang ditambahkan, diubah, atau dihapus.
unregisterInputDeviceListener()
Mencerminkan unregisterInputDeviceListener(). Membatalkan pendaftaran pemroses perangkat input.
onGenericMotionEvent()
Mencerminkan onGenericMotionEvent(). Memungkinkan game Anda mencegat dan menangani objek MotionEvent dan nilai sumbu yang mewakili peristiwa seperti pergerakan joystick dan penekanan trigger analog.
onPause()
Menghentikan polling peristiwa pengontrol game saat aktivitas utama dijeda, atau saat game tidak lagi memiliki fokus.
onResume()
Memulai polling peristiwa pengontrol game saat aktivitas utama dilanjutkan, atau saat game dimulai dan berjalan di latar depan.
InputDeviceListener
Mencerminkan antarmuka InputManager.InputDeviceListener. Memungkinkan game Anda mengetahui saat ada pengontrol game yang ditambahkan, diubah, atau dihapus.

Selanjutnya, buat implementasi untuk InputManagerCompat yang dapat berfungsi di berbagai versi platform. Jika game Anda berjalan di Android 4.1 atau yang lebih tinggi dan memanggil metode InputManagerCompat, implementasi proxy akan memanggil metode yang setara di InputManager. Namun, jika game Anda berjalan di Android 3.1 hingga Android 4.0, proses implementasi kustom akan memanggil metode InputManagerCompat dengan menggunakan API yang diperkenalkan di Android dengan versi yang tidak lebih baru dari 3.1 saja. Terlepas dari implementasi khusus versi mana pun yang digunakan pada waktu proses, implementasi tersebut akan meneruskan kembali hasil panggilan secara transparan ke game.

Gambar 1. Diagram class antarmuka dan implementasi khusus versi.

Menerapkan antarmuka di Android 4.1 dan versi yang lebih tinggi

InputManagerCompatV16 merupakan implementasi antarmuka InputManagerCompat yang mewakili panggilan metode ke InputManager dan InputManager.InputDeviceListener yang sebenarnya. InputManager diperoleh dari Context sistem.

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
    }

}

Mengimplementasikan antarmuka di Android 3.1 hingga Android 4.0

Untuk membuat implementasi InputManagerCompat yang mendukung Android 3.1 hingga Android 4.0, Anda dapat menggunakan objek berikut:

  • SparseArray ID perangkat untuk melacak pengontrol game yang tersambung ke perangkat.
  • Handler untuk memproses peristiwa perangkat. Saat aplikasi dimulai atau dilanjutkan, Handler menerima pesan untuk memulai polling pemutusan sambungan pengontrol game. Kode Handler akan memulai loop untuk memeriksa setiap pengontrol game tersambung yang dikenali dan melihat apakah ID perangkat ditampilkan. Nilai ditampilkan yang berupa null menunjukkan bahwa pengontrol game terputus sambungannya. Handler menghentikan polling saat aplikasi dijeda.
  • Map objek InputManagerCompat.InputDeviceListener. Anda akan menggunakan pemroses untuk memperbarui status sambungan pengontrol game yang dilacak.

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

Menerapkan objek PollingMessageHandler yang memperluas Handler, dan menggantikan metode handleMessage(). Metode ini memeriksa apakah pengontrol game yang terpasang terputus sambungannya dan memberi tahu pemroses yang terdaftar.

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

Guna memulai dan menghentikan polling untuk pemutusan sambungan pengontrol game, ganti metode ini:

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

Untuk mendeteksi saat ada perangkat input yang telah ditambahkan, ganti metode onGenericMotionEvent(). Saat sistem melaporkan peristiwa gerakan, periksa apakah peristiwa ini berasal dari ID perangkat yang telah dilacak, atau dari ID perangkat yang baru. Jika berasal dari ID perangkat baru, beri tahu pemroses yang terdaftar.

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

Notifikasi pemroses diimplementasikan dengan menggunakan objek Handler untuk mengirim objek DeviceEvent Runnable ke antrean pesan. DeviceEvent berisi referensi ke InputManagerCompat.InputDeviceListener. Saat DeviceEvent berjalan, metode callback pemroses yang sesuai dipanggil untuk menandakan saat ada pengontrol game yang ditambahkan, diubah, atau dihapus.

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

Kini, Anda memiliki dua implementasi InputManagerCompat: satu berfungsi di perangkat yang menjalankan Android 4.1 dan versi yang lebih tinggi, dan satu lagi berfungsi di perangkat yang menjalankan Android 3.1 hingga Android 4.0.

Menggunakan implementasi khusus versi

Logika peralihan khusus versi diimplementasikan di class yang berperan sebagai factory.

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

Kini Anda cukup membuat instance objek InputManagerCompat dan mendaftarkan InputManagerCompat.InputDeviceListener di View utama Anda. Karena logika peralihan versi yang disiapkan, game Anda akan secara otomatis menggunakan implementasi yang sesuai untuk versi Android yang dijalankan perangkat.

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

Selanjutnya, ganti metode onGenericMotionEvent() di tampilan utama Anda, seperti yang dijelaskan di Menangani MotionEvent dari Pengontrol Game. Kini game Anda seharusnya dapat memproses peristiwa pengontrol game secara konsisten di perangkat yang menjalankan Android 3.1 (API level 12) dan versi yang lebih tinggi.

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

Anda dapat menemukan implementasi lengkap dari kode kompatibilitas ini di class GameView yang disediakan di contoh ControllerSample.zip, tersedia untuk didownload di atas.