2011年5月15日日曜日

Android USB Host

USB Host

Android-powered デバイスが USB host mode の場合、USB host として振る舞い、バスに電力を供給し、接続された USB デバイスを列挙します。Usb host mode は Android 3.1 以上でサポートされています。

---

API Overview


まずはじめに、動かすために必要なクラスを理解しましょう。次の表は android.hardware.usb パッケージ内の USB host APIs を記述したものです。

ClassDescription
UsbManager接続している USB デバイスの列挙と通信を許可する
UsbDevice接続された USB デバイスを表し、それを識別するための情報、インタフェース、エンドポイントにアクセスするためのメソッドを含む
UsbInterfaceデバイスに対する functionality のセットを定義する USB デバイスのインタフェースを表す
UsbEndpointこのインタフェースへの通信チャネルであるインタフェースエンドポイントを表す。1つのインタフェースは1個以上のエンドポイントを持つことができ、通常はデバイスと two-way 通信するために入力と出力それぞれのエンドポイントを持つ
UsbDeviceConnectionエンドポイントでデータを転送する、デバイスへの接続を表す。このクラスによって送ったデータを戻したり、同期や非同期を forth することができる
UsbRequestUsbDeviceConnection を通してデバイスと通信するための非同期リクエストを表す
UsbConstansLinux kernel の linux/usb/ch9.h での定義に対応する USB 定数の定義


ほとんどの状況では、USB デバイスと通信するときにこれらのクラス全てを使う必要があります(UsbRequest は非同期通信を行う場合のみ必要です)。一般的には使いたい UsbDevice を取り出すために UsbManager を取得します。デバイスを取得したら、適切な UsbInterface と、そのインタフェースの UsbEndpoint を見つける必要があります。正しいエンドポイントが得られたら、UsbDeviceConnection を開いて、USB デバイスと通信します。

---

Android Manifest Requirements


次のリストは、USB host APIs と動作する前にアプリケーションのマニフェストに追加しなければならない項目です。

  • すべての Android-powered デバイスが USB accessory APIs をサポートする保証があるわけではないので、アプリケーションが android.hardware.usb.accessory 機能を使うことを宣言した <uses-feature> エレメントを含むようにする

  • アプリケーションの最小 SDK を API Level 12 もしくはそれ以上に設定する。USB host APIs は以前の API level では提供されない。

  • USB デバイスが取り付けられたときにアプリケーションに通知したい場合は、android.hardware.usb.action.USB_DEVICE_ATTACHED インテント用の <intent-filter><meta-data> エレメントのペアをメインの Activity に指定する
    <meta-data> エレメントでは、検出したデバイスの識別情報を宣言した外部の XML リソースを示す

    XML リソースファイルでは、フィルターしたいデバイス用の <usb-device> エレメント を宣言する
    <usb-device> は次の属性を持つことができる

     ・ vendor-id
     ・ product-id
     ・ class
     ・ subclass
     ・ protocol (デバイスもしくはインタフェース)

    一般的には、特定のデバイスに対して filter したい場合には vendor と product ID を使い、マスストレージデバイスやデジタルカメラなど USB デバイスのグループに対して filter したい場合には class, subclass, protocol を使う
    リソースファイルは /res/xml ディレクトリに保存する
    リソースファイルの名前 (.xml 拡張子を除いた部分) は <meta-data> エレメントで指定したものと同じでなければならない
    XML リソースファイルの形式も次の example にある



Manifest and resource file examples

次の例はサンプルマニフェストとそれに対応するリソースファイルです。

<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 に保存されているべきであり、vendor IDと product ID に応じてフィルターされるすべての USB デバイスを指定します。これらの ID はデバイス特有のもので、デバイスメーカーによって指定されます。

<?xml version="1.0" encoding="utf-8"?>

<resources>
<usb-device vendor-id="1234" product-id="5678" />
</resources>


---

Working with Devices


ユーザーが USB デバイスを Android-powered デバイスに接続したとき、Android のシステムは接続されたデバイスにあなたのアプリケーションが反応したいかどうか決めることができます。その場合、望むのであればデバイスとの通信をセットアップすることができます。このようにするには、アプリケーションは

  • 1. USB デバイスが取り付けられたイベントをフィルターする intent filter を使うか、すでに接続されている USB デバイスを列挙して接続された USB デバイスを見つける

  • 2. USB デバイスと通信するためのパーミッションをユーザーに尋ねる(またパーミッションを得られていない場合)

  • 3. 適切なインタフェースエンドポイントでデータを読み書きすることで USB デバイスと通信する



Discovering a device

アプリケーションは、ユーザーが USB デバイスを接続したときに知らせてくれる intent filter を使うか、もしくはすでに接続されている USB デバイスを列挙するか、のいずれかの方法をつかって USB デバイスを見つけることができます。アプリケーションが意図している USB デバイス を自動的に検出したい場合、intent filter を使うことはとても便利です。intent をフィルターせず、すべての接続されているデバイスのリストを取得したい場合、接続されている USB デバイスの列挙は便利です。


Using an intent filter

アプリケーションで特定の USB デバイスを見つけるために、android.hardware.usb.action.USB_DEVICE_ATTACHED intent に対してフィルターする intent filter を指定することができます。この intent filter と一緒に USB デバイスのプロパティ(product ID, vendor ID)を宣言したリソースファイルを指定する必要があります。
あなたの設定したデバイスフィルターに一致するデバイスをユーザーが接続すると、システムはあなたのアプリケーションをスタートさせるかどうかを聞くダイアログを表示します。ユーザーが了承すると、あなたのアプリケーションは、デバイスが取り外されるまでデバイスにアクセスするパーミッションを自動的に与えられます。

