Общие сведения об аксессуарах USB

Режим USB-аксессуара позволяет подключать к устройствам Android хост-оборудование USB, специально разработанное для таких устройств. Аксессуары должны соответствовать протоколу Android, описанному в документации Android Accessory Development Kit. Это позволяет устройствам на базе Android, которые не могут выступать в качестве USB-хоста, взаимодействовать с USB-оборудованием. Когда устройство Android работает в режиме USB-аксессуара, подключенный аксессуар Android USB выступает в роли хоста, подает питание на шину USB и перечисляет подключенные устройства. В Android 3.1 (уровень API 12) поддерживается режим USB-аксессуара, а также эта функция была перенесена в Android 2.3.4 (уровень API 10), чтобы обеспечить поддержку более широкого спектра устройств.

Как выбрать подходящие API для USB-аксессуаров

Хотя API для USB-аксессуаров были добавлены в платформу Android 3.1, они также доступны в Android 2.3.4 с помощью библиотеки дополнений API Google. Поскольку эти API были перенесены в более ранние версии с помощью внешней библиотеки, для поддержки режима USB-аксессуара можно импортировать два пакета. В зависимости от того, какие устройства Android вы хотите поддерживать, вам может понадобиться использовать один из следующих вариантов:

  • com.android.future.usb: чтобы поддерживать режим USB-аксессуара в Android 2.3.4, библиотека дополнения API Google включает API USB-аксессуара с обратной совместимостью, которые содержатся в этом пространстве имен. Android 3.1 также поддерживает импорт и вызов классов в этом пространстве имен для поддержки приложений, написанных с помощью библиотеки дополнений. Эта библиотека дополнений представляет собой тонкую оболочку для API аксессуаров android.hardware.usb и не поддерживает режим хоста USB. Если вы хотите, чтобы ваше приложение поддерживало как можно больше устройств, работающих в режиме USB-аксессуара, используйте дополнительную библиотеку и импортируйте этот пакет. Важно отметить, что не все устройства Android 2.3.4 должны поддерживать функцию USB-аксессуаров. Поддержку этой функции определяет производитель устройства, поэтому ее необходимо указать в файле манифеста.
  • android.hardware.usb: это пространство имен содержит классы, поддерживающие режим USB-аксессуара в Android 3.1. Этот пакет входит в состав API фреймворка, поэтому Android 3.1 поддерживает режим USB-аксессуара без использования дополнительной библиотеки. Используйте этот пакет, если вам нужны только устройства с Android 3.1 или более поздней версии, которые поддерживают режим аксессуара USB. Эту функцию можно указать в файле манифеста.

Как установить библиотеку дополнения API Google

Чтобы установить дополнение, установите пакет API Google Android API 10 с помощью SDK Manager. Подробнее об установке дополнения API Google…

Обзор API

Поскольку библиотека дополнений является оболочкой для API фреймворка, классы, поддерживающие функцию USB-аксессуара, похожи. Вы можете использовать справочную документацию по android.hardware.usb, даже если вы используете библиотеку дополнений.

Примечание. Однако между библиотекой дополнений и API фреймворка есть небольшое различие в использовании, о котором вам следует знать.

В таблице ниже перечислены классы, поддерживающие API USB-аксессуаров.

Класс Описание
UsbManager Позволяет перечислять подключенные USB-аксессуары и взаимодействовать с ними.
UsbAccessory Представляет USB-аксессуар и содержит методы для доступа к его идентификационной информации.

Различия в использовании библиотеки дополнений и API платформы

Существует два различия в использовании библиотеки дополнения API Google и API платформы.

Если вы используете библиотеку дополнений, объект UsbManager нужно получить следующим образом:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Если вы не используете библиотеку дополнений, объект UsbManager можно получить следующим образом:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

Если вы фильтруете подключенный аксессуар с помощью фильтра интентов, объект UsbAccessory будет содержаться в интенте, который передается в ваше приложение. Если вы используете библиотеку дополнений, объект UsbAccessory нужно получить следующим образом:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Если вы не используете библиотеку дополнений, объект UsbAccessory можно получить следующим образом:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Требования к манифесту Android

Ниже перечислены элементы, которые необходимо добавить в файл манифеста приложения, прежде чем работать с API для USB-аксессуаров. В примерах файлов манифеста и ресурсов показано, как объявлять эти элементы:

  • Поскольку не все устройства на базе Android гарантированно поддерживают API для аксессуаров USB, добавьте элемент <uses-feature>, в котором будет указано, что ваше приложение использует функцию android.hardware.usb.accessory.
  • Если вы используете библиотеку дополнений, добавьте элемент <uses-library>, указав com.android.future.usb.accessory для библиотеки.
  • Установите минимальную версию SDK приложения на уровне API 10, если вы используете библиотеку дополнений, или 12, если вы используете пакет android.hardware.usb.
  • Если вы хотите, чтобы ваше приложение получало уведомления о подключении USB-аксессуара, укажите пару элементов <intent-filter> и <meta-data> для намерения android.hardware.usb.action.USB_ACCESSORY_ATTACHED в основном действии. Элемент <meta-data> указывает на внешний XML-файл ресурса, в котором объявляется идентифицирующая информация об аксессуаре, который вы хотите обнаружить.

    В XML-файле ресурсов объявите элементы <usb-accessory> для аксессуаров, которые вы хотите отфильтровать. Каждый объект <usb-accessory> может иметь следующие атрибуты:

    • manufacturer
    • model
    • version

    Фильтрация по параметру "version" не рекомендуется. Аксессуар или устройство может не всегда указывать строку версии (намеренно или случайно). Если в приложении указан атрибут версии для фильтрации, а в аксессуаре или устройстве не указана строка версии, это приводит к ошибке NullPointerException в более ранних версиях Android. Эта проблема устранена в Android 12.

    Сохраните файл ресурсов в каталоге res/xml/. Название файла ресурса (без расширения .xml) должно совпадать с названием, указанным в элементе <meta-data>. Формат XML-файла ресурсов также показан в примере ниже.

