Android поддерживает USB клавиатуры, мыши и джойстики. Статья содержит небольшое исследование возможностей Android и описание работы с такими устройствами.

Эксперименты проводил с Nexus 5X с переходником из USB-C в маму USB-A (то есть как на копьютере, что дает возможность попробовать подключить любую компьютерную периферию), и на Raspbery Pi с установленным Android Things (об этом звере в ближайшее время напишу отдельную статью). Подопытной периферией были: беспроводная клавиатура с мышью с общим USB-ресивером, и проводной джойстик Thrustmaster.

Подключение

Итак. Достаточно подключить любое из этих устройств к USB, чтобы Android их увидел.

При подключении мыши и клавиатуры меняется конфигурация:

Если вы замечали в списке квалификаторов nonav, dpad, nokeys, qwerty, то это оно. Что означает, что при подключении или отключении мыши или клавиатуры по умолчанию будет пересоздана Activity. Для того, чтобы обработать изменение конфигурации самому, потребуются следующие флаги:

<activity
    ....
    android:configChanges="keyboard|keyboardHidden|navigation"
    />

Подключение джойстика изменение конфигурации не вызывает. Но он также виден системой, и его наклоны обрабатываются пользовательским интерфейсом.

Диагностика

Получить список всех подключенных устройств ввода можно командой

adb shell dumpsys input

Ее вывод описан в документации.

Использование

После подключения мыши на экране телефона появилась стандартная мышиная стрелочка, мышь вдет себя ожидаемо, даже работает прокрутка списков колесиком. Стандартные элементы интерфейса при прохождении мышки над ними визуально это показывают, что удобно.

При подключении клавиатуры появляется уведомление, котороя ведет к настройкам и подсказке. Клавиша, в подсказке обозначенная как лупа, — это клавиша с логотипом Microsoft (третий скриншот). На клавиатуре работают даже виртуальные клавиши вроде «Play», «Next», регулировка громкости.

Screenshot 1 Screenshot 2 Screenshot 3

Единственная странность — раскладки требовали донастройки. Изначально язык переключался, но буквы в обоих случаях оставались английскими. В настройках клавиатуры (второй скриншот) при клике на каждый из пунктов, соответствующий языку, предлагается выбрать раскладку. После того, как для русского языка я выбрал русскую раскладку, переключение языков стало работать, как и положено.

Стрелки на клавиатуре используются в интерфейсе. Забавно, что часть повседневных операций с клавиатуры делать удобно, а часть — невозможно. В приложении GMail, например, удобно просматривать письма, но у меня так и не получилось удалить сообщение. Яндекс.Музыка с клавиатуры почти не управляется, а жаль.

Обработка

Приложение всегда может получить список устройств:

int[] deviceIds = InputDevice.getDeviceIds();
for (int deviceId : deviceIds) {
    InputDevice dev = InputDevice.getDevice(deviceId);
 
    Log.d("happy", dev.toString());
}

В этот список попадут все устройства, включая точскрин и виртуальную клавиатуру. Приложение может получить достаточно подробную информацию о каждом устройстве, включая список осей и диапазон значений у них.

Чтобы перехватить все события-перемещения от внешних устройств ввода в Activity, нужно переопределить два метода: dispatchTouchEvent() и dispatchGenericMotionEvent(). В первый из них попадает все, что может трактоваться как касания интерфейса. То есть передвижения мыши с нажатой кнопкой попадают в dispatchTouchEvent(). Во второй попадают все остальные события-движения. В частности, в dispatchGenericMotionEvent() попадут перемещения мыши без нажатой кнопки (так называемый hover) и все перемещения джойстика. Прокрутка колесиска мышки попадет с разными action в оба этих метода. Аналогично — нажатие на колесико.

Кнопки клавиатуры и джойстика можно перехватить в методе dispatchKeyEvent(). Джойстик передает события о нажатиях для всех свои 12 кнопок. Клавиатура — для всех кнопок, плюс виртуальные кнопки вроде «play/pause», «next track», «volume up».

Обратите внимание, что event.getDevice() может вернуть null. Это случается, если устройство ввода уже отключено, а событие еще обрабатывается в UI.

Для экспериментаторов:

void printMotionEvent(MotionEvent event, String prefix) {
    int source = event.getSource();
    Log.d("happy", prefix + " source " + source);
    InputDevice d = event.getDevice();
    if (d != null) {
         List<InputDevice.MotionRange> ranges = d.getMotionRanges();
        for (InputDevice.MotionRange r : ranges) {
            Log.d("happy", prefix + " " + MotionEvent.axisToString(r.getAxis())
                    + ": "
                    + event.getAxisValue(r.getAxis()));            }
    } else {
        Log.d("happy", prefix + " device is null");
    }
}
 
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    printMotionEvent(ev, "MainActivity.dispatchTouchEvent");
    return true;
}
 
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
    printMotionEvent(ev, "MainActivity.dispatchGenericMotionEvent");
    return true;
}
 
@Override
public boolean dispatchTrackballEvent(MotionEvent ev) {
    printMotionEvent(ev, "MainActivity.dispatchTrackballEvent");
    return true;
}
 
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    Log.d("happy", "dispatchKeyEvent");
    Log.d("happy", event.toString());
 
    InputDevice d = event.getDevice();
    if (d != null) {
        Log.d("happy", d.getName());
    } else {
        Log.d("happy", "device is null");
    }
    return true;
}

Итог

Легкая интеграция с внешними устройствами дает новые возможности как в повседневном использовании, так и в разработке новых решений.

Наличие мыши и клавиатуры на устройстве оказалось более реальным, чем я предполагал. Для меня это значит, что нужно не игнорировать состояния hovered и selected при разработке цветовых схем и создании кастомных вьюх. Также нужно проверять, можно ли выполнить хотя бы основной сценарий приложения с помощью стрелок, поскольку различных геймпадов и прочих устройств с кнопками будет становиться все больше.

Возможность использовать мышь — хорошая находка. Устройство с приемником от мышки можно спрятать в пакет, прозрачную коробку или просто оставить за стеклом автомобиля. Это сильно облегчает использование любого устройства в особо грязных условиях: дождь, поход, авторемонт. Подумаю, может быть в автомобиль куплю трекбол: крутить шарик проще, чтобы попадать пальцем в трясущемся автомобиле в нужное место точскрина.

Неожиданный вывод — нужно делать поддержку устройств с клавиатурой. Мы никогда не обращали на этот тип устройств внимание.

Поддержка джойстика прадовала тем, что легко можно получить орган управления для различных роботизированных устройств.

У вас есть еще идеи? Поделитесь.

Ссылки