USB ホストの概要

Android 搭載デバイスを USB ホストモードにすると、Android 搭載デバイスは 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 は非同期通信を実行する場合にのみ必要です)。一般に、UsbManager を取得して、必要な UsbDevice を取得します。デバイスを取得したら、通信を行う適切な UsbInterface とそのインターフェースの UsbEndpoint を見つける必要があります。正しいエンドポイントを取得したら、UsbDeviceConnection を開いて USB デバイスと通信します。

Android マニフェストの要件

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

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

    XML リソースファイルで、フィルタする USB デバイス用の <usb-device> 要素を宣言します。<usb-device> の属性を次のリストに示します。一般に、特定のデバイスをフィルタする場合はベンダーとプロダクト ID を使用し、USB デバイスのグループ(マスストレージ デバイスやデジタルカメラなど)をフィルタする場合はクラス、サブクラス、プロトコルを使用します。こうした属性を一切指定しないこともできるほか、すべてを指定することもできます。属性を指定しないとすべての 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 デバイスを列挙すると便利です。

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

アプリで特定の USB デバイスを検出するために、インテント フィルタを指定して android.hardware.usb.action.USB_DEVICE_ATTACHED インテントをフィルタできます。このインテント フィルタに加えて、プロダクトやベンダー 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 デバイスを列挙してそのうちの 1 台と通信する場合などです。デバイスと通信する前に、デバイスへのアクセス権限があるかを確認する必要があります。そうしないと、ユーザーがデバイスへのアクセスを拒否した場合にランタイム エラーが発生します。

明示的に権限を取得するには、まずブロードキャスト レシーバを作成します。このレシーバは、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 クラスを使用して非同期リクエストの initializequeue を行い、requestWait() を使用して結果を待ちます。

詳しくは、非同期のバルク転送の方法を示した AdbTest のサンプルと、割り込みエンドポイントでの非同期リッスンの方法を示した MissileLauncher のサンプルをご覧ください。

デバイスとの通信の終了

デバイスとの通信が終了したときや、デバイスの接続が解除された場合は、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
                }
            }
        }
    };
    

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