Android USB Host APIの活用

AndroidデバイスがUSBホストモードで動作する際、デバイスはUSBバスに電力を供給し、接続されたUSBデバイスの列挙を行います。この機能はAndroid 3.1以降でサポートされています。

API概要

USBホスト機能を利用するには、android.hardware.usbパッケージ内の以下のクラスを理解することが重要です。

  • UsbManager: 接続されているUSBデバイスの列挙と通信を管理します。
  • UsbDevice: 接続されたUSBデバイスを表し、識別情報、インターフェース、エンドポイントへのアクセスを提供します。
  • UsbInterface: USBデバイスの機能セットを定義するインターフェースを表します。1つのデバイスは複数のインターフェースを持つことがあります。
  • UsbEndpoint: インターフェースのエンドポイント(通信チャネル)を表します。双方向通信のために、通常は入力用と出力用のエンドポイントが存在します。
  • UsbDeviceConnection: デバイスへの接続を表し、エンドポイント経由でデータを送受信します。同期または非同期通信が可能です。
  • UsbRequest: UsbDeviceConnectionを介した非同期通信リクエストを表します。
  • UsbConstants: Linuxカーネルのlinux/usb/ch9.hに対応するUSB定数を定義します。

通常、これらのクラス(非同期通信の場合はUsbRequestも)を組み合わせてUSBデバイスと通信します。まずUsbManagerを取得し、目的のUsbDeviceを見つけます。次に、そのデバイスの適切なUsbInterfaceUsbEndpointを特定し、UsbDeviceConnectionを開いて通信を開始します。

マニフェスト要件

USBホストAPIを使用するには、アプリケーションのマニフェストファイルに以下の設定が必要です。

  • android.hardware.usb.host機能を必要とすることを<uses-feature>要素で宣言します。
  • アプリケーションの最小SDKバージョンをAPIレベル12以上に設定します。
  • USBデバイス接続時に通知を受け取りたい場合は、メインアクティビティにandroid.hardware.usb.action.USB_DEVICE_ATTACHEDインテントに対する<intent-filter><meta-data>要素を設定します。<meta-data>要素は、検出したいデバイスの識別情報を記述した外部XMLリソースファイルを指します。

XMLリソースファイルでは、フィルタリングしたいUSBデバイスを<usb-device>要素で宣言します。vendor-idproduct-idclasssubclassprotocol属性を使用して、特定のデバイスまたはデバイスグループをフィルタリングできます。これらの属性を指定しない場合、すべてのUSBデバイスが対象となります。

リソースファイルはres/xml/ディレクトリに保存し、ファイル名は<meta-data>要素で指定したものと一致させる必要があります。

マニフェストとリソースファイルの例

以下は、マニフェストファイルとそれに対応するリソースファイルの例です。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
    <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には以下のように記述します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>

デバイスとの連携

ユーザーがUSBデバイスを接続すると、アプリケーションがそのデバイスに関心があるかどうかをシステムが判断します。関心がある場合、アプリケーションは以下の手順でデバイスとの通信を確立できます。

  1. デバイスの検出: インテントフィルターを使用して接続時に通知を受けるか、既に接続されているデバイスを列挙します。
  2. 接続許可の要求: 必要に応じて、ユーザーにデバイスへの接続許可を求めます。
  3. デバイスとの通信: 適切なインターフェースのエンドポイント経由でデータの送受信を行います。

デバイスの検出

インテントフィルターの使用:

特定のUSBデバイスを検出するには、android.hardware.usb.action.USB_DEVICE_ATTACHEDインテントをフィルタリングするインテントフィルターを設定します。デバイスフィルターリソースファイル(例: @xml/device_filter)で、ベンダーIDやプロダクトIDなどのデバイスプロパティを指定します。一致するデバイスが接続されると、システムはアプリケーションを起動するかどうかをユーザーに尋ねるダイアログを表示します。ユーザーが許可すると、デバイスが切断されるまでアプリケーションは自動的にデバイスへのアクセス権限を得ます。

アクティビティ内で、インテントから接続されたUsbDeviceを取得できます。

UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

デバイスの列挙:

アプリケーション実行中に接続されているすべてのUSBデバイスを検査するには、UsbManager.getDeviceList()メソッドを使用して、接続されているすべてのUsbDeviceのハッシュマップを取得します。ハッシュマップはデバイス名でキー付けされています。

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice specificDevice = deviceList.get("deviceName"); // 特定のデバイスを取得

