USB 액세서리 개요

USB 액세서리 모드를 통해 사용자는 Android 지원 기기용으로 특별히 설계된 USB 호스트 하드웨어를 연결할 수 있습니다. 액세서리는 Android 액세서리 개발 키트 문서에 개략적으로 설명된 Android 액세서리 프로토콜을 준수해야 합니다. 프로토콜 준수를 통해 USB 호스트로 작동할 수 없는 Android 지원 기기가 여전히 USB 하드웨어와 상호작용할 수 있습니다. Android 지원 기기가 USB 액세서리 모드일 때, 연결된 Android USB 액세서리는 호스트 역할을 하며 USB 버스에 전원을 공급하고 연결된 기기를 열거합니다. Android 3.1(API 레벨 12)은 USB 액세서리 모드를 지원하며 이 기능은 Android 2.3.4(API 레벨 10)로 백포팅되어 광범위한 기기를 지원할 수 있습니다.

적절한 USB 액세서리 API 선택

USB 액세서리 API는 Android 3.1의 플랫폼에 도입되었지만 Google API 부가기능 라이브러리를 사용하여 Android 2.3.4에서도 사용할 수 있습니다. 이러한 API는 외부 라이브러리를 사용하여 백포팅되었으므로 개발자가 USB 액세서리 모드를 지원하기 위해 가져올 수 있는 패키지가 두 가지 있습니다. 지원하려는 Android 기반 기기에 따라 다음 중 하나를 사용해야 할 수 있습니다.

  • com.android.future.usb: Android 2.3.4의 USB 액세서리 모드를 지원하기 위해 Google API 부가기능 라이브러리에는 백포팅된 USB 액세서리 API가 포함되어 있으며 이러한 API는 이 네임스페이스에 있습니다. 또한 Android 3.1도 이 네임스페이스 내에서 클래스 가져오기 및 호출을 지원하여 부가기능 라이브러리로 작성된 애플리케이션을 지원합니다. 이 부가기능 라이브러리는 android.hardware.usb 액세서리 API 주변의 얇은 래퍼이며 USB 호스트 모드를 지원하지 않습니다. USB 액세서리 모드를 지원하는 기기를 가장 광범위하게 지원하려면 부가기능 라이브러리를 사용하여 이 패키지를 가져오세요. 모든 Android 2.3.4 기기가 USB 액세서리 기능을 지원해야 하는 것은 아니라는 점에 유의하시기 바랍니다. 각각의 개별 기기 제조업체가 이 기능을 지원할지 여부를 결정합니다. 이러한 이유로 개발자는 manifest 파일에서 이 기능을 선언해야 합니다.
  • android.hardware.usb: 이 네임스페이스에는 Android 3.1의 USB 액세서리 모드를 지원하는 클래스가 포함되어 있습니다. 이 패키지는 프레임워크 API의 일부로 포함되므로 Android 3.1에서는 부가기능 라이브러리를 사용하지 않고도 USB 액세서리 모드를 지원합니다. manifest 파일에서 선언할 수 있는 USB 액세서리 모드를 지원하는 하드웨어가 있는 Android 3.1 이상 기기에만 관심이 있다면 이 패키지를 사용하세요.

Google API 부가기능 라이브러리 설치

부가기능을 설치하려면 SDK Manager를 사용하여 Google API Android API 10 패키지를 설치하면 됩니다. 부가기능 라이브러리 설치에 관한 자세한 내용은 Google API 부가기능 설치를 참조하세요.

API 개요

부가기능 라이브러리는 프레임워크 API의 래퍼이므로 USB 액세서리 기능을 지원하는 클래스가 비슷합니다. 부가기능 라이브러리를 사용하더라도 android.hardware.usb의 참조 문서를 사용할 수 있습니다.

참고: 그러나 부가기능 라이브러리와 프레임워크 API 사이에는 약간의 사용법 차이가 있는데 개발자는 이러한 차이를 알고 있어야 합니다.

다음 표에서는 USB 액세서리 API를 지원하는 클래스를 설명합니다.

클래스 설명
UsbManager 이 클래스를 사용하면 연결된 USB 액세서리를 열거하고 액세서리와 통신할 수 있습니다.
UsbAccessory 이 클래스는 USB 액세서리를 나타내며 식별 정보에 액세스하는 메서드를 포함하고 있습니다.

부가기능 라이브러리와 플랫폼 API의 사용법 차이

Google API 부가기능 라이브러리와 플랫폼 API 사용법에는 두 가지 차이점이 있습니다.

부가기능 라이브러리를 사용한다면 다음과 같은 방식으로 UsbManager 객체를 얻어야 합니다.

Kotlin

    val manager = UsbManager.getInstance(this)
    

자바

    UsbManager manager = UsbManager.getInstance(this);
    

부가기능 라이브러리를 사용하지 않는다면 다음과 같은 방식으로 UsbManager 객체를 얻어야 합니다.

Kotlin

    val manager = getSystemService(Context.USB_SERVICE) as UsbManager
    

자바

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

