2013年3月22日 星期五

[Android] Android Developer Note Connectivity 6

USB Accessory (見Android Developer USB Accessory)
USB accessory模式允許使用者去連接USB host指定被設計給Android裝置用的硬體。
這些accessories必須要依附由Android Accessory Development Kit文件
所概述的Android accessory protocol。
這允許了那些不能作為USB host的Android裝置仍舊能夠和USB硬體來互動。
當一個Android裝置是在USB accessory模式時,
被連上的Android USB accessory表現得像host,
提供電力給USB匯流排(bus),並且枚舉連接的裝置。
Android 3.1(API level 12)支援USB accessory模式,
且這功能也回到Android 2.3.4(API level 10)來支援以讓更多裝置能使用。

Choosing the Right USB Accessory APIs
儘管USB accessory APIs在Android 3.1被引入,
使用Google APIs add-on library後也可以在2.3.4被支援。
因為這些APIs被使用一個外加的library給backported,
所以你可以藉由import兩種packages之一來支援USB accessory模式。
端看你要支援什麼樣的Android裝置,你也許會用一個而不用另一個。

1. com.android.future.usb:
為了在Android 2.3.4支援USB accessory模式,
Google APIs add-on library包含了backported USB accessory APIs,
且他們被含在這個namespace裡。
Android 3.1也支援在這個namespace裡引入並呼叫些classes,
以支援使用add-on library來撰寫的app。
這個add-on library是一個小的wrapper,
包起了 android.hardware.usb accessory APIs,
但並不支援USB host模式。
如果你想要支援最多最廣使用accessory模式的Android裝置,
使用add-on library並且將此package引入(import)。
注意不是所有Android 2.3.4裝置都有被要求支援USB accessory模式,
這很重要XD。
所有獨立的裝置開發商決定要不要支援這個功能,
所以這就是為什麼你必須要在manifest檔裡聲明這件事情。

2. android.hardware.usb:
這個namespace包含了支援Android 3.1下的USB accessory模式的classes。
這個package裡包含一部分的framwork APIs,故Android 3.1支援USB accessory模式,
而不需使用add-on library。使用這個package,
如果你只關心Android 3.1以上硬體支援accessory模式的裝置。
(你可以在manifest檔裡聲明這點)

Installing the Google APIs add-on library
如果你要安裝add-on,你可以透過SDK Manager,
安裝Google APIs Android API 10的package。
可以看Installing the Google APIs Add-on來找尋更多安裝相關資訊。

API Overview:
因為add-on library是framwork APIs的包裹器(wrapper),
支援USB accessory功能的classes長的很像。
你可以為android.hardware.usb使用reference文件,
即便你正在使用add-on library。
注意:
儘管如此,你必須注意這兩者的還是有微小使用差異(usage difference)的。
下面列表描述了支援USB accessory APIs的classes:
ClassDescription
UsbManagerAllows you to enumerate and communicate with connected USB accessories.
UsbAccessoryRepresents a USB accessory and contains methods to access its identifying information.

Usage differences between the add-on library and platform APIs:
在兩者之間有兩個使用上的不同。
如果用add-on library,你必須要以下面的方式取得UsbManager物件:

UsbManager manager = UsbManager.getInstance(this);

如果你不是在使用add-on library,那麼要用這種方式:

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

當你在用intent filter篩一個連線的accessory時,
UsbAccessory物件被包含在要傳給你的app的intent裡。
如果你用的是add-on library,你必須以下面的方式來獲取UsbAccessory物件:

UsbAccessory accessory = UsbManager.getAccessory(intent);

如果你不是在使用add-on library,
那麼要用這種方式:

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Android Manifest requirements
下面的列表描述了你所需加到app的manifest檔的東西。
(在使用USB accessory APIs前)
在接下來會展示如何聲明這些項目:
1. 因為不是所有Android裝置都能保證支援USB accessory APIs,
故加入<uses-feature>元素,
聲明你的app使用android.hardware.usb.accessory功能。
2. 如果你在使用add-on library的話,加入<uses-library>元素,
來指定com.android.future.usb.accessory給library。
3. 設最小的SDK版本到API Level 10(使用add-on library時),
或者用12(如果是使用android.hardware.usb package時)。
4. 如果你想要你的app能夠在連接上一個USB accessory時被提醒的話,
指名一個 <intent-filter>以及<meta-data>元素的配對(pair),
android.hardware.usb.action.USB_ACCESSORY_ATTACHED intent(在你的主activity)。
 <meta-data>元素指向一個外部的XML資源檔,它聲明了辨認你想偵測的accessory。
