2013年3月22日 星期五

[Android] Android Developer Note Connectivity 7

USB Host(見Android Developer USB Host)
序言
當你的Android裝置是在USB host模式時,
它運作的就如USB host,供應匯流排電力,
並且枚舉連結的USB裝置。
USB host模式在3.1以上的版本被支援。

API Overview
在開始之前,了解有那些class你需要一起處理是很重要的。
下面的表格描述了在android.hardware.usb package裡的USB host的APIs。
ClassDescription
UsbManager允許你列舉以及與連接的USB裝置溝通。
UsbDevice代表一個連結的USB裝置,且含有methods來存取其資料,介面及終端。
UsbInterface代表一個USB裝置的介面,其定義了一套的裝置的功能。
一個裝置可以有一或多個介面開放溝通。
UsbEndpoint代表介面的終端,是一個介面溝通的頻道。一個介面可以有一或多個終端,
且通常有輸入輸出的終端來和裝置作雙向溝通。
UsbDeviceConnection代表一個和裝置的連結,用來在終端上傳出資料。
這個class允許你同步或非同步地往復傳輸資料。
UsbRequest代表一個不同步的request,
來和裝置透過一個UsbDeviceConnection來作溝通。
UsbConstants定義USB的常數,其依照Linux kernel的linux/usb/ch9.h裡的定義。
在大多數的狀況下,當和一個USB裝置溝通時,
你需要使用所有的classes。(UsbRequest只在進行不同步的溝通時需要)。
大體上,你取得一個UsbManager來取回想要的UsbDevice
當你有了裝置,你需要去找適當的UsbInterface以及UsbEndpoint的介面去進行溝通。
依但你取得了正確的終端,開啟一個UsbDeviceConnection來和USB裝置溝通。

Android Manifest Requirements
下面的列表描述了在使用USB host APIs之前,
你需要去加到你的app的manifest檔的東西:
1. 因為並不是所有的Android裝置都保證會支援USB host APIs,
所以將<uses-feature>元素包進來,
以聲明你的app使用android.hardware.usb.host的功能。
2. 設定最小的SDK版本到API Level 12或更高。
USB host APIs並不在早期版本出現。
3. 如果你希望你的app能夠在USB裝置連結時被提醒的話,
在你的主activity裡,
指定<intent-filter><meta-data>的元件配對(element pair)給
android.hardware.usb.action.USB_DEVICE_ATTACHED的intent。
<meta-data>的元件指到一個外部的XML資源檔,
其聲明了你想要去偵測的裝置資訊。

在XML資源檔裡,聲明<uses-feature>元素給你想篩的USB裝置。
下面的列表描述了<usb-device>的屬性。
大體上如果你想要篩特別的裝置且使用class,subclass,
就用vendor和product ID;
如果你要篩一個群組的USB裝置(如大型儲存裝置、數位相機等),
就使用protocol吧!
(編按:這句的子句也硬塞太多= =)
你也可以什麼屬性都不指定。
什麼屬性都不指定的話就代表符合所有的USB裝置,
所以只有在你的app需要的時候才這麼做。
元素有下列幾種:
1. vendor-id
2. product-id
3. class
4. subclass
5. protocol(裝置或介面)
將資源檔存在 目錄底下。資源檔的名稱(不含.xml)必須跟你在 元素中指定的一樣。
XML資源檔的格式在下面的範例會展示。

Manifest and resource file examples
下面展示一個範例的manifest及對應的資源檔:

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

Working with Devices
當使用者連結USB裝置到一個Android的裝置時,
Android系統可以決定你的app是否對連結的裝置有興趣。
要能夠設定成和有興趣的裝置溝通的話,你的app必須做:
1. 探索連接的USB裝置(透過intent filter),
或者枚舉。
2. 跟使用者要授權(如果還沒包含的話)。
3. 跟USB裝置透過在適當的終端介面讀寫溝通。
(這幾點跟前一篇的一樣)

Discovering a device:
你的app可以探索accessories,
透過使用intent filter來被提醒使用者連接,
或者透過枚舉已連接的accessories。
如果你想要能夠去讓你的app自動去偵測到想要的accessory,
用intent filter很有用。
如果你想要拿到所有連接的list且你的app並不作intent的filter的話,
枚舉連接的accessories很有用。


