6 月 3 日の「#Android11: The Beta Launch Show」にぜひご参加ください。

USB アクセサリの概要

USB アクセサリ モードでは、ユーザーが Android 搭載デバイス用に設計された USB ホスト ハードウェアを接続できます。アクセサリは、Android Accessory Development Kit ドキュメントに記載されている Android アクセサリ プロトコルに準拠している必要があります。これにより、USB ホストとして動作できない Android 搭載デバイスでも USB ハードウェアと通信できるようになります。Android 搭載デバイスを USB アクセサリ モードにすると、接続された Android USB アクセサリがホストとして動作し、USB バスに電源を供給して、接続されているデバイスを列挙します。Android 3.1(API レベル 12)が USB アクセサリ モードをサポートしているほか、幅広いデバイスのサポートを可能にするために、この機能は Android 2.3.4(API レベル 10)にも移植されています。

適切な USB アクセサリ API を選択する

USB アクセサリ API は Android 3.1 でプラットフォームに導入されましたが、Android 2.3.4 でも Google API アドオン ライブラリを使用して提供されています。こうした API は外部ライブラリを使用して移植されているため、USB アクセサリ モードをサポートするためにインポートできるパッケージは 2 つ存在します。サポートする Android 搭載デバイスに応じて、次のいずれかの方法の使用が必要となります。

  • com.android.future.usb: Android 2.3.4 で USB アクセサリ モードをサポートするため、移植された USB アクセサリ API が Google API アドオン ライブラリに収録されており、この名前空間に含まれています。Android 3.1 でも、このアドオン ライブラリを使用して作成されたアプリをサポートするために、この名前空間内のクラスのインポートと呼び出しをサポートしています。このアドオン ライブラリは android.hardware.usb アクセサリ API のシンラッパーであり、USB ホストモードはサポートしていません。USB アクセサリ モードに対応した幅広いデバイスをサポートする場合は、アドオン ライブラリを使用して、このパッケージをインポートします。すべての Android 2.3.4 デバイスに USB アクセサリ機能のサポートが求められているわけではない、という点に注意してください。この機能をサポートするかどうかは各デバイス メーカーが判断します。マニフェスト ファイルでの宣言が必要なのはこのためです。
  • android.hardware.usb: この名前空間には、Android 3.1 で USB アクセサリ モードをサポートするクラスが含まれています。このパッケージはフレームワーク API の一部として含まれているため、Android 3.1 はアドオン ライブラリを使用せずに USB アクセサリ モードをサポートしています。USB アクセサリ モードをサポートするハードウェアを持つ Android 3.1 以降のデバイスのみを対象とする場合(このことはマニフェスト ファイル内で宣言が可能です)は、このパッケージを使用します。

Google API アドオン ライブラリをインストールする

アドオンをインストールする場合は、SDK Manager で Google API Android API 10 パッケージをインストールすることによって、アドオンをインストールできます。アドオン ライブラリのインストールについて詳しくは、Google API アドオンのインストールについての説明をご覧ください。

API の概要

アドオン ライブラリはフレームワーク API のラッパーであるため、USB アクセサリ機能をサポートするクラスは類似しています。アドオン ライブラリを使用する場合でも、android.hardware.usb のリファレンス ドキュメントを使用できます。

注: ただし、アドオン ライブラリとフレームワーク API との間には、わずかに使用方法の違いがあるため、ご注意ください。

USB アクセサリ API をサポートしているクラスを、次の表に示します。

クラス 説明
UsbManager 接続された USB デバイスの列挙と通信が可能です。
UsbAccessory USB アクセサリを表します。また、識別情報にアクセスするためのメソッドを含んでいます。

アドオン ライブラリとプラットフォーム API の使用方法の違い

Google API アドオン ライブラリとプラットフォーム API の使用には、使用方法の違いが 2 つあります。

アドオン ライブラリを使用している場合は、次の方法で UsbManager オブジェクトを取得する必要があります。

Kotlin

    val manager = UsbManager.getInstance(this)
    

Java

    UsbManager manager = UsbManager.getInstance(this);
    

アドオン ライブラリを使用していない場合は、次の方法で UsbManager オブジェクトを取得する必要があります。

Kotlin

    val manager = getSystemService(Context.USB_SERVICE) as UsbManager
    

Java

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

接続されているアクセサリをインテント フィルタでフィルタする場合、アプリに渡されるインテント内に UsbAccessory オブジェクトが含まれています。アドオン ライブラリを使用している場合は、次の方法で UsbAccessory オブジェクトを取得する必要があります。