Примеры файлов манифеста и ресурсов

Ниже приведен пример манифеста и соответствующего файла ресурсов:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.accessory" />
    
    <uses-sdk android:minSdkVersion="<version>" />
    ...
    <application>
      <uses-library android:name="com.android.future.usb.accessory" />
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" />
        </activity>
    </application>
</manifest>

В этом случае следующий файл ресурсов должен быть сохранен в res/xml/accessory_filter.xml и указывает, что любой аксессуар с соответствующей моделью, производителем и версией должен быть отфильтрован. Аксессуар отправляет на устройство Android следующие атрибуты:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

Как работать с аксессуарами

Когда пользователи подключают USB-аксессуары к устройству Android, система Android может определить, заинтересовано ли ваше приложение в подключенном аксессуаре. Если да, вы можете настроить связь с аксессуаром. Для этого приложение должно:

  1. Чтобы найти подключенные аксессуары, используйте фильтр интентов, который фильтрует события подключения аксессуаров, или перечислите подключенные аксессуары и найдите нужный.
  2. Запросите у пользователя разрешение на связь с аксессуаром, если оно ещё не получено.
  3. Обмениваться данными с аксессуаром, считывая и записывая их через подходящие конечные точки интерфейса.

Как найти аксессуар

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

Используйте фильтр намерений

Чтобы приложение обнаруживало определенный USB-аксессуар, можно задать фильтр интентов, чтобы отбирать только intent android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Вместе с этим фильтром интентов необходимо указать файл ресурсов, в котором заданы свойства USB-аксессуара, например производитель, модель и версия.

В следующем примере показано, как объявить фильтр интентов:

<activity ...>
    ...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
    </intent-filter>

    <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
</activity>

В следующем примере показано, как объявить соответствующий файл ресурсов, в котором указаны интересующие вас USB-аксессуары:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

В действии можно получить объект UsbAccessory, представляющий подключенный аксессуар, из намерения следующим образом (с использованием библиотеки дополнений):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

или так (с API платформы):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Перечисление аксессуаров

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

Чтобы получить массив всех подключенных USB-аксессуаров, используйте метод getAccessoryList():

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();

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

Как получить разрешение на подключение к аксессуару

Прежде чем начать обмен данными с 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 accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    accessory?.apply {
                        // call method to set up accessory communication
                    }
                } else {
                    Log.d(TAG, "permission denied for accessory $accessory")
                }
            }
        }
    }
}

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) {
                UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if(accessory != null){
                        // call method to set up accessory communication
                    }
                }
                else {
                    Log.d(TAG, "permission denied for accessory " + accessory);
                }
            }
        }
    }
};

Чтобы зарегистрировать широковещательный приемник, добавьте в метод 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), 0)
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), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

Чтобы показать диалоговое окно с запросом разрешения на подключение к аксессуару, вызовите метод requestPermission():

Kotlin

lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);

Когда пользователи отвечают на диалоговое окно, ваш широковещательный приемник получает интент, содержащий дополнительный объект EXTRA_PERMISSION_GRANTED, который представляет собой логическое значение, соответствующее ответу. Перед подключением к аксессуару проверьте, имеет ли этот дополнительный параметр значение true.

Как общаться с аксессуаром

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

Kotlin

private lateinit var accessory: UsbAccessory
private var fileDescriptor: ParcelFileDescriptor? = null
private var inputStream: FileInputStream? = null
private var outputStream: FileOutputStream? = null
...

private fun openAccessory() {
    Log.d(TAG, "openAccessory: $mAccessory")
    fileDescriptor = usbManager.openAccessory(accessory)
    fileDescriptor?.fileDescriptor?.also { fd ->
        inputStream = FileInputStream(fd)
        outputStream = FileOutputStream(fd)
        val thread = Thread(null, this, "AccessoryThread")
        thread.start()
    }
}

Java

UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
...

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    fileDescriptor = usbManager.openAccessory(accessory);
    if (fileDescriptor != null) {
        FileDescriptor fd = fileDescriptor.getFileDescriptor();
        inputStream = new FileInputStream(fd);
        outputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

В методе run() потока можно читать данные с аксессуара и записывать их в него с помощью объектов FileInputStream или FileOutputStream. При чтении данных с аксессуара с объектом FileInputStream убедитесь, что буфер, который вы используете, достаточно велик для хранения данных пакета USB. Протокол Android Accessory Protocol поддерживает буферы пакетов размером до 16 384 байт, поэтому для простоты вы можете всегда объявлять буфер такого размера.

Примечание. На более низком уровне пакеты имеют размер 64 байта для полноскоростных аксессуаров USB и 512 байтов для высокоскоростных аксессуаров USB. Протокол аксессуаров Android объединяет пакеты для обеих скоростей в один логический пакет.

Подробнее о том, как использовать потоки в Android…

Как прекратить связь с аксессуаром

Когда вы закончите работу с аксессуаром или если он будет отсоединен, закройте дескриптор файла, открытый с помощью функции close(). Чтобы прослушивать события отключения, создайте широковещательный приемник, как показано ниже:

Kotlin

var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED == intent.action) {
            val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
            accessory?.apply {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
}

Java

BroadcastReceiver usbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
            UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
            if (accessory != null) {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
};

Создание широковещательного приемника в приложении, а не в манифесте, позволяет приложению обрабатывать отсоединенные события только во время его работы. Таким образом, отсоединенные события отправляются только в запущенное приложение, а не транслируются всем приложениям.