Omówienie hosta USB

Gdy urządzenie z Androidem jest w trybie hosta USB, działa jako host USB, zasila magistralę, i wylicza podłączone urządzenia USB. Tryb hosta USB jest obsługiwany w Androidzie 3.1 i nowszych.

Omówienie interfejsu API

Zanim zaczniesz, musisz wiedzieć, z którymi zajęciami musisz pracować. W tej tabeli opisano interfejsy API hostów USB w pakiecie android.hardware.usb.

Tabela 1. Interfejsy API hostów USB

Kategoria Opis
UsbManager Umożliwia wyliczanie podłączonych urządzeń USB i komunikowanie się z nimi.
UsbDevice Reprezentuje podłączone urządzenie USB i zawiera metody dostępu do jego danych identyfikacyjnych oraz informacji, interfejsów i punktów końcowych.
UsbInterface Reprezentuje interfejs urządzenia USB, który definiuje zestaw funkcji urządzenia. Urządzenie może mieć jeden lub więcej interfejsów do komunikacji.
UsbEndpoint Reprezentuje punkt końcowy interfejsu, który jest kanałem komunikacyjnym tego interfejsu. An interfejs może mieć jeden lub więcej punktów końcowych i zazwyczaj ma punkty wejściowe wejściowe i wyjściowe dla dwukierunkową komunikację z urządzeniem.
UsbDeviceConnection Reprezentuje połączenie z urządzeniem, które przesyła dane w punktach końcowych. Te zajęcia umożliwia przesyłanie danych w obie strony synchronicznie lub asynchronicznie.
UsbRequest Reprezentuje asynchroniczne żądanie komunikacji z urządzeniem za pomocą interfejsu UsbDeviceConnection.
UsbConstants Definiuje stałe USB odpowiadające definicjom w linux/usb/ch9.h w systemie Linux jądro.

W większości przypadków musisz używać wszystkich tych klas (pole UsbRequest jest wymagane tylko w przypadku komunikacji asynchronicznej) podczas komunikacji z urządzeniem USB. Zazwyczaj uzyskujesz taki element (UsbManager), aby pobrać żądane UsbDevice. Gdy już masz urządzenie, znajdź odpowiednie UsbInterface oraz UsbEndpoint za pomocą którego chcesz się komunikować. Gdy uzyskasz prawidłowy punkt końcowy, otwórz UsbDeviceConnection, aby skomunikować się z urządzeniem USB.

Wymagania dotyczące pliku manifestu na Androida

Na poniższej liście opisaliśmy, co musisz dodać do pliku manifestu aplikacji, zanim z interfejsami API hosta USB:

  • Nie wszystkie urządzenia z Androidem będą obsługiwać interfejsy API hosta USB, zawierać element <uses-feature> deklarujący, że aplikacja używa funkcja android.hardware.usb.host.
  • Ustaw minimalny pakiet SDK aplikacji na API na poziomie 12 lub wyższym. Interfejsy API hosta USB nie są dostępnych na wcześniejszych poziomach interfejsu API.
  • Jeśli chcesz otrzymywać powiadomienia do aplikacji o podłączonym urządzeniu USB, określ Para elementów <intent-filter> i <meta-data> dla android.hardware.usb.action.USB_DEVICE_ATTACHED intencje w Twojej głównej aktywności. Element <meta-data> wskazuje zewnętrzny plik zasobów XML z deklaracją umożliwiające identyfikację informacji o urządzeniu, które chcesz wykryć.

    W pliku zasobów XML zadeklaruj elementy <usb-device> dla USB. urządzenia, które chcesz filtrować. Poniższa lista opisuje atrybuty: <usb-device> Ogólnie do filtrowania należy używać identyfikatora dostawcy i produktu dla konkretnego urządzenia oraz użyj klasy, podklasy i protokołu, jeśli chcesz przefiltrować dane według grupy. urządzeń USB, takich jak urządzenia pamięci masowej lub aparaty cyfrowe. Możesz wpisać brak lub wszystkich tych atrybutów. Jeśli nie określono atrybutów pasujących do każdego urządzenia USB, zrób to tylko jeśli aplikacja tego wymaga:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol (urządzenie lub interfejs)

    Zapisz plik zasobów w katalogu res/xml/. Nazwa pliku zasobów (bez rozszerzenia .xml) musi być taki sam jak URL określony w <meta-data> element. Plik zasobów XML ma format przykład poniżej.

