Tổng quan về phụ kiện USB

Chế độ phụ kiện USB cho phép người dùng kết nối phần cứng hỗ trợ USB được thiết kế riêng cho các thiết bị chạy Android. Các phụ kiện phải tuân thủ giao thức phụ kiện Android được nêu trong tài liệu về Bộ phát triển phụ kiện Android. Điều này cho phép các thiết bị chạy Android không thể hoạt động như một thiết bị lưu trữ USB vẫn tương tác được với phần cứng USB. Khi một thiết bị chạy Android đang ở chế độ phụ kiện USB, phụ kiện USB Android đi kèm sẽ đóng vai trò là thiết bị lưu trữ, cung cấp nguồn cho cổng USB và liệt kê các thiết bị đã kết nối. Android 3.1 (API cấp 12) hỗ trợ chế độ phụ kiện USB và tính năng này cũng được điều chỉnh cho phiên bản cũ là Android 2.3.4 (API cấp 10) để cho phép hỗ trợ nhiều thiết bị hơn.

Chọn API phụ kiện USB phù hợp

Mặc dù API phụ kiện USB đã được giới thiệu vào nền tảng này trong Android 3.1, nhưng các API này cũng có trong Android 2.3.4 bằng cách sử dụng thư viện tiện ích bổ sung API của Google. Vì các API này được điều chỉnh cho phiên bản cũ bằng cách sử dụng thư viện bên ngoài, nên bạn có thể nhập 2 gói để hỗ trợ chế độ phụ kiện USB. Tuỳ thuộc vào loại thiết bị chạy Android mà bạn muốn hỗ trợ, bạn có thể phải sử dụng một trong hai thiết bị sau:

  • com.android.future.usb: Để hỗ trợ chế độ phụ kiện USB trong Android 2.3.4, thư viện tiện ích bổ sung API Google bao gồm các API phụ kiện USB được điều chỉnh cho phiên bản cũ và nằm trong không gian tên này. Android 3.1 cũng hỗ trợ tính năng nhập và gọi các lớp trong không gian tên này để hỗ trợ các ứng dụng được viết bằng thư viện tiện ích bổ sung. Thư viện tiện ích bổ sung này là một trình bao bọc mỏng xung quanh API phụ kiện android.hardware.usb và không hỗ trợ chế độ lưu trữ USB. Nếu bạn muốn hỗ trợ nhiều thiết bị nhất có hỗ trợ chế độ phụ kiện USB, hãy dùng thư viện tiện ích bổ sung và nhập gói này. Điều quan trọng cần lưu ý là không phải tất cả thiết bị Android 2.3.4 đều cần phải hỗ trợ tính năng phụ kiện USB. Mỗi nhà sản xuất thiết bị đều sẽ quyết định có hỗ trợ tính năng này hay không. Đó là lý do bạn phải khai báo tính năng này trong tệp kê khai.
  • android.hardware.usb: Không gian tên này chứa các lớp hỗ trợ chế độ phụ kiện USB trong Android 3.1. Gói này được đưa vào như một phần của các API khung, vì vậy, Android 3.1 hỗ trợ chế độ phụ kiện USB mà không cần sử dụng thư viện tiện ích bổ sung. Hãy sử dụng gói này nếu bạn chỉ quan tâm đến Android 3.1 trở lên có hỗ trợ phần cứng cho chế độ phụ kiện USB. Bạn có thể khai báo chế độ này trong tệp kê khai.

Cài đặt thư viện tiện ích bổ sung API của Google

Nếu muốn cài đặt tiện ích bổ sung này, bạn có thể cài đặt gói API Android 10 của Google API bằng Trình quản lý SDK. Xem phần Cài đặt tiện ích bổ sung API của Google để biết thêm thông tin về cách cài đặt thư viện tiện ích bổ sung.

Tổng quan về API

Vì thư viện tiện ích bổ sung là một trình bao bọc cho API khung, nên các lớp hỗ trợ tính năng phụ kiện USB cũng tương tự như vậy. Bạn có thể sử dụng tài liệu tham khảo cho android.hardware.usb ngay cả khi đang sử dụng thư viện tiện ích bổ sung.

Lưu ý: Tuy nhiên, có một sự khác biệt nhỏ về việc sử dụng giữa thư viện tiện ích bổ sung và các API khung mà bạn nên biết.