在XML資源檔裡,聲明<usb-accessory>元素給那些你想篩的accessories。
每一個<usb-accessory>裡可以擁有下面的屬性:
1. manufacturer
2. model
3. version
將資源檔存到 res/xml/目錄裡。
資源檔的名字(不算.xml的外加)必須要和你在 <meta-data> 元素裡指名的相同。
XML資源檔格式在下面的範例。

Manifest and resource file examples
下面是sample的manifest以及對應的資源檔:

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

在這個case來說,下面的資源檔必須要存在res/xml/accessory_filter.xml
並且指名任何有相對應的model,manufacturer以及版本都要被篩。
accessory傳送這些屬性給Android裝置:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>


Wokring with Accessories
當使用者連接USB accessories到Android裝置時,
Android系統可以決定你的app是否對其感到有興趣XDDD
如果這樣的話,你可以設定和accessory溝通(如果你想的話)
要達到這點,你的app必須要:
1. 使用intent filter去篩accessory連結的events,
或由枚舉連接的accessories並找到適當的一個,
來探索那些連結的accessories。
2. 詢問使用者授權和accessory溝通的許可,如果並沒有先被取得的話。
3. 透過在適合的interface終端(endpoints)讀寫和accessory溝通。

Discovering an accessory
你的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的資源檔,
像是manufacturer,model,及version。
當使用者連接一個符合你的accessory filter的accessory,
下面的範例展示如何聲明intent filter:

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

下面的範例展示,
如何去聲明那個指名你感興趣的accessories的相對應的資源檔:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

在你的activity哩,你可以獲得取intent分別透過兩個方式。
如果是add-on library要這麼做:

UsbAccessory accessory = UsbManager.getAccessory(intent);

如果是平台APIs:

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Enumerating accessories:
你可以在app執行中的時候,
枚舉那些已經自己給出識別過的accessories。
使用getAccessoryList() method來取得一個裝所有連線的USB accessories的陣列:

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAcccessoryList();

注意:
目前只有一次只有一個連接的accessory被支援,
但API在將來是被設計成支援多重accessories的。

Obtaining permission to communicate with an accessory
在和USB accessory溝通前,你的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) {
                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() 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:

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

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

Communicating with an accessory
你可以透過使用UsbManager來獲得file descriptor和accessory溝通。
(透過從input和output串流(stream)讀寫資料到descriptor)
串流代表了accessory的輸入輸出大批終端點(bulk endpoints)。
你應該要在另一個thread裡設定好裝置和accessory的溝通,
這樣你才不會鎖住主UI thread。
下面的範例展示了如何去開一個accessory來溝通:

UsbAccessory mAccessory;
ParcelFileDescriptor mFileDescriptor;
FileInputStream mInputStream;
FileOutputStream mOutputStream;
...
private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    mFileDescriptor = mUsbManager.openAccessory(mAccessory);
    if (mFileDescriptor != null) {
        FileDescriptor fd = mFileDescriptor.getFileDescriptor();
        mInputStream = new FileInputStream(fd);
        mOutputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

在thread的run() method裡,
你可以藉由FileInputStreamFileOutputStream物件讀寫到accessory。
當從accessory使用FileInputStream物件來讀資料時,
確保使用的buffer要夠大來儲存USB封包資料(packet data)。
Android accessory protocol支援了packet buffer上限到16384 bytes,
所以你可以選擇去總是聲明你的buffer到這個size這樣比較簡便。
注意:
當在較低的level下,
packets是:
64 bytes->USB full-speed accessories,
512 bytes->USB high-speed accessories。
Android accessory protocol會將兩種速度的packets打包在一起,
拿給一個logical packet以求簡便。

Terminating communication with an accessory
當你完成和accessory的溝通,或者accessory被移除連接(detached),
使用close()來關掉你開啟來的file descriptor。
要監聽移除連結的events,作一個廣播接收器比如像下面這樣:

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

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

沒有留言:

張貼留言