Kotlin

    val accessory = UsbManager.getAccessory(intent)
    

Java

    UsbAccessory accessory = UsbManager.getAccessory(intent);
    

アドオン ライブラリを使用していない場合は、次の方法で UsbAccessory オブジェクトを取得する必要があります。

Kotlin

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

Java

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

Android マニフェストの要件

USB アクセサリ API を使用する前にアプリのマニフェスト ファイルに追加する必要がある項目を、次のリストに示します。こうした項目の宣言方法については、マニフェストとリソース ファイルの例をご覧ください。

  • すべての Android 搭載デバイスが USB アクセサリ API をサポートしているわけではないため、アプリで android.hardware.usb.accessory 機能を使用することを宣言する <uses-feature> 要素を含めます。
  • アドオン ライブラリを使用している場合は、ライブラリの com.android.future.usb.accessory を指定した <uses-library> 要素を追加します。
  • アドオン ライブラリを使用している場合はアプリの最小 SDK を API レベル 10 に設定します。または、android.hardware.usb パッケージを使用している場合は 12 に設定します。
  • USB アクセサリが接続されたときにアプリに通知されるようにする場合は、メイン アクティビティに android.hardware.usb.action.USB_ACCESSORY_ATTACHED インテント用の <intent-filter> 要素と <meta-data> 要素のペアを指定します。<meta-data> 要素で、検出するアクセサリについての識別情報を宣言する外部 XML リソース ファイルを指定します。

    この XML リソース ファイル内で、フィルタするアクセサリについての <usb-accessory> 要素を宣言します。各 <usb-accessory> には次の属性を指定できます。

    • manufacturer
    • model
    • version

    リソース ファイルを 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 を、次のようにして(アドオン ライブラリを使用して)インテントから取得できます。

Kotlin

    val accessory = UsbManager.getAccessory(intent)
    

Java

    UsbAccessory accessory = UsbManager.getAccessory(intent);
    

プラットフォーム API を使用する場合は次のようになります。

Kotlin

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

Java

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

アクセサリを列挙する

アプリが実行している間、アプリはシステムが識別済みのアクセサリを列挙できます。

getAccessoryList() メソッドを使用して、接続されているすべての USB アクセサリの配列を取得します。

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

注: 一度に 1 つの接続済みアクセサリのみがサポートされています。

アクセサリとの通信の権限を取得する

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

ブロードキャスト レシーバを登録するには、アクティビティの 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 accessory: UsbAccessory
    ...
    usbManager.requestPermission(accessory, permissionIntent)
    

Java

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

ユーザーがダイアログに応答すると、ブロードキャスト レシーバは、回答を表すブール値である EXTRA_PERMISSION_GRANTED エクストラを含んだインテントを受信します。アクセサリに接続する前に、このエクストラの値が true であるかを確認します。

アクセサリと通信する

UsbManager を使用してファイル記述子を取得することでアクセサリと通信できます。ファイル記述子には、記述子に対するデータの読み取りや書き込みを行う入出力ストリームを設定できます。ストリームは、アクセサリの入出力用バルク エンドポイントを表します。メインの UI スレッドをブロックしないよう、デバイスとアクセサリとの間の通信は別のスレッドに設定する必要があります。通信するアクセサリを開く方法を次の例に示します。

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

スレッドの run() メソッドで FileInputStream または FileOutputStream オブジェクトを使用することで、アクセサリへの読み取りや書き込みが可能です。FileInputStream オブジェクトを使用してアクセサリからデータを読み取る場合は、使用するバッファに、USB パケットのデータを保存できる十分な容量があることを確認します。Android アクセサリ プロトコルでは最大 16,384 バイトのパケット バッファをサポートしているため、常にこのサイズでバッファを宣言すると処理を簡略化できます。

注: 下位レベルでは、USB Full-Speed アクセサリのパケットは 64 バイト、USB High-Speed アクセサリのパケットは 512 バイトです。Android アクセサリ プロトコルでは、処理を簡略化するために、どちらの速度でも複数のパケットを 1 つの論理パケットにバンドルします。

Android でのスレッドの使用について詳しくは、プロセスとスレッドについての説明をご覧ください。

アクセサリとの通信を終了する

アクセサリとの通信が終了したときや、アクセサリの接続が解除された場合は、close() を呼び出すことで、開いたファイル記述子を閉じます。接続解除のイベントをリッスンするには、次のようにしてブロードキャスト レシーバを作成します。

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

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