Bảng sau đây mô tả các lớp hỗ trợ API phụ kiện USB:

Lớp Nội dung mô tả
UsbManager Cho phép bạn liệt kê và giao tiếp với các phụ kiện USB đã kết nối.
UsbAccessory Đại diện cho một phụ kiện USB và chứa các phương thức để truy cập vào thông tin nhận dạng của phụ kiện đó.

Sự khác biệt về cách sử dụng giữa API nền tảng và thư viện tiện ích bổ sung

Có hai sự khác biệt về cách sử dụng khi sử dụng thư viện tiện ích bổ sung API Google và API nền tảng.

Nếu đang sử dụng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbManager theo cách sau:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Nếu không sử dụng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbManager theo cách sau:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

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

Khi bạn lọc tìm một phụ kiện đã kết nối bằng bộ lọc ý định, đối tượng UsbAccessory sẽ nằm trong ý định được truyền đến ứng dụng. Nếu đang sử dụng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbAccessory theo cách sau:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Nếu không sử dụng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbAccessory theo cách sau:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

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

Yêu cầu về tệp kê khai Android

Danh sách sau đây mô tả những nội dung bạn cần thêm vào tệp kê khai của ứng dụng trước khi làm việc với các API phụ kiện USB. Ví dụ về tệp kê khai và tệp tài nguyên cho biết cách khai báo các mục này:

  • Không phải thiết bị chạy Android nào cũng hỗ trợ API phụ kiện USB, hãy thêm phần tử <uses-feature> khai báo rằng ứng dụng của bạn sử dụng tính năng android.hardware.usb.accessory.
  • Nếu bạn đang sử dụng thư viện tiện ích bổ sung, hãy thêm phần tử <uses-library> chỉ định com.android.future.usb.accessory cho thư viện.
  • Đặt SDK tối thiểu của ứng dụng thành API cấp 10 nếu bạn đang sử dụng thư viện tiện ích bổ sung hoặc cấp 12 nếu bạn đang sử dụng gói android.hardware.usb.
  • Nếu bạn muốn ứng dụng nhận được thông báo về một phụ kiện USB đi kèm, hãy chỉ định một cặp phần tử <intent-filter><meta-data> cho ý định android.hardware.usb.action.USB_ACCESSORY_ATTACHED trong hoạt động chính. Phần tử <meta-data> trỏ đến một tệp tài nguyên XML bên ngoài khai báo thông tin nhận dạng về phụ kiện mà bạn muốn phát hiện.

    Trong tệp tài nguyên XML, hãy khai báo phần tử <usb-accessory> cho các phụ kiện mà bạn muốn lọc. Mỗi <usb-accessory> có thể có các thuộc tính sau:

    • manufacturer
    • model
    • version

    Bạn không nên lọc trên version. Không phải lúc nào phụ kiện hoặc thiết bị cũng có thể chỉ định chuỗi phiên bản (cố ý hoặc vô tình). Khi ứng dụng của bạn khai báo một thuộc tính phiên bản để lọc và phụ kiện hoặc thiết bị không chỉ định chuỗi phiên bản, thì điều này sẽ dẫn đến NullPointerException trên các phiên bản Android cũ. Vấn đề này được khắc phục trong Android 12.

    Lưu tệp tài nguyên trong thư mục res/xml/. Tên tệp tài nguyên (không có đuôi .xml) phải giống với tên tệp bạn đã chỉ định trong phần tử <meta-data>. Định dạng của tệp tài nguyên XML cũng được thể hiện trong ví dụ dưới đây.

Ví dụ về tệp kê khai và tệp tài nguyên

Ví dụ sau đây cho thấy một tệp kê khai mẫu và tệp tài nguyên tương ứng:

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

Trong trường hợp này, tệp tài nguyên sau sẽ được lưu trong res/xml/accessory_filter.xml và chỉ định rằng mọi phụ kiện có kiểu máy, nhà sản xuất và phiên bản tương ứng đều phải được lọc. Phụ kiện sẽ gửi các thuộc tính sau đây cho thiết bị chạy Android:

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

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

Làm việc với phụ kiện

