Обзор USB-аксессуаров

Режим USB-аксессуара позволяет пользователям подключать USB-хост-оборудование, специально разработанное для устройств на базе Android. Аксессуары должны соответствовать протоколу аксессуаров Android, описанному в документации комплекта разработки аксессуаров Android . Это позволяет устройствам на базе Android, которые не могут выступать в качестве USB-хоста, по-прежнему взаимодействовать с USB-оборудованием. Когда устройство под управлением Android находится в режиме USB-аксессуара, подключенный USB-аксессуар Android действует как хост, обеспечивает питание шины 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.

Если вы хотите установить надстройку, вы можете сделать это, установив пакет Google API Android API 10 с помощью SDK Manager. Дополнительную информацию об установке дополнительной библиотеки см. в разделе Установка надстройки Google API .

Обзор API

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

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

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

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

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

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

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

Котлин

val manager = UsbManager.getInstance(this)

Ява

UsbManager manager = UsbManager.getInstance(this);

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

Котлин

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Ява

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

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

Котлин

val accessory = UsbManager.getAccessory(intent)

Ява

UsbAccessory accessory = UsbManager.getAccessory(intent);

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

Котлин

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

Ява

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-аксессуар, вы можете указать фильтр намерений для фильтрации намерения 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 , который представляет прикрепленный аксессуар, следующим образом (с помощью дополнительной библиотеки):

Котлин

val accessory = UsbManager.getAccessory(intent)

Ява

UsbAccessory accessory = UsbManager.getAccessory(intent);

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

Котлин

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

Ява

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

Перечислить аксессуары

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

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

Котлин

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

Ява

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

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

Получите разрешение на общение с аксессуаром

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

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

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

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

Котлин

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

Ява

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() своей деятельности:

Котлин

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)

Ява

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() :

Котлин

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

Ява

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

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

Общайтесь с аксессуаром

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

Котлин

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

Ява

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 поддерживает буферы пакетов размером до 16384 байт, поэтому для простоты вы можете всегда объявлять буфер такого размера.

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

Дополнительные сведения об использовании потоков в Android см. в разделе Процессы и потоки .

Завершить связь с аксессуаром

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

Котлин

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

Ява

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

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