Когда устройство Android находится в режиме хоста USB, оно действует как хост USB, питает шину и перечисляет подключенные USB-устройства. Режим USB-хоста поддерживается в Android 3.1 и более поздних версиях.
Общие сведения об API
Прежде чем начать, важно понять, с какими классами вам нужно работать. В таблице ниже описаны API USB-хоста в пакете android.hardware.usb.
Таблица 1. USB Host API
| Класс | Описание |
|---|---|
UsbManager |
Позволяет перечислять подключенные USB-устройства и обмениваться с ними данными. |
UsbDevice |
Представляет подключенное USB-устройство и содержит методы для доступа к его идентифицирующей информации, интерфейсам и конечным точкам. |
UsbInterface |
Представляет интерфейс USB-устройства, который определяет набор функций для устройства. У устройства может быть один или несколько интерфейсов для связи. |
UsbEndpoint |
Представляет конечную точку интерфейса, которая является каналом связи для этого интерфейса. У интерфейса может быть одна или несколько конечных точек, и обычно у него есть конечные точки ввода и вывода для двусторонней связи с устройством. |
UsbDeviceConnection |
Представляет подключение к устройству, которое передает данные на конечные точки. Этот класс позволяет отправлять и получать данные синхронно или асинхронно. |
UsbRequest |
Представляет асинхронный запрос на обмен данными с устройством через UsbDeviceConnection. |
UsbConstants |
Определяет константы USB, которые соответствуют определениям в linux/usb/ch9.h ядра Linux. |
В большинстве случаев при взаимодействии с USB-устройством необходимо использовать все эти классы (UsbRequest требуется только для асинхронной связи). Как правило, чтобы получить нужный объект UsbDevice, необходимо получить объект UsbManager.
После этого нужно найти подходящий интерфейс UsbInterface и его конечную точку UsbEndpoint для обмена данными. Получив нужную конечную точку, откройте UsbDeviceConnection для связи с USB-устройством.
Требования к манифесту Android
В следующем списке описано, что нужно добавить в файл манифеста приложения, прежде чем работать с API USB-хоста:
- Поскольку не все устройства Android поддерживают API USB-хоста, добавьте элемент
<uses-feature>, который указывает, что ваше приложение использует функциюandroid.hardware.usb.host. - Установите минимальный уровень API для SDK приложения на 12 или выше. API USB-хоста отсутствуют на более ранних уровнях API.
- Если вы хотите, чтобы ваше приложение получало уведомления о подключении USB-устройства, укажите пару элементов
<intent-filter>и<meta-data>для намеренияandroid.hardware.usb.action.USB_DEVICE_ATTACHEDв основном действии. Элемент<meta-data>указывает на внешний XML-файл ресурсов, в котором содержится идентифицирующая информация об устройстве, которое вы хотите обнаружить.В XML-файле ресурсов объявите элементы
<usb-device>для USB-устройств, которые вы хотите отфильтровать. Ниже перечислены атрибуты<usb-device>. Как правило, идентификаторы поставщика и товара используются для фильтрации по конкретному устройству, а класс, подкласс и протокол – для фильтрации по группе USB-устройств, например по накопителям или цифровым камерам. Вы можете указать все эти атрибуты или ни одного. Если не указать атрибуты, будут выбраны все USB-устройства. Делайте это, только если это необходимо для вашего приложения.vendor-idproduct-idclasssubclassprotocol(устройство или интерфейс)
Сохраните файл ресурсов в каталоге
res/xml/. Название файла ресурса (без расширения .xml) должно совпадать с указанным в элементе<meta-data>. Формат XML-файла ресурсов показан в примере ниже.
Примеры файлов манифеста и ресурсов
Ниже приведен пример манифеста и соответствующего файла ресурсов.
<manifest ...> <uses-feature android:name="android.hardware.usb.host" /> <uses-sdk android:minSdkVersion="12" /> ... <application> <activity ...> ... <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity> </application> </manifest>
В этом случае в папке res/xml/device_filter.xml нужно сохранить файл ресурсов, который указывает, что любое USB-устройство с заданными атрибутами должно быть отфильтровано:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> </resources>
Работа с устройствами
Когда пользователи подключают USB-устройства к устройству на базе Android, система Android может определить, заинтересовано ли ваше приложение в подключенном устройстве. Если да, вы можете настроить связь с устройством. Для этого приложение должно:
- Обнаруживать подключенные USB-устройства с помощью фильтра интентов, чтобы получать уведомления, когда пользователь подключает USB-устройство, или путем перечисления уже подключенных USB-устройств.
- Запрашивать у пользователя разрешение на подключение к USB-устройству, если оно ещё не получено.
- Обмен данными с USB-устройством путем чтения и записи данных на соответствующих конечных точках интерфейса.
Как найти устройство
Приложение может обнаруживать USB-устройства, используя фильтр интентов для получения уведомлений о подключении пользователем устройства или перечисляя уже подключенные USB-устройства. Использование фильтра интентов полезно, если вы хотите, чтобы приложение автоматически обнаруживало нужное устройство. Перечисление подключенных USB-устройств полезно, если вы хотите получить список всех подключенных устройств или если ваше приложение не отфильтровало намерение.
Используйте фильтр интентов
Чтобы приложение могло обнаружить определенное USB-устройство, вы можете указать фильтр интентов для фильтрации интента android.hardware.usb.action.USB_DEVICE_ATTACHED. Вместе с этим фильтром интентов необходимо указать файл ресурсов, в котором заданы свойства USB-устройства, например идентификатор продукта и поставщика. Когда пользователь подключает устройство, соответствующее вашему фильтру, система показывает диалоговое окно с вопросом, хочет ли он запустить ваше приложение.
Если пользователь согласится, ваше приложение автоматически получит разрешение на доступ к устройству, пока оно не будет отключено.
В следующем примере показано, как объявить фильтр интентов:
<activity ...> ... <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity>
В следующем примере показано, как объявить соответствующий файл ресурсов, в котором указаны интересующие вас USB-устройства:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" /> </resources>
В своем объекте activity вы можете получить объект UsbDevice, представляющий подключенное устройство, из объекта intent следующим образом:
Kotlin
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
Java
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Перечисление устройств
Если вашему приложению необходимо проверить все USB-устройства, подключенные в данный момент, оно может перечислить устройства на шине. Используйте метод getDeviceList(), чтобы получить хеш-карту всех подключенных USB-устройств. Если вы хотите получить устройство из хеш-карты, используйте в качестве ключа название USB-устройства.
Kotlin
val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... val deviceList = manager.getDeviceList() val device = deviceList.get("deviceName")
Java
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
При желании вы также можете просто получить итератор из хеш-карты и обрабатывать каждое устройство по одному:
Kotlin
val manager = getSystemService(Context.USB_SERVICE) as UsbManager .. val deviceList: HashMap<String, UsbDevice> = manager.deviceList deviceList.values.forEach { device -> // your code }
Java
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while(deviceIterator.hasNext()){ UsbDevice device = deviceIterator.next(); // your code }
Получение разрешения на обмен данными с устройством
Прежде чем взаимодействовать с USB-устройством, приложение должно получить разрешение от пользователя.
Примечание. Если ваше приложение использует фильтр интентов для обнаружения USB-устройств при их подключении, оно автоматически получает разрешение, если пользователь разрешает приложению обрабатывать интент. Если нет, то перед подключением к устройству необходимо явно запросить разрешение в приложении.
Явный запрос разрешения может потребоваться в некоторых ситуациях, например когда ваше приложение перечисляет уже подключенные USB-устройства, а затем хочет связаться с одним из них. Прежде чем пытаться связаться с устройством, необходимо проверить, есть ли у вас разрешение на доступ к нему. В противном случае, если пользователь запретит доступ к устройству, вы получите ошибку выполнения.
Чтобы получить явное разрешение, сначала создайте широковещательный приемник. Этот получатель прослушивает намерение, которое транслируется при вызове requestPermission(). Вызов requestPermission() показывает пользователю диалоговое окно с запросом разрешения на подключение к устройству. В следующем образце кода показано, как создать широковещательный приемник.
Kotlin
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" private val usbReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (ACTION_USB_PERMISSION == intent.action) { synchronized(this) { val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { device?.apply { // call method to set up device communication } } else { Log.d(TAG, "permission denied for device $device") } } } } }
Java
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if(device != null){ // call method to set up device communication } } else { Log.d(TAG, "permission denied for device " + device); } } } } };
Чтобы зарегистрировать широковещательный приемник, добавьте в метод onCreate() своего класса activity следующий код:
Kotlin
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" ... val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE) val filter = IntentFilter(ACTION_USB_PERMISSION) registerReceiver(usbReceiver, filter)
Java
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; ... permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbReceiver, filter);
Чтобы показать диалоговое окно с запросом разрешения на подключение к устройству, вызовите метод requestPermission():
Kotlin
lateinit var device: UsbDevice ... usbManager.requestPermission(device, permissionIntent)
Java
UsbDevice device; ... usbManager.requestPermission(device, permissionIntent);
Когда пользователи отвечают в диалоговом окне, ваш широковещательный приемник получает намерение, содержащее дополнительный параметр EXTRA_PERMISSION_GRANTED, который является логическим значением, представляющим ответ. Прежде чем подключаться к устройству, убедитесь, что это значение равно true.
Обмен данными с устройством
Связь с USB-устройством может быть синхронной или асинхронной. В любом случае вам следует создать новый поток, в котором будут выполняться все передачи данных, чтобы не блокировать поток UI. Чтобы правильно настроить связь с устройством, необходимо получить соответствующие UsbInterface и UsbEndpoint устройства, с которым вы хотите связаться, и отправить запросы на эту конечную точку с помощью UsbDeviceConnection. В целом ваш код должен:
- Проверьте атрибуты объекта
UsbDevice, например идентификатор товара, идентификатор поставщика или класс устройства, чтобы определить, нужно ли вам взаимодействовать с устройством. - Когда вы будете уверены, что хотите установить связь с устройством, найдите нужный интерфейс
UsbInterfaceи его конечную точкуUsbEndpoint. Интерфейсы могут иметь одну или несколько конечных точек и обычно имеют входную и выходную конечные точки для двусторонней связи. - Найдя нужную конечную точку, откройте на ней
UsbDeviceConnection. - Укажите данные, которые вы хотите передать в конечную точку, с помощью метода
bulkTransfer()илиcontrolTransfer(). Чтобы не блокировать основной поток UI, этот шаг следует выполнять в другом потоке. Подробнее о процессах и потоках в Android…
В следующем фрагменте кода показан простой способ синхронной передачи данных. В коде должна быть логика, позволяющая правильно найти нужный интерфейс и конечные точки для связи, а также передавать данные в потоке, отличном от основного потока UI.
Kotlin
private lateinit var bytes: ByteArray private val TIMEOUT = 0 private val forceClaim = true ... device?.getInterface(0)?.also { intf -> intf.getEndpoint(0)?.also { endpoint -> usbManager.openDevice(device)?.apply { claimInterface(intf, forceClaim) bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT) //do in another thread } } }
Java
private Byte[] bytes; private static int TIMEOUT = 0; private boolean forceClaim = true; ... UsbInterface intf = device.getInterface(0); UsbEndpoint endpoint = intf.getEndpoint(0); UsbDeviceConnection connection = usbManager.openDevice(device); connection.claimInterface(intf, forceClaim); connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
Чтобы отправить данные асинхронно, используйте класс UsbRequest для initialize и queue асинхронного запроса, а затем дождитесь результата с помощью requestWait().
Завершение связи с устройством
Когда вы закончите обмен данными с устройством или если оно было отключено, закройте UsbInterface и UsbDeviceConnection, вызвав releaseInterface() и close(). Чтобы прослушивать события отсоединения, создайте широковещательный приемник, как показано ниже.
Kotlin
var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) { val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) device?.apply { // call your method that cleans up and closes communication with the device } } } }
Java
BroadcastReceiver usbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { // call your method that cleans up and closes communication with the device } } } };
Создание широковещательного приемника в приложении, а не в манифесте, позволяет приложению обрабатывать события отсоединения только во время работы. Таким образом, события отсоединения отправляются только в приложение, которое запущено в данный момент, а не во все приложения.