Using an intent filter:
要讓你的app去探索一個特定的USB accessory,
你可以指定intent filter,
來篩android.hardware.usb.action.USB_ACCESSORY_ATTACHED intent。
隨著這個intent filter,你需要指名指定USB accessory的資源檔,
像是product和vendor ID。
當使用者連接一個符合你的accessory filter的accessory,
下面的範例展示如何聲明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>

(前面那段大同小異= =)
下面的範例展示,
如何去聲明那個指名你感興趣的accessories的相對應的資源檔:


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

在你的activity裡,你可以從intent取得代表UsbDevice的連結裝置,
如:

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

Enumerating devices:
如果你的app在執行時,對於偵測所有的現有連結中的USB裝置有興趣,
它可以枚舉在匯流排(bus)上的裝置。
使用的getDeviceList() method來取得對所有連結的裝置的hash map。
如果你想要從map取得一個裝置的話,hash map是由USB裝置的名稱作為鍵值。

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...  HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");

如果想要的話,你也可以只從hash map取得一個走訪器(iterator),
並且一個一個操作每一個裝置。

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
}

Obtaining permission to communicate with a device
在和USB裝置溝通之前,你的app必須從你的使用者那要到許可權。
注意:
如果你的app是用intent filter來篩連接的accessories的話,
會自動接收權限(如果使用者有允許你的app來處理intent的話)
如果並不是如此的話,
你必須要在連接前,明確地在你的app裡取得授權。


明確地去詢問授權可能在某些狀況是必要的,
像是當你的app枚舉出所有已連結的accessories以後想要和一個溝通。
如果沒這麼做的話,
你會收到一個執行期錯誤,如果使用者拒絕授權通過的話。


要明確地去取得授權,首先要作一個廣播接收器,
接收器監聽那種當你呼叫requestPermission()時會廣播的intent。
呼叫requestPermission()會顯示一個對話框給使用者,
請求連接的授權。
下面的sample程式碼展示了如何做一個廣播接受器:



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


(除了code以外又完全一樣了是怎樣= =)
要註冊廣播接收器,將其放在onCreate() method(在你的activity裡):

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() method:

UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);

當使用者回應這個對話框時,
你的廣播接收器接收包含EXTRA_PERMISSION_GRANTED extra的intent,
(它是boolean值,代表回應的答案)
在連結之前,檢查這個extra的value是否為真。


(真的只有code有改= =+++++++++++++++!)

Communicating with a device
和USB裝置的溝通可以是同步或非同步地,
你應該要在另一個thread裡設定好裝置和accessory的溝通,
這樣你才不會鎖住主UI thread。

要好正確地跟裝置溝通的話,你需要從你想溝通通的裝置,
取得適當的UsbInterface以及UsbEndpoint
並且在這個終端上用UsbDeviceConnection傳送請求。
大體上你的code應該要:
1. 檢查UsbDevice物件的屬性,比如product ID, vendor ID,
或者裝置的class來弄清楚你到底想不想跟這個裝置溝通。
2. 當你確定你要跟裝置溝通時,
找到你想要和適當的UsbEndpoint的操作介面用來溝通的UsbInterface
介面可以有一或多個終端點,
而且通常會有一個input和output的終端點作為雙向溝通。
3. 當你找到正確的終端點時,在那個終端點上打開UsbDeviceConnection
4. 在那個終端點上,使用bulkTransfer()或者controlTransfer()
來支援你想傳送的資料。你應該把這步放在別的thread來做,
以避免擋到主程式的UI thread運行。

下面的code片段是一個trivial的方法來做同步資料的傳輸。
你的code應該要有更多邏輯來正確地找到正確的介面及終端點,
來進行溝通,且也應在一個不同的thread做任何資料傳輸,
而非在主程式。

private Byte[] bytesprivate 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的class來初始化並queue成同步的request,
然後使用requestWait()來等候結果。

要找尋更多的資訊,請看AdbTest sample
展示了如何去做不同步的bulk傳輸。
還有可以參考MissleLauncher sample
它展示了如何不同步地監聽一個中斷終端點。

Terminating communication with a device
當你做完和裝置地溝通,或者裝置被拔除,
透過呼叫releaseInterface()close()
關閉UsbInterface還有UsbDeviceConnection
要監聽拔除的events,
做一個廣播接收器如下:

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

在app裡作廣播接收器而不在manifest裡作,
讓你的app只會在執行時去處理移除連結的events。
這樣的話,移除連結的events只會被送到現在正在跑的app,
而不會廣播到所有的apps。

沒有留言:

張貼留言