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。

沒有留言:

張貼留言