intent filter の定義は、例えば次のようになります。


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


対応するリソースファイルは、例えば次のようになります。


<?xml version="1.0" encoding="utf-8"?>

<resources>
<usb-device vendor-id="1234" product-id="5678" />
</resources>


activity 内では、取り付けられたデバイスを表す UsbDevice を次のようにして intent から取得することができます。

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



Enumerating devices

アプリケーションが起動している間、現在接続されている USB デバイスの inspecting したい場合、バス上のデバイスを列挙することで実現できます。
getDeviceList() メソッドを使って、接続されている全ての USB デバイスのハッシュマップを取得することができます。ハッシュマップは USB デバイスの名前がキーに使われており、マップからデバイスを取得するには次のようにします。


UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap deviceList = manager.getDeviceList();
Iterator deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
UsbDevice device = deviceIterator.next();
// your code
}



Obtaining permission to communicate with a device

USB デバイスと通信する前に、アプリケーションはユーザーから許可を得る必要があります。

注意: アプリケーションが intent filter を使って接続された USB デバイスを見つける場合、アプリケーションがその intent を処理することをユーザーが許可すると、自動的にパーミッションを受信します。そうではない場合、デバイスに接続する前にアプリケーション内で明示的にパーミッションをリクエストしなければなりません。

アプリケーションですでに接続されている USB デバイスを列挙して、いずれかの1つと通信したい場合など、いくつかの状況で明示的にパーミッションを聞く必要があります。デバイスと通信を試みる前に、デバイスにアクセスするパーミッションをチェックしなければなりません。そうしないと、ユーザーが デバイスへのアクセスを拒否した場合にランタイムエラーを受信するでしょう。

明示的にパーミッションを得るために、まず broadcast receiver を作成します。この receiver では requestPermission() を呼んだときに broadcast を取得する intent を listen するようにします。requestPermission() を呼ぶとユーザーに デバイスに接続するかどうかのパーミッションを聞くダイアログが表示されます。
例えば、次のようにして broadcast receiver を作成します。


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){
//call method to set up device communication
}
}
else {
Log.d(TAG, "permission denied for device " + device);
}
}
}
}
};


broadcast receiver を登録するには、これを Activity の onCreate() メソッド内に入れます:


UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);


デバイス への接続パーミッションを尋ねるダイアログを表示するには、requestPermission() メソッドを呼びます:


UsbAccessory accessory;
...
mUsbManager.requestPermission(device, mPermissionIntent);


ユーザーがダイアログに返答すると、broadcast receiver は答えを表す boolean の EXTRA_PERMISSION_GRANTED extra を含む intent を受信します。デバイスに接続するまえに、この extra が true になっているかチェックします。


Communicating with a device

USB デバイスとは同期通信、非同期通信のいずれも可能です。どちらのケースでも、UI スレッドをブロックしないために、データを転送するための別のスレッドを作成するべきです。デバイスと正しく通信をセットアップするには、デバイスの適切な UsbInterfaceUsbEndpoint を取得し、UsbDeviceConnection でこのエンドポイントにリクエストを送信する必要があります。一般的にコードで行うべきことは、

  • UsbDevice オブジェクトの属性 (product ID, vendor ID など) もしくはデバイスクラスをチェックして、通信したいデバイスかどうか調べる

  • 通信したいデバイスかどうか確認したら、通信に使いたい適切な UsbInterface とそのインタフェースの適切な UsbEndpoint を見つける
    インタフェースは1つ以上のエンドポイントを持つことができ、通常はtwo-way通信用に入力と出力のエンドポイントを持ちます。

  • 正しいエンドポイントを見つけたら、そのエンドポイントで UsbDeviceConnection を開きます。

  • 転送したいデータをエンドポイント上で bulkTransfer()controlTransfer() メソッド使って供給します。
    メイン UI スレッドをブロックしないように、このステップは別のスレッドで行うべきです。Android でスレッドを使うためのより詳しい情報は Processes and Threads を参照してください。


次のコードスニペットは、同期データ転送を行う自明な方法です。あなたのコードは正しいインタフェースと通信用のエンドポイントを正しく見つけるためのロジックが必要ですし、データの転送をメイン UI スレッドではなく異なるスレッドで行うべきです。


private Byte[] bytes
private static int TIMEOUT = 0;
private boolean forceClaim = true;

...

UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = intf.getEndpoint(0);
UsbDeviceConnection connection = mUsbManager.openDevice(device);
connection.claimInterface(intf, forceClaim);
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread


データを非同期に送るには、UsbRequest クラスを使って初期化(initializeと非同期リクエストをキューに入れます(queue。そして、requestWait() で結果を待ちます。

より詳しい情報は、非同期 bulk transfer を行う方法を示した AdbTest サンプルを見てください。また、エンドポイントでの非同期割り込みを listen する方法を示した MissleLauncher サンプルも見てください。


Terminating communication with a device

デバイスとの通信が完了した、もしくはデバイスが取り外されたときには、releaseInterface()close() を呼んで UsbInterfaceUsbDeviceConnection を閉じます。取り外されたイベントを察知するには、次のような broadcast receiver を作成します:


BroadcastReceiver mUsbReceiver = 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
}
}
}
};


マニフェストではなく、アプリケーション内で broadcast receiver を作成することで、アプリケーションが起動している間だけ取り外しイベントを処理できるようになります。これにより、取り外しイベントが全てのアプリケーションに broadcast されることなく、現在起動しているアプリケーションにのみ送られます。



Related Samples

1. AdbTest
2. MissleLauncher

0 件のコメント:

コメントを投稿