Omówienie akcesoriów USB

Tryb akcesorium USB umożliwia użytkownikom podłączanie sprzętu USB hosta USB przeznaczonego do urządzeń z Androidem. Akcesoria muszą być zgodne z protokołem akcesoriów na Androida opisanym w dokumentacji zestawu Android Accessory Development Kit. Dzięki temu urządzenia z Androidem, które nie mogą działać jako host USB, mogą nadal korzystać ze sprzętu USB. Gdy urządzenie z Androidem jest w trybie akcesorium USB, podłączone akcesorium USB na Androida działa jako host, dostarcza zasilanie do magistrali USB i wymienia połączone urządzenia. Android 3.1 (poziom interfejsu API 12) obsługuje tryb akcesorium USB. Ta funkcja jest też wsteczna do Androida 2.3.4 (poziom interfejsu API 10), aby zapewnić obsługę większej liczby urządzeń.

Wybierz odpowiednie interfejsy API akcesoriów USB

Chociaż interfejsy API akcesoriów USB zostały wprowadzone na platformie w Androidzie 3.1, są one również dostępne w Androidzie 2.3.4 za pomocą biblioteki dodatków interfejsów API Google. Te interfejsy API zostały przeniesione wstecznie przy użyciu zewnętrznej biblioteki, dlatego możesz zaimportować 2 pakiety, aby obsługiwać tryb akcesorium USB. W zależności od tego, jakie urządzenia z Androidem mają być obsługiwane, konieczne może być używanie jednego z nich:

  • com.android.future.usb: aby obsługiwać tryb akcesorium USB w Androidzie 2.3.4, biblioteka dodatków interfejsów API Google zawiera wstecznie przeniesione interfejsy API akcesoriów USB. Znajdują się one w tej przestrzeni nazw. Android 3.1 obsługuje też importowanie i wywoływanie klas w tej przestrzeni nazw w celu obsługi aplikacji napisanych za pomocą biblioteki dodatków. Ta biblioteka dodatków to cienka powłoka otaczająca interfejsy API akcesorium android.hardware.usb. Nie obsługuje trybu hosta USB. Jeśli chcesz obsługiwać najszerszą gamę urządzeń obsługujących tryb akcesorium USB, użyj biblioteki dodatków i zaimportuj ten pakiet. Pamiętaj, że nie wszystkie urządzenia z Androidem 2.3.4 muszą obsługiwać akcesorium USB. Każdy producent urządzenia decyduje, czy będzie obsługiwać tę funkcję, dlatego musisz to zadeklarować w pliku manifestu.
  • android.hardware.usb: ta przestrzeń nazw zawiera klasy obsługujące tryb akcesorium USB w Androidzie 3.1. Ten pakiet wchodzi w skład interfejsów API, dlatego Android 3.1 obsługuje tryb akcesorium USB bez korzystania z biblioteki dodatków. Użyj tego pakietu, jeśli interesują Cię tylko urządzenia z Androidem 3.1 lub nowszym ze sprzętową obsługą trybu akcesorium USB, co możesz zadeklarować w pliku manifestu.

Instalowanie biblioteki dodatków interfejsów API Google

Jeśli chcesz zainstalować dodatek, możesz to zrobić, instalując pakiet Android API 10 interfejsów API Google za pomocą SDK Manager. Aby dowiedzieć się więcej, przeczytaj artykuł Instalowanie dodatku Google API, aby dowiedzieć się więcej o instalowaniu biblioteki dodatków.

Omówienie interfejsu API

Biblioteka dodatków jest otoką interfejsów API platformy, więc klasy obsługujące funkcję akcesoriów USB są podobne. Nawet jeśli korzystasz z biblioteki dodatków, możesz skorzystać z dokumentacji referencyjnej android.hardware.usb.

Uwaga: występuje jednak drobna różnica w wykorzystaniu między biblioteką dodatków a interfejsami API platformy, o której musisz wiedzieć.

W tabeli poniżej opisujemy klasy obsługujące interfejsy API akcesoriów USB:

Kategoria Opis
UsbManager Umożliwia wyliczenie podłączonych akcesoriów USB i komunikowanie się z nimi.
UsbAccessory Reprezentuje akcesorium USB i zawiera metody dostępu do jego informacji identyfikacyjnych.

Różnice w wykorzystaniu między biblioteką dodatków a interfejsami API platformy

Istnieją 2 różnice w sposobie korzystania z biblioteki dodatkowej interfejsów API Google i z interfejsami API platformy.

Jeśli korzystasz z biblioteki dodatków, musisz uzyskać obiekt UsbManager w ten sposób:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Jeśli nie korzystasz z biblioteki dodatków, musisz uzyskać obiekt UsbManager w ten sposób:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

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

