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 アクセサリ モードをサポートするために、Google API アドオン ライブラリにバックポート USB アクセサリ 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 をサポートしているとは限りません。そのため、<uses-feature> 要素を追加して、アプリが android.hardware.usb.accessory 機能を使用することを宣言します。
  • アドオン ライブラリを使用している場合は、ライブラリの 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

    version でフィルタすることはおすすめしません。アクセサリやデバイスでは、意図的かどうかに関係なくバージョン文字列が指定される場合があります。アプリでフィルタするバージョン属性を宣言し、アクセサリまたはデバイスがバージョン文字列を指定していない場合、以前のバージョンの Android では NullPointerException が発生します。この問題は Android 12 で修正されています。

    リソース ファイルを 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);

アクセサリを列挙する

アプリケーションの実行中に識別されたアクセサリを、アプリケーションに列挙させることができます。

接続されているすべての USB アクセサリの配列を取得するには、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();

注: 接続されているアクセサリは、一度に 1 つのみサポートできます。

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

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 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 フルスピード アクセサリのパケットは 64 バイト、USB 高速アクセサリのパケットは 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
            }
        }
    }
};

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