Khi người dùng kết nối phụ kiện USB với thiết bị chạy Android, hệ thống Android có thể xác định xem ứng dụng của bạn có quan tâm đến phụ kiện được kết nối hay không. Nếu muốn, bạn có thể thiết lập hoạt động giao tiếp với phụ kiện đó nếu muốn. Để làm được điều này, ứng dụng của bạn phải:

  1. Hãy khám phá các phụ kiện đã kết nối bằng cách sử dụng bộ lọc ý định có chức năng lọc các sự kiện đi kèm với phụ kiện hoặc liệt kê các phụ kiện đã kết nối và tìm phụ kiện thích hợp.
  2. Hãy yêu cầu người dùng cấp quyền giao tiếp với phụ kiện nếu chưa nhận được.
  3. Giao tiếp với phụ kiện bằng cách đọc và ghi dữ liệu trên các điểm cuối giao diện thích hợp.

Khám phá phụ kiện

Ứng dụng của bạn có thể khám phá các phụ kiện bằng cách sử dụng bộ lọc ý định để nhận thông báo khi người dùng kết nối với phụ kiện hoặc bằng cách liệt kê các phụ kiện đã kết nối. Bộ lọc ý định sẽ hữu ích nếu bạn muốn ứng dụng của mình tự động phát hiện phụ kiện mong muốn. Việc liệt kê các phụ kiện đã kết nối sẽ hữu ích nếu bạn muốn xem danh sách tất cả các phụ kiện đã kết nối hoặc nếu ứng dụng của bạn không lọc ra một ý định.

Sử dụng bộ lọc ý định

Để cho phép ứng dụng khám phá một phụ kiện USB cụ thể, bạn có thể chỉ định một bộ lọc ý định để lọc ý định android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Cùng với bộ lọc ý định này, bạn cần chỉ định một tệp tài nguyên chỉ định các thuộc tính của phụ kiện USB, chẳng hạn như nhà sản xuất, kiểu máy và phiên bản.

Ví dụ sau đây cho thấy cách khai báo bộ lọc ý định:

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

Ví dụ sau cho biết cách khai báo tệp tài nguyên tương ứng chỉ định các phụ kiện USB mà bạn quan tâm:

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

<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

Trong hoạt động của mình, bạn có thể lấy UsbAccessory đại diện cho phụ kiện đính kèm qua ý định như sau (với thư viện tiện ích bổ sung):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

hoặc như thế này (với API nền tảng):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

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

Liệt kê phụ kiện

Bạn có thể yêu cầu ứng dụng liệt kê các phụ kiện đã tự xác định trong khi ứng dụng đang chạy.

Sử dụng phương thức getAccessoryList() để lấy một mảng tất cả các phụ kiện USB đã kết nối:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList

Java

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

Lưu ý: Mỗi lần chỉ hỗ trợ một phụ kiện đã kết nối.

Yêu cầu cấp quyền để giao tiếp với một phụ kiện

Trước khi giao tiếp với phụ kiện USB, ứng dụng của bạn phải được người dùng cho phép.

Lưu ý: Nếu sử dụng bộ lọc ý định để khám phá các phụ kiện khi chúng được kết nối, ứng dụng sẽ tự động nhận được quyền nếu người dùng cho phép ứng dụng của bạn xử lý ý định. Nếu không, bạn phải yêu cầu cấp quyền rõ ràng trong ứng dụng trước khi kết nối với phụ kiện.

Bạn có thể cần phải yêu cầu cấp quyền rõ ràng trong một số trường hợp, chẳng hạn như khi ứng dụng của bạn liệt kê các phụ kiện đã được kết nối và sau đó muốn giao tiếp với một phụ kiện đó. Bạn phải kiểm tra quyền sử dụng phụ kiện trước khi cố gắng kết nối với phụ kiện đó. Nếu không, bạn sẽ gặp lỗi thời gian chạy nếu người dùng từ chối cấp quyền truy cập vào phụ kiện.

Để xin phép một cách rõ ràng, trước tiên, hãy tạo một broadcast receiver. receiver này theo dõi ý định sẽ được truyền tin khi bạn gọi requestPermission(). Lệnh gọi đến requestPermission() sẽ hiển thị hộp thoại cho người dùng để yêu cầu cấp quyền kết nối với phụ kiện. Mã mẫu sau đây trình bày cách tạo broadcast receiver:

Kotlin

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"

private val usbReceiver = object : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (ACTION_USB_PERMISSION == intent.action) {
            synchronized(this) {
                val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    accessory?.apply {
                        // call method to set up accessory communication
                    }
                } else {
                    Log.d(TAG, "permission denied for accessory $accessory")
                }
            }
        }
    }
}