Gdy filtrujesz połączone akcesoria za pomocą filtra intencji, obiekt UsbAccessory jest zawarty w intencji przekazywanej do aplikacji. Jeśli korzystasz z biblioteki dodatków, musisz uzyskać obiekt UsbAccessory w ten sposób:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Jeśli nie korzystasz z biblioteki dodatków, musisz uzyskać obiekt UsbAccessory w ten sposób:

Kotlin

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

Java

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

Wymagania dotyczące pliku manifestu na Androidzie

Na liście poniżej opisujemy, co należy dodać do pliku manifestu aplikacji przed rozpoczęciem korzystania z interfejsów API akcesoriów USB. Przykłady plików manifestu i plików zasobów pokazują, jak zadeklarować te elementy:

  • Nie wszystkie urządzenia z Androidem obsługują interfejsy API akcesoriów USB, dlatego dodaj element <uses-feature>, który deklaruje, że Twoja aplikacja korzysta z funkcji android.hardware.usb.accessory.
  • Jeśli używasz biblioteki dodatków, dodaj element <uses-library> określający com.android.future.usb.accessory biblioteki.
  • Ustaw minimalny pakiet SDK aplikacji na poziom API 10, jeśli używasz biblioteki dodatków, lub na 12, jeśli używasz pakietu android.hardware.usb.
  • Jeśli chcesz, aby aplikacja była powiadamiana o podłączonym akcesorium USB, w głównej aktywności określ parę elementów <intent-filter> i <meta-data> dla intencji android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Element <meta-data> wskazuje zewnętrzny plik zasobów XML z deklaracją informacji identyfikujących akcesorium, które chcesz wykryć.

    W pliku zasobów XML zadeklaruj elementy <usb-accessory> dla akcesoriów, które chcesz filtrować. Każdy element <usb-accessory> może mieć te atrybuty:

    • manufacturer
    • model
    • version

    Filtrowanie według: version nie jest zalecane. Akcesorium lub urządzenie nie zawsze muszą podawać ciąg znaków wersji (umyślnie lub przypadkowo). Jeśli Twoja aplikacja zadeklaruje atrybut wersji, według którego ma być filtrowany, a akcesorium lub urządzenie nie będą zawierać ciągu znaków wersji, we wcześniejszych wersjach Androida wystąpi błąd NullPointerException. Ten problem został rozwiązany w Androidzie 12.

    Zapisz plik zasobów w katalogu res/xml/. Nazwa pliku zasobów (bez rozszerzenia .xml) musi być taka sama jak nazwa określona w elemencie <meta-data>. Format pliku zasobów XML jest też podany w przykładzie poniżej.

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

Ten przykład przedstawia przykładowy plik manifestu i odpowiadający mu plik zasobów:

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

W tym przypadku poniższy plik zasobów należy zapisać w usłudze res/xml/accessory_filter.xml i określić, że wszystkie akcesoria o odpowiednim modelu, producenta i wersji powinny zostać odfiltrowane. Akcesorium wysyła te atrybuty do urządzenia z Androidem:

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

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

Praca z akcesoriami

Gdy użytkownicy podłączają akcesoria USB do urządzenia z Androidem, system Android może określić, czy aplikacja jest zainteresowana podłączonymi akcesoriami. Jeśli tak, w razie potrzeby możesz skonfigurować komunikację z akcesorium. Aby to umożliwić, aplikacja musi:

  1. Wykrywaj połączone akcesoria za pomocą filtra intencji, który odfiltrowuje zdarzenia dołączonych do akcesoriów, lub przez wyliczenie połączonych akcesoriów i znalezienie odpowiedniego.
  2. Poproś użytkownika o pozwolenie na komunikowanie się z akcesoriami, jeśli jeszcze nie zostały uzyskane.
  3. Komunikuj się z akcesorium, odczytując i zapisując dane w odpowiednich punktach końcowych interfejsu.

Odkryj akcesorium

Aplikacja może wykrywać akcesoria za pomocą filtra intencji, który będzie otrzymywać powiadomienia, gdy użytkownik połączy się z urządzeniem, lub przez listę akcesoriów, które są już połączone. Filtr intencji jest przydatny, jeśli chcesz, by aplikacja automatycznie wykrywała odpowiednie akcesorium. Wyliczenie połączonych akcesoriów przydaje się, gdy chcesz zobaczyć listę wszystkich podłączonych akcesoriów lub gdy aplikacja nie została odfiltrowana pod kątem intencji.

Używanie filtra intencji