Przykłady plików manifestu i plików zasobów

Poniżej znajduje się przykładowy plik manifestu i odpowiadający mu plik zasobów:

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

W tym przypadku należy zapisać poniższy plik zasobów w res/xml/device_filter.xml i określa, że każde urządzenie USB z atrybuty powinny być filtrowane:

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

<resources>
    <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>

Praca z urządzeniami

Gdy użytkownik podłącza urządzenie USB do urządzenia z Androidem, system Android może określić, czy aplikacja jest zainteresowana połączonym urządzeniem. Jeśli tak, możesz skonfigurować w razie potrzeby komunikować się z urządzeniem. Aby to było możliwe, aplikacja musi:

  1. Wykrywanie podłączonych urządzeń USB za pomocą filtra intencji, który pozwala otrzymywać powiadomienia, gdy użytkownik służy do podłączenia urządzenia USB lub przez zliczenie już podłączonych urządzeń USB.
  2. Poproś użytkownika o zgodę na połączenie się z urządzeniem USB, jeśli jeszcze nie ma tej zgody.
  3. Komunikuj się z urządzeniem USB, odczytując i zapisuj dane w odpowiednim interfejsie i punktów końcowych.

Odkryj urządzenie

Aplikacja może wykrywać urządzenia USB, korzystając z filtra intencji i powiadamiając go, gdy użytkownik podłącza urządzenie lub wymienia urządzenia USB, które są już podłączone. Za pomocą jest przydatny, jeśli chcesz, aby aplikacja automatycznie wykrywała wybranego urządzenia. Wyliczanie podłączonych urządzeń USB jest przydatne, jeśli chcesz uzyskać listę wszystkich urządzeń połączonych urządzeń lub jeśli aplikacja nie odfiltrowała intencji.

Korzystanie z filtra intencji

Aby aplikacja wykrywała określone urządzenie USB, możesz ustawić filtr intencji filtr do intencji android.hardware.usb.action.USB_DEVICE_ATTACHED. Razem z do tego filtra intencji, musisz określić plik zasobów określający właściwości dysku USB urządzenia, takie jak produkt i identyfikator dostawcy. Gdy użytkownicy podłączą urządzenie zgodne z Twoim urządzeniem system wyświetla mu okno z pytaniem, czy chce uruchomić aplikację. Jeśli użytkownicy zaakceptują warunki, aplikacja automatycznie uzyska dostęp do urządzenia do czasu urządzenie jest odłączone.

Ten przykład pokazuje, jak zadeklarować filtr intencji:

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

Poniższy przykład pokazuje, jak zadeklarować odpowiedni plik zasobów, który określa Urządzenia USB, które Cię interesują:

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

<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

W swojej aktywności możesz uzyskać UsbDevice, który reprezentuje podłączone urządzenie z intencji wygląda tak:

Kotlin

val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)

Java

UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

Wyliczanie urządzeń

Jeśli Twoja aplikacja chce sprawdzić wszystkie podłączone obecnie urządzenia USB gdy aplikacja jest uruchomiona, może wyliczyć urządzenia magistrali. Użyj metody getDeviceList(), aby uzyskać mapę skrótu wszystkich podłączone urządzenia USB. Jeśli chcesz, mapa haszująca jest osadzona w nazwie urządzenia USB urządzenia z mapy.

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

W razie potrzeby można również uzyskać iterację z mapy haszującej i przetworzyć dla każdego urządzenia przez:

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
}

Uzyskiwanie uprawnień do komunikowania się z urządzeniem

Przed komunikacją z urządzeniem USB aplikacja musi mieć pozwolenie użytkowników.

Uwaga: jeśli aplikacja korzysta z parametru filtra intencji, aby wykryć podłączone urządzenia USB, a następnie automatycznie odbierze jeśli użytkownik zezwala aplikacji na obsługę intencji. Jeśli nie, bezpośrednio w aplikacji przed nawiązaniem połączenia z urządzeniem.