Java

private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
private final BroadcastReceiver usbReceiver = 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);
                }
            }
        }
    }
};

Để đăng ký broadcast receiver, hãy đặt nội dung này vào phương thức onCreate() trong hoạt động của bạn:

Kotlin

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
...
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
...
permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
val filter = IntentFilter(ACTION_USB_PERMISSION)
registerReceiver(usbReceiver, filter)

Java

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

Để hiện hộp thoại yêu cầu người dùng cấp quyền kết nối với phụ kiện, hãy gọi phương thức requestPermission():

Kotlin

lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);

Khi người dùng trả lời hộp thoại, broadcast receiver của bạn sẽ nhận được ý định chứa EXTRA_PERMISSION_GRANTED bổ sung, là một boolean đại diện cho câu trả lời. Hãy kiểm tra thêm giá trị này để biết giá trị true trước khi kết nối với phụ kiện.

Kết nối với một phụ kiện

Bạn có thể giao tiếp với phụ kiện bằng cách sử dụng UsbManager để lấy chỉ số mô tả tệp cho phép thiết lập luồng đầu vào và đầu ra nhằm đọc và ghi dữ liệu vào chỉ số mô tả. Các luồng này đại diện cho các điểm cuối hàng loạt đầu vào và đầu ra của phụ kiện. Bạn nên thiết lập hoạt động giao tiếp giữa thiết bị và phụ kiện trong một luồng khác để không khoá luồng giao diện người dùng chính. Ví dụ sau đây cho biết cách mở một phụ kiện để giao tiếp với:

Kotlin

private lateinit var accessory: UsbAccessory
private var fileDescriptor: ParcelFileDescriptor? = null
private var inputStream: FileInputStream? = null
private var outputStream: FileOutputStream? = null
...

private fun openAccessory() {
    Log.d(TAG, "openAccessory: $mAccessory")
    fileDescriptor = usbManager.openAccessory(accessory)
    fileDescriptor?.fileDescriptor?.also { fd ->
        inputStream = FileInputStream(fd)
        outputStream = FileOutputStream(fd)
        val thread = Thread(null, this, "AccessoryThread")
        thread.start()
    }
}

Java

UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
...

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    fileDescriptor = usbManager.openAccessory(accessory);
    if (fileDescriptor != null) {
        FileDescriptor fd = fileDescriptor.getFileDescriptor();
        inputStream = new FileInputStream(fd);
        outputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

Trong phương thức run() của luồng, bạn có thể đọc và ghi vào phụ kiện bằng cách sử dụng đối tượng FileInputStream hoặc FileOutputStream. Khi đọc dữ liệu từ một phụ kiện có đối tượng FileInputStream, hãy đảm bảo rằng vùng đệm mà bạn sử dụng đủ lớn để lưu trữ dữ liệu gói USB. Giao thức phụ kiện Android hỗ trợ vùng đệm gói lên đến 16384 byte. Vì vậy, để đơn giản, bạn có thể chọn luôn khai báo vùng đệm của mình có kích thước này.

Lưu ý: Ở cấp độ thấp hơn, các gói là 64 byte đối với phụ kiện USB tốc độ cao và 512 byte đối với phụ kiện USB tốc độ cao. Giao thức phụ kiện Android sẽ nhóm các gói lại với nhau ở cả hai tốc độ thành một gói logic để đơn giản hoá.

Để biết thêm thông tin về cách sử dụng luồng trong Android, hãy xem Quy trình và luồng.

Chấm dứt kết nối với một phụ kiện

Khi bạn đã giao tiếp với một phụ kiện hoặc nếu phụ kiện đó đã bị tháo rời, hãy đóng chỉ số mô tả tệp mà bạn đã mở bằng cách gọi close(). Để theo dõi các sự kiện đã tách ra, hãy tạo một broadcast receiver như bên dưới:

Kotlin

var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED == intent.action) {
            val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
            accessory?.apply {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
}

Java

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

Việc tạo broadcast receiver trong ứng dụng chứ không phải tệp kê khai cho phép ứng dụng của bạn chỉ xử lý các sự kiện đã tách ra khi ứng dụng đang chạy. Bằng cách này, các sự kiện được tách ra chỉ được gửi đến ứng dụng hiện đang chạy chứ không được truyền đến tất cả các ứng dụng.