Aby aplikacja wykrywała określone akcesorium USB, możesz określić filtr intencji, który będzie filtrował intencję android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Oprócz tego filtra intencji musisz też podać plik zasobów, który określa właściwości akcesorium USB, takie jak producent, model i wersja.

Z przykładu poniżej dowiesz się, jak zadeklarować filtr intencji:

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

Z przykładu poniżej dowiesz się, jak zadeklarować plik zasobów określający akcesoria USB, które Cię interesują:

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

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

W swojej aktywności możesz uzyskać identyfikator UsbAccessory reprezentujący dołączone akcesorium z intencji w ten sposób (z biblioteką dodatków):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

lub w ten sposób (za pomocą interfejsów API platformy):

Kotlin

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

Java

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

Wyliczenie akcesoriów

Możesz poprosić aplikację o wyliczenie akcesoriów, które zidentyfikowały się podczas jej działania.

Aby wyświetlić tablicę ze wszystkimi podłączonymi akcesoriami USB, użyj metody 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();

Uwaga: obsługiwane jest tylko 1 połączone akcesorium naraz.

Uzyskiwanie uprawnień do komunikacji z akcesoriami

Przed rozpoczęciem komunikacji z akcesoriami USB aplikacja musi mieć pozwolenie od użytkowników.

Uwaga: jeśli aplikacja używa filtra intencji do wykrywania podłączonych akcesoriów, automatycznie otrzymuje pozwolenie, gdy użytkownik zezwala na obsługę intencji. Jeśli nie, przed połączeniem się z akcesoriami musisz wyraźnie poprosić o to w aplikacji.

W niektórych sytuacjach pytanie o zgodę może być konieczne, np. gdy aplikacja wymienia akcesoria, które są już podłączone, a potem chce się z nim połączyć. Musisz sprawdzić uprawnienia dostępu do akcesorium, zanim spróbujesz się z nim połączyć. W przeciwnym razie w przypadku odmowy dostępu do akcesorium pojawi się błąd środowiska wykonawczego.

Aby uzyskać wyraźne pozwolenie, najpierw utwórz odbiornik. Ten odbiornik nasłuchuje intencji, która jest wysyłana, gdy dzwonisz pod numer requestPermission(). Wywołanie requestPermission() wyświetla użytkownikowi okno z prośbą o pozwolenie na połączenie się z akcesoriami. Poniższy przykładowy kod pokazuje, jak utworzyć 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 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);
                }
            }
        }
    }
};

Aby zarejestrować odbiornik, wpisz w swojej aktywności metodę onCreate():

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

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

Kotlin

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

Java

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

Gdy użytkownik odpowie w oknie, odbiornik otrzyma intencję zawierającą dodatkowy element (EXTRA_PERMISSION_GRANTED), który jest wartością logiczną reprezentującą odpowiedź. Zanim połączysz się z akcesoriami, sprawdź, czy ma wartość true (prawda).

Komunikacja z akcesoriami

Możesz komunikować się z akcesorium za pomocą interfejsu UsbManager, aby uzyskać deskryptor pliku, który pozwoli Ci skonfigurować strumienie danych wejściowych i wyjściowych do odczytywania i zapisywania w nim danych. Strumienie reprezentują wejściowe i wyjściowe punkty końcowe akcesorium. Komunikację między urządzeniem a akcesorium należy skonfigurować w innym wątku, aby nie zablokować głównego wątku interfejsu. Ten przykład pokazuje, jak otworzyć akcesorium, aby się z nim połączyć:

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

W metodzie run() wątku możesz odczytywać i zapisywać w akcesorium obiekty FileInputStream lub FileOutputStream. Podczas odczytywania danych z akcesorium z obiektem FileInputStream upewnij się, że używany bufor jest wystarczająco duży, by zapisać dane pakietu USB. Protokół akcesoriów Android obsługuje bufory pakietów o rozmiarze do 16 384 bajtów, więc dla uproszczenia możesz zawsze zadeklarować taki rozmiar.

Uwaga: na niższym poziomie pakiety mają 64 bajty w przypadku pełnej szybkości akcesoriów USB i 512 bajtów w przypadku akcesoriów USB szybkiego dostępu. Protokół akcesoriów Android dla uproszczenia łączy pakiety dla obu prędkości w jeden pakiet logiczny.

Więcej informacji o używaniu wątków na Androidzie znajdziesz w artykule Procesy i wątki.

Zakończ komunikację z akcesoriami

Po zakończeniu komunikacji z akcesoriami lub odłączonym akcesorium zamknij otwarty deskryptor pliku, wywołując metodę close(). Aby nasłuchiwać innych zdarzeń, utwórz odbiornik, jak poniżej:

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

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