または、イテレータを使用して各デバイスを順次処理することもできます。

Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
    UsbDevice currentDevice = deviceIterator.next();
    // デバイス処理
}

デバイスとの通信許可の取得

USBデバイスと通信する前に、ユーザーからの明示的な許可が必要です。インテントフィルターを使用してデバイスを検出した場合、ユーザーが許可すれば自動的に権限が付与されます。それ以外の場合は、UsbManager.requestPermission()を呼び出して明示的に許可を要求する必要があります。

許可を要求するには、まずブロードキャストレシーバーを作成し、requestPermission()の呼び出しに応答してブロードキャストされるインテントをリッスンします。

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver = 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){
                        // デバイス通信設定メソッドを呼び出す
                    }
                } else {
                    Log.d(TAG, "Permission denied for device " + device);
                }
            }
        }
    }
};

アクティビティのonCreate()メソッドで、ブロードキャストレシーバーを登録します。

UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
registerReceiver(mUsbReceiver, filter);

ユーザーに許可を求めるダイアログを表示するには、requestPermission()を呼び出します。

UsbDevice targetDevice; // 対象デバイス
// ...
mUsbManager.requestPermission(targetDevice, mPermissionIntent);

ユーザーがダイアログに応答すると、ブロードキャストレシーバーはEXTRA_PERMISSION_GRANTEDインテントを受け取ります。この値がtrueであることを確認してから、デバイスとの通信を開始します。

デバイスとの通信

USBデバイスとの通信は、同期または非同期で行うことができます。いずれの場合も、UIスレッドをブロックしないように、データ転送は別のスレッドで行うべきです。

  1. UsbDeviceの属性(プロダクトID、ベンダーID、デバイスクラスなど)を確認し、通信対象のデバイスか判断します。
  2. 通信するUsbInterfaceと、そのインターフェースの適切なUsbEndpointを見つけます。
  3. 見つけたエンドポイントに対してUsbDeviceConnectionを開きます。
  4. UsbDeviceConnection.bulkTransfer()またはUsbDeviceConnection.controlTransfer()メソッドを使用してデータを送信します。この操作は別スレッドで行う必要があります。

以下は、同期データ転送の基本的な例です。実際の実装では、適切なインターフェースとエンドポイントの特定、および別スレッドでの実行を考慮する必要があります。

private byte[] dataToSend;
private static final int TRANSFER_TIMEOUT = 0;
private final boolean forceInterfaceClaim = true;

// ...

UsbInterface usbInterface = device.getInterface(0); // 例として最初のインターフェース
UsbEndpoint outputEndpoint = usbInterface.getEndpoint(0); // 例として最初のエンドポイント
UsbDeviceConnection connection = mUsbManager.openDevice(device);

if (connection != null && connection.claimInterface(usbInterface, forceInterfaceClaim)) {
    // 新しいスレッドで実行
    new Thread(() -> {
        int bytesSent = connection.bulkTransfer(outputEndpoint, dataToSend, dataToSend.length, TRANSFER_TIMEOUT);
        if (bytesSent >= 0) {
            Log.i(TAG, "Sent " + bytesSent + " bytes");
        } else {
            Log.e(TAG, "Transfer failed");
        }
        // 通信終了後、インターフェースを解放
        // connection.releaseInterface(usbInterface);
        // connection.close();
    }).start();
} else {
    Log.e(TAG, "Could not open device or claim interface");
}

非同期通信を行うには、UsbRequestクラスを使用してリクエストを初期化しキューイングし、requestWait()で結果を待ちます。

デバイスとの通信終了

デバイスとの通信が完了した、またはデバイスが切断された場合は、UsbDeviceConnection.releaseInterface()およびUsbDeviceConnection.close()を呼び出してリソースを解放します。

デバイス切断イベントをリッスンするには、以下のブロードキャストレシーバーを作成します。

private final BroadcastReceiver mDetachedReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice detachedDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (detachedDevice != null) {
                // デバイス切断時のクリーンアップ処理を呼び出す
                Log.d(TAG, "Device detached: " + detachedDevice.getDeviceName());
                // 必要に応じて、関連する接続を閉じる
            }
        }
    }
};

このレシーバーをアプリケーション内で登録することで、アプリケーションが実行中にのみ切断イベントを処理できます。

タグ: Android USB Host UsbManager UsbDevice UsbInterface

8月12日 07:37 投稿