인텐트 필터를 사용하여 연결된 액세서리를 필터링할 때 UsbAccessory 객체는 애플리케이션에 전달되는 인텐트 내부에 포함됩니다. 부가기능 라이브러리를 사용한다면 다음과 같은 방식으로 UsbAccessory 객체를 얻어야 합니다.

Kotlin

    val accessory = UsbManager.getAccessory(intent)
    

자바

    UsbAccessory accessory = UsbManager.getAccessory(intent);
    

부가기능 라이브러리를 사용하지 않는다면 다음과 같은 방식으로 UsbAccessory 객체를 얻어야 합니다.

Kotlin

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

자바

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

Android manifest 요구사항

다음 목록에서는 USB 액세서리 API로 작업하기 전에 애플리케이션의 manifest 파일에 추가해야 하는 항목을 설명합니다. manifest 및 리소스 파일 예는 이러한 항목을 선언하는 방법을 보여줍니다.

  • 모든 Android 지원 기기가 USB 액세서리 API를 지원한다는 보장이 없으므로 애플리케이션에서 android.hardware.usb.accessory 기능을 사용한다는 것을 선언하는 <uses-feature> 요소를 포함해야 합니다.
  • 부가기능 라이브러리를 사용한다면 라이브러리의 com.android.future.usb.accessory를 지정하는 <uses-library> 요소를 추가해야 합니다.
  • 애플리케이션의 최소 SDK를 부가기능 라이브러리 사용 시에는 API 레벨 10으로 설정하고 android.hardware.usb 패키지 사용 시에는 API 레벨 12로 설정해야 합니다.
  • 애플리케이션에서 연결된 USB 액세서리에 관한 알림을 받도록 하려면 기본 활동에서 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 인텐트의 <intent-filter><meta-data> 요소 쌍을 지정합니다. <meta-data> 요소는 감지하려는 액세서리에 관한 식별 정보를 선언하는 외부 XML 리소스 파일을 가리킵니다.

    XML 리소스 파일에서 필터링하려는 액세서리의 <usb-accessory> 요소를 선언해야 합니다. 각 <usb-accessory>에는 다음과 같은 속성이 있을 수 있습니다.

    • manufacturer
    • model
    • version

    리소스 파일을 res/xml/ 디렉터리에 저장해야 합니다. 리소스 파일 이름(.xml 확장자 제외)은 <meta-data> 요소에서 지정한 이름과 동일해야 합니다. XML 리소스 파일의 형식은 아래 에 나와 있습니다.

manifest 및 리소스 파일 예

다음 예는 샘플 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>
    

여기서는 다음 리소스 파일을 res/xml/accessory_filter.xml에 저장해야 합니다. 이 파일에서는 원하는 모델, 제조업체 및 버전이 있는 액세서리를 필터링하도록 지정합니다. 액세서리는 Android 지원 기기에 이러한 속성을 전달합니다.

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

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

액세서리 사용

사용자가 USB 액세서리를 Android 지원 기기에 연결할 때 Android 시스템은 애플리케이션이 연결된 액세서리에 관심을 보이는지 확인할 수 있습니다. 관심을 보이는 경우 필요하다면 액세서리와의 통신을 설정할 수 있습니다. 통신을 설정하려면 애플리케이션에서 다음과 같이 해야 합니다.

  1. 액세서리 연결 이벤트를 필터링하는 인텐트 필터를 사용하거나 연결된 액세서리를 열거하고 적절한 액세서리를 검색하여 연결된 액세서리를 찾습니다.
  2. 액세서리와 통신할 수 있는 권한이 아직 없다면 권한을 사용자에게 요청합니다.
  3. 적절한 인터페이스 엔드포인트에서 데이터를 읽고 쓰는 방식으로 액세서리와 통신합니다.

액세서리 찾기

애플리케이션은 사용자가 액세서리를 연결할 때 알림을 받도록 인텐트 필터를 사용하거나 이미 연결된 액세서리를 열거하여 액세서리를 찾을 수 있습니다. 애플리케이션에서 원하는 액세서리를 자동으로 감지할 수 있게 하려면 인텐트 필터를 사용하는 방법이 유용합니다. 연결된 모든 액세서리 목록을 확보하려면 또는 애플리케이션이 인텐트를 필터링하지 않는다면 연결된 액세서리를 열거하는 방법이 유용합니다.

인텐트 필터 사용

애플리케이션에서 특정 USB 액세서리를 찾도록 하려면 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 인텐트를 필터링하는 인텐트 필터를 지정하면 됩니다. 이 인텐트 필터와 함께 제조업체, 모델 및 버전과 같은 USB 액세서리의 속성을 명시하는 리소스 파일을 지정해야 합니다. 사용자가 액세서리 필터와 일치하는 액세서리를 연결할 때

다음 예는 인텐트 필터를 선언하는 방법을 보여줍니다.

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

다음 예는 상응하는 리소스 파일을 선언하는 방법을 보여줍니다. 이 파일은 관심 있는 USB 액세서리를 지정합니다.

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

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

다음과 같이 활동의 인텐트에서 연결된 액세서리를 나타내는 UsbAccessory를 얻을 수 있습니다(부가기능 라이브러리 사용 시).

Kotlin

    val accessory = UsbManager.getAccessory(intent)
    

자바

    UsbAccessory accessory = UsbManager.getAccessory(intent);
    

또는 다음과 같이 얻을 수 있습니다(플랫폼 API 사용 시).

Kotlin

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

자바

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

액세서리 열거

애플리케이션이 실행되는 동안 자신을 식별한 액세서리를 열거하도록 할 수 있습니다.

getAccessoryList() 메서드를 사용하면 다음과 같이 연결된 모든 USB 액세서리를 배열로 가져올 수 있습니다.

Kotlin

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

자바

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

참고: 연결된 액세서리는 한 번에 하나만 지원됩니다.

액세서리와 통신할 수 있는 권한 얻기

USB 액세서리와 통신하려면 애플리케이션에서 사용자로부터 권한을 받아야 합니다.

참고: 애플리케이션이 인텐트 필터를 사용하여 연결된 액세서리를 검색하는 경우, 사용자가 애플리케이션에서 인텐트를 처리하도록 허용하면 애플리케이션이 자동으로 권한을 받습니다. 권한을 자동으로 받지 못하면 액세서리에 연결하기 전에 애플리케이션에서 명시적으로 권한을 요청해야 합니다.

애플리케이션이 이미 연결된 액세서리를 열거한 후 액세서리와 통신하려는 때와 같은 일부 상황에서는 명시적으로 권한을 요청해야 할 수 있습니다. 액세서리와 통신하려고 하기 전에 액세서리에 액세스할 수 있는 권한이 있는지 확인해야 합니다. 권한을 확인하지 않고 통신을 시도했는데 사용자가 액세서리 액세스 권한을 거부하면 런타임 오류가 발생합니다.

명시적으로 권한을 얻으려면 먼저 broadcast receiver를 생성하세요. receiver는 requestPermission()이 호출될 때 브로드캐스트를 받는 인텐트를 수신 대기합니다. requestPermission()이 호출되면 사용자에게 액세서리 연결 권한을 요청하는 대화상자가 표시됩니다. 다음 샘플 코드는 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")
                    }
                }
            }
        }
    }
    

자바

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

broadcast receiver를 등록하려면 활동의 onCreate() 메서드에 다음 코드를 추가하세요.

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)
    

자바

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

사용자에게 액세서리 연결 권한을 요청하는 대화상자를 표시하려면 다음과 같이 requestPermission() 메서드를 호출하세요.

Kotlin

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

자바

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

사용자가 대화상자에 응답할 때 broadcast receiver는 EXTRA_PERMISSION_GRANTED 엑스트라가 포함된 인텐트를 수신하는데 이 엑스트라는 응답을 나타내는 부울입니다. 액세서리에 연결하기 전에 이 엑스트라가 true 값인지 확인해야 합니다.

액세서리와 통신

UsbManager를 사용함으로써 액세서리와 통신하여 데이터를 설명어에 읽고 쓸 수 있도록 입력 및 출력 스트림을 설정할 수 있는 파일 설명어를 얻을 수 있습니다. 스트림은 액세서리의 입력 및 출력 일괄 엔드포인트를 나타냅니다. 다른 스레드에서 기기와 액세서리 간 통신을 설정해야 하므로 기본 UI 스레드를 잠그지 않아야 합니다. 다음 예에서는 통신할 액세서리를 여는 방법을 보여줍니다.

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

자바

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

스레드의 run() 메서드에서 FileInputStream 또는 FileOutputStream 객체를 사용하여 액세서리에 읽고 쓸 수 있습니다. FileInputStream 객체를 사용하여 액세서리에서 데이터를 읽을 때는 사용하는 버퍼가 USB 패킷 데이터를 저장하기에 충분히 큰지 확인해야 합니다. Android 액세서리 프로토콜은 최대 16,384바이트의 패킷 버퍼를 지원하므로 편의를 위해 버퍼가 항상 이 크기가 되도록 선언할 수도 있습니다.

참고: 하위 레벨에서 최고 속도 USB 액세서리의 패킷은 64바이트이며 고속 USB 액세서리의 패킷은 512바이트입니다. Android 액세서리 프로토콜은 편의를 위해 두 속도의 패킷을 함께 하나의 논리 패킷으로 묶어 번들로 제공합니다.

Android에서 스레드 사용에 관한 자세한 내용은 프로세스 및 스레드를 참조하세요.

액세서리와의 통신 종료

액세서리와의 통신이 완료되었거나 액세서리가 분리되었다면 close()를 호출하여 열었던 파일 설명어를 닫습니다. 분리 이벤트를 수신 대기하려면 다음과 같이 broadcast receiver를 생성합니다.

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

자바

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

manifest가 아닌 애플리케이션 내에 broadcast receiver를 생성하면 애플리케이션이 실행되는 동안 분리 이벤트만 처리하도록 할 수 있습니다. 이렇게 하면 분리 이벤트가 현재 실행 중인 애플리케이션으로만 전달되고 모든 애플리케이션으로 브로드캐스트되지는 않습니다.