USB ホストの概要

Android 搭載デバイスを USB ホストモードにすると、USB ホストとして機能し、バスに電力を供給して、接続されている USB デバイスを列挙します。USB ホストモードは Android 3.1 以降でサポートされています。

API の概要

まず、使用する必要のあるクラスを理解することが重要です。次の表に、android.hardware.usb パッケージ内の USB ホスト API を示します。

表 1. USB ホスト API

クラス 説明
UsbManager 接続された USB デバイスの列挙と通信を可能にします。
UsbDevice 接続された USB デバイスを表し、識別情報、インターフェース、エンドポイントにアクセスするためのメソッドを含みます。
UsbInterface USB デバイスのインターフェースを表します。デバイスの機能セットを定義します。デバイスは、通信するインターフェースを 1 つ以上持つことができます。
UsbEndpoint このインターフェースの通信チャネルである、インターフェースのエンドポイントを表します。インターフェースは 1 つ以上のエンドポイントを持つことができ、通常は、デバイスとの双方向通信のための入出力エンドポイントを持ちます。
UsbDeviceConnection エンドポイントのデータを転送する、デバイスへの接続を表します。このクラスを使用すると、同期的または非同期的にデータを送受信できます。
UsbRequest UsbDeviceConnection を通じてデバイスと通信する非同期リクエストを表します。
UsbConstants Linux カーネルの linux/usb/ch9.h の定義に対応する USB 定数を定義します。

ほとんどの場合、USB デバイスと通信する際は、これらのクラスをすべて使用する必要があります(UsbRequest は非同期通信を実行する場合にのみ必要です)。通常は、目的の UsbDevice を取得するために UsbManager を取得します。デバイスが手元にあるら、そのインターフェースの通信に使用する適切な UsbInterfaceUsbEndpoint を見つける必要があります。正しいエンドポイントを取得したら、UsbDeviceConnection を開いて USB デバイスと通信します。

Android マニフェストの要件

USB ホスト API を使用する前に、アプリのマニフェスト ファイルに追加する必要のあるものを次に示します。

  • すべての Android 搭載デバイスで USB ホスト API のサポートが保証されているわけではないため、<uses-feature> 要素を追加して、アプリが android.hardware.usb.host 機能を使用することを宣言します。
  • アプリの最小 SDK を API レベル 12 以上に設定します。以前の API レベルには、USB ホスト API はありません。
  • USB デバイスが接続されていることをアプリに通知するには、メイン アクティビティで android.hardware.usb.action.USB_DEVICE_ATTACHED インテントに <intent-filter> 要素と <meta-data> 要素のペアを指定します。<meta-data> 要素は、検出するデバイスの識別情報を宣言する外部 XML リソース ファイルを指定します。

    XML リソース ファイルで、フィルタする USB デバイスの <usb-device> 要素を宣言します。<usb-device> の属性は次のとおりです。一般に、特定のデバイスでフィルタする場合はベンダー ID とプロダクト ID を使用し、マスストレージ デバイスやデジタルカメラなどの USB デバイスのグループをフィルタする場合は、クラス、サブクラス、プロトコルを使用します。これらの属性を 1 つでも指定することも、すべて指定することもできます。属性を指定しないと、すべての USB デバイスに一致するため、アプリで必要な場合にのみ行ってください。

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol(デバイスまたはインターフェース)

    リソース ファイルを 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 システムは、その接続済みデバイスにアプリが関連しているかどうかを判別できます。その場合は、必要に応じてデバイスとの通信を設定できます。そのためには、アプリで次の処理を行う必要があります。

  1. インテント フィルタを使用してユーザーが USB デバイスに接続したときに通知されるようにするか、すでに接続されている USB デバイスを列挙することで、接続されている USB デバイスを検出します。
  2. USB デバイスへの接続を許可するようユーザーに求めます(まだ許可を取得していない場合)。
  3. 適切なインターフェース エンドポイントでデータの読み取りと書き込みを行い、USB デバイスと通信します。

デバイスを検出する

アプリで USB デバイスを検出するには、インテント フィルタを使用してユーザーがデバイスに接続したときに通知されるようにするか、すでに接続されている USB デバイスを列挙します。アプリが目的のデバイスを自動的に検出できるようにする場合は、インテント フィルタを使用すると便利です。接続されている USB デバイスの列挙は、接続されているすべてのデバイスのリストを取得する場合や、アプリでインテントのフィルタを行わない場合に便利です。

インテント フィルタを使用する

アプリで特定の USB デバイスを検出するには、インテント フィルタを指定して、android.hardware.usb.action.USB_DEVICE_ATTACHED インテントをフィルタすることができます。このインテント フィルタとともに、製品 ID やベンダー ID など、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>

アクティビティでは、次のように、接続されたデバイスを表す UsbDevice をインテントから取得できます。

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

必要であれば、ハッシュマップからイテレータを取得し、各デバイスを 1 つずつ処理することもできます。

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() メソッドに以下を追加します。

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 device: UsbDevice
...
usbManager.requestPermission(device, permissionIntent)

Java

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

ユーザーがダイアログに返信すると、ブロードキャスト レシーバは EXTRA_PERMISSION_GRANTED エクストラ(回答を表すブール値)を含むインテントを受け取ります。デバイスに接続する前に、このエクストラの値が true であることを確認します。

デバイスと通信する

USB デバイスとの通信は、同期または非同期のいずれかになります。いずれの場合も、すべてのデータ送信を行う新しいスレッドを作成し、UI スレッドをブロックしないようにする必要があります。デバイスとの通信を適切に設定するには、通信するデバイスの適切な UsbInterfaceUsbEndpoint を取得し、UsbDeviceConnection を使用してこのエンドポイントでリクエストを送信する必要があります。通常、コードは次のようになります。

  • デバイスと通信する必要があるかどうかを判断するには、UsbDevice オブジェクトの属性(製品 ID、ベンダー ID、デバイスクラスなど)を確認します。
  • 確実にデバイスと通信する必要があることを確認したら、通信に使用する適切な UsbInterface と、そのインターフェースの適切な UsbEndpoint を見つけます。インターフェースには 1 つ以上のエンドポイントを設定できます。通常は、双方向通信用の入力エンドポイントと出力エンドポイントがあります。
  • 正しいエンドポイントが見つかったら、そのエンドポイントで 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() で結果を待機します。

デバイスとの通信の終了

デバイスとの通信が完了したとき、またはデバイスが接続解除されている場合は、releaseInterface()close() を呼び出して UsbInterfaceUsbDeviceConnection を閉じます。接続解除されたイベントをリッスンするには、次のようにブロードキャスト レシーバを作成します。

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

マニフェストではなくアプリ内にブロードキャスト レシーバを作成することで、アプリは、実行中のみデタッチされたイベントを処理できます。これにより、接続解除されたイベントは、すべてのアプリにはブロードキャストされず、現在実行中のアプリにのみ送信されます。