Wyraźne proszenie o zgodę może być konieczne w niektórych sytuacjach, na przykład gdy Aplikacja wylicza urządzenia USB, które są już podłączone, a następnie chce się komunikować z jeden. Przed próbą skomunikowania się z urządzeniem musisz sprawdzić uprawnienia dostępu. Jeśli nie, jeśli użytkownik odmówi zgody na dostęp do urządzenia, wyświetli się błąd czasu działania.

Aby bezpośrednio uzyskać odpowiednie uprawnienia, najpierw utwórz odbiornik. Ten odbiornik nasłuchuje przez intencję, która ma być transmitowana, gdy zadzwonisz do: requestPermission(). W wywołaniu requestPermission() pojawi się okno użytkownik prosi o zgodę na połączenie z urządzeniem. Ten przykładowy kod pokazuje, jak Utwórz odbiornik:

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

Aby zarejestrować odbiornik, dodaj ten parametr do metody onCreate() w aktywność:

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

Aby wyświetlić okno z prośbą o pozwolenie na połączenie z urządzeniem, wywołaj metodę requestPermission():

Kotlin

lateinit var device: UsbDevice
...
usbManager.requestPermission(device, permissionIntent)

Java

UsbDevice device;
...
usbManager.requestPermission(device, permissionIntent);

Gdy użytkownik odpowie na okno, odbiornik wiadomości otrzyma intencję zawierającą EXTRA_PERMISSION_GRANTED dodatkowy, który jest wartością logiczną reprezentująca odpowiedź. Sprawdź, czy ten dodatek ma wartość prawda, zanim połączysz się z urządzenia.

Komunikacja z urządzeniem

Komunikacja z urządzeniem USB może być synchroniczna lub asynchroniczna. W obu przypadkach należy utworzyć nowy wątek, w którym będą przeprowadzane wszystkie transmisje danych, aby nie blokować Wątek interfejsu. Aby prawidłowo skonfigurować komunikację z urządzeniem, musisz uzyskać odpowiednie UsbInterface i UsbEndpoint z urządzenie, na którym chcesz się komunikować i wysyłać żądania w tym punkcie końcowym za pomocą UsbDeviceConnection. Ogólnie kod powinien:

  • Sprawdź atrybuty obiektu UsbDevice, takie jak identyfikator produktu, dostawcy lub klasy urządzenia w celu określenia, czy chcesz komunikować się z urządzenia.
  • Jeśli masz pewność, że chcesz skomunikować się z urządzeniem, znajdź odpowiednią UsbInterface, których chcesz używać do komunikowania się z odpowiedni UsbEndpoint interfejsu. Interfejsy mogą mieć jeden lub większą liczbę punktów końcowych. Zwykle mają też punkt końcowy wejścia i wyjścia dla komunikacji między usługami.
  • Gdy znajdziesz właściwy punkt końcowy, otwórz UsbDeviceConnection w tym punkcie końcowym.
  • Dostarcz dane, które chcesz przesyłać w punkcie końcowym, za pomocą metody bulkTransfer() lub controlTransfer(). Zalecenia wykonaj ten krok w innym wątku, aby uniknąć zablokowania głównego wątku interfejsu użytkownika. Więcej informacji na temat używania wątków w Androidzie można znaleźć w artykule Procesy i Wątki.

Poniższy fragment kodu to prosty sposób synchronicznego przesyłania danych. Twój kod powinno mieć więcej logiki umożliwiającej poprawne wyszukiwanie interfejsu i punktów końcowych do komunikacji oraz przenosić dane w innym wątku niż główny wątek 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

Aby wysyłać dane asynchronicznie, użyj klasy UsbRequest do żądania initialize i queue żądania asynchronicznego, a następnie poczekaj na wynik dzięki requestWait().

Kończenie komunikacji z urządzeniem

Gdy skończysz komunikować się z urządzeniem lub jeśli zostało ono odłączone, zamknij UsbInterface i UsbDeviceConnection przez dzwonię do: releaseInterface() i close() Aby nasłuchiwać odłączonych zdarzeń, utwórz odbiornik tak jak poniżej:

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

Utworzenie odbiornika w aplikacji, a nie pliku manifestu, umożliwia do obsługi odłączonych zdarzeń tylko podczas działania aplikacji. Dzięki temu odłączone zdarzenia są wysyłane tylko do aktywnej aplikacji, a nie do wszystkich aplikacji.