관리 메뉴

드럼치는 프로그래머

[안드로이드] Android USB Accessory 본문

★─Programing/☆─Android

[안드로이드] Android USB Accessory

드럼치는한동이 2013. 5. 6. 08:44

(원본 : http://developer.android.com/guide/topics/usb/accessory.html)

 

USB 액세서리 모드는 사용자가 USB host 하드웨어에 연결할 수 있도록 안드로이드 장비를 위해 설계되어졌다. 액세서리들은 Android Accessory Development Kit 문서에 설명되어져 있는 Android accessory protocol 을 준수해야 하며 USB host로 동작하지 않는 안드로이드 장비가 USB 하드웨어와 함께 동작하는 것을 지원한다. 안드로이드 장비가 USB 액세서리 모드로 동작할 때 장착된 안드로이드 USB 액세서리는 USB 버스를 통해 전력을 제공하는 호스트로서 동작하고 연결된 장비들을 열거한다. 안드로이드 3.1 (API level 12)는 USB 액세서리 모드를 지원하고 안드로이드 2.3.4 (API level 10)은 라이브러리를 추가함으로 장비에 대해 광범위하게 지원될 수 있도록 한다.

APIs 선택

USB 액세서리 API는 안드로이드 3.1에서 소개되었고 안드로이드 2.3.4에서는 Google APIs add-on library를 통해 사용할 수 있다. 이 API는 외부 라이브러리로서 제공되므로 액세서리 모드를 지원하기 위해서는 2개의 package를 import 할 수 있다. 이 것은 안드로이드 장비의 지원에 따라 선택적으로 import 한다.

 

Android USB Accessory 패키지
package android version 설명

com.android.future.usb

Android 2.3.4 진저브레이드 (Google APIs add-on library)
Android 3.1 이상

안드로이드 3.1에서도 이 패키지로 구현된 앱을 지원하기 위해 android.hardware.usb 패키지를 wrapper 하여 지원한다. 만약 폭넓게 지원하는 앱을 만들고 싶다면 이 패키지를 사용하여 구현하는 것이 좋다. 그러나 한가지 염두해둬야 하는 것은 모든 안드로이드 2.3.4의 장비가 이 패키지를 지원하는 것은 아니라는 것이다.이것의 지원 여부는 전적으로 제조사의 결정에 의하며 개발자는 매니페스트의 <use-feature> 를 통해 필터링해야 한다.
이 패키지를 사용하여 개발하기 위해서는 안드로이드 SDK Manager에서 Google APIs Android 10 패키지를 install 해야 한다.

android.hardware.usb

Android 3.1 이상

안드로이드 프레임워크에 포함된 패키지로서 add-on library를 사용할 필요없다. 만약 안드로이드 3.1 이후의 최신 장비만 지원하도록 앱을 개발한다면 이 패키지를 사용하면 된다.

APIs

add-on library는 프레임워크의 APIs를 wrapper 하였기 때문에 클래스 명과 기능적으로 유사하며 add-on library를 사용해도 android.hardware.usb 패키지를 참조하면 된다.

 

Android USB Accessory APIs
Class 설명

UsbManager

연결된 USB 액세서리들을 조회할 수 있을 뿐만 아니라 액세서리와 통신할 수 있는 방법을 제공한다.

UsbAccessory

USB 액세서리를 나타내며 액세서리의 정보를 획득할 수 있는 메서드들이 있다.

 

add-on library와 프레임워크 APIs에서 차잇점은 다음과 같이 각 클래스를 획득하는 방법에서 찾아볼 수 있다. (UsbAccessory는 intent에 포함된 것을 획득할 경우)

 

add-on library와 framework api 차잇점
Class add-on library framework api

UsbManager

UsbManager manager =
UsbManager.getInstance(this);

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

UsbAccessory

UsbAccessory accessory =
UsbManager.getAccessory(intent);

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

매니페스트 설정

USB 액세서리 API를 사용하기 위해서는 앱의 매니페스트 파일에 몇가지를 선언해줘야 한다.

 

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

 

매니페이스에는 다음과 같은 것이 정의되어져 있는 것이다.

  • 모든 안드로이드 장비들이 USB 액세서리 API를 지원하는 것은 아니므로 <uses-feature> 엘리먼트를 이용하여 앱이 android.hardware.usb.accessory 사용한다는 것을 선언해야 한다.
  • 만약 add-on library를 사용하는 것이면 <uses-library> 엘리먼트를 이용하여 com.android.future.usb.accessory 사용을 명시해야 한다.
  • 만약 add-on library를 사용하면 minSdkVersion에 10 (API Level 10)으로 설정하고 android.hardware.usb를 사용하는 것이면 12 (API Level 12)로 설정한다.
  • 만약 USB 액세서리가 안드로이드 장비와 연결됨과 동시에 알고 싶으면 <intent-filter>와 <meta-data> 엘리먼트를 설정해줘야 하는데 먼저 Activity 안에 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 에 해당하는 인텐트를 받기 위해 <intent-filter>를 선언하고 검색을 원하는 식별 정보를 선언한 XML 리소스 파일을 가리키는 <meta-data> 엘리먼트를 선언한다.

XML 리소스 파일안에는 원하는 액세서리의 제조사(manufacturer), 모델(model), 버전(version)을 <use-accessory> 엘리먼트를 이용하여 선언한 내용이 있으면 된다.

 

<?xmlversion="1.0"encoding="utf-8"?>

<resources>

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

</resources>

 

이 XML 파일은 res/xml/ 디렉토리에 저장하면 되고 <meta-data> 엘리먼트에는 @xml/[확장자를 뺀 파일명] 으로 선언하면 된다. 즉 res/xml/accessory_filter.xml 에 저장하였으면 위의 예제처럼 @xml/accessory_filter 라고 선언하면 된다.

액세서리와의 동작

안드로이드 장비에 액세서리가 연결될 때 안드로이드 시스템은 연결된 액세서리를 앱에서 획득하여 제어할 수 있는 방법을 제공하고 있는데 기본적으로 앱은 다음 3가지를 작업을 수행한다.

  • 액세서리가 연결될 때 발생하는 이벤트를 받기 위해 인텐트 필터를 사용 혹은 연결된 액세서리 목록을 획득하여 원하는 액세서리를 찾는 방법 중 하나를 통해 원하는 액세서리 획득한다.
  • 액세서리와 통신하기 위해 사용자로부터 허가(Permission)를 얻어야 한다.
  • 인터페이스 endpoint를 통하여 데이터를 읽고 쓰기를 한다.

액세서리 검색

앱은 우선 위에서 설명했던 매니페스트에 인텐트 필터를 등록하여 액세서리가 연결될 때 발생하는 이벤트시에 액세서리 정보를 획득할 수 있다. 액세서리의 정보는 Activity에 전달되는 인텐트(Intent) 객체를 통해 전달되는데 Add-on library를 사용할 경우에는

UsbAccessory accessory =UsbManager.getAccessory(intent);

프레임워크 API를 사용할 경우에는

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


를 사용하여 액세서리 정보를 획득한다.

인텐트 필터 등록외에 getAccessoryList() 메서드를 통해 현재 연결되어져 있는 액세서리들의 목록을 획득할 수 있다.

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

참고

현재는 하나의 연결된 액세서리 정보만 반환하고 있지만 가까운 미래에 여러개의 액세서리 정보를 반환하도록 지원될 것이다.

사용허가 획득

액세서리와 통신하기에 앞서 앱의 사용자로 부터 통신할 수 있는 허가를 받아야 한다. 만약 인텐트 필터를 등록했을 경우 연결될 때 자동으로 사용자에게 허가를 받도록 나타난다. 그러나 만약 사용자가 허가하지 않았을 때를 대비하여 액세서리에 연결할 때 명시적으로 허가를 요청하여야 한다.

명시적으로 허가를 얻기 위해서 먼저 브로드캐스트 리시버(broadcast receiver)를 생성한다. 이 리시버는 requestPermission()을 호출할 때 브로드캐스트를 얻는 인텐트를 받기 위함인데 requestPermission()을 호출하면 사용자에게 액세서리에 연결해도 되는지를 묻는 다이얼로그가 나타나고 사용자의 선택에 따른 결과가 인텐트를 통해 리시버에 전달된다.

리시버의 샘플코드는 다음과 같다.

 

private static final String ACTION_USB_PERMISSION =
   "com.android.example.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver =newBroadcastReceiver(){
  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){
            //액세서와 통신하기 위한 설정 코드
          }
        }
        else{
          Log.d(TAG,"permission denied for accessory "+ accessory);
        }
      }
    }
  }
};

 

이렇게 만든 리시버를 Activity 의 onCreate() 메서드에서 등록을 한다.

 

UsbManager mUsbManager =(UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this,0,newIntent(ACTION_USB_PERMISSION),0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

 

연결을 위해 허가를 획득하기 위한 다이얼로그를 표시하기 위해 requestPermission() 메서드를 호출한다.

 

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

 

사용자가 응답하면 리시버는 EXTRA_PERMISSION_GRANTED extra를 포함된 인텐트를 받는다. 이 값은 boolean 형식이다. 이 값을 통해 허가 여부를 판단한다.

액세서리와의 통신

허가를 획득하였으면 UsbManager를 통해 액세서리와 통신할 수 있는 file descriptor를 획득할 수 있다. 이 file descriptor를 통해 읽고 쓸 수 있는 input, output 스트림을 설정할 수 있다. 그리고 액세서리와 통신하는 코드는 메인 쓰레드가 lock 되지 않도록 별도의 쓰레스를 사용한다. 다음은 이에 대한 샘플 코드이다.

 

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

 

쓰레드의 run 메소드안에 FileInputStream 객체를 통해 액세서리로부터 데이터를 읽고 FileOutputStream 객체를 통해 액세서리로 데이터를 보내는 작업을 수행한다. FileInputStream 객체를 통해 읽을 때 데이터를 충분히 담을 수 있는 버퍼를 사용해야 하는데 안드로이드 액세서리 프로토콜은 16384 bytes 이상의 패킷 버퍼를 지원한다. 그러므로 앱에서 이 버퍼 사이즈로 정의하여 읽도록 코딩할 수 있다.

참고

낮은 레벨에서 패킷들은 USB full-speed 액세서리들은 64 bytes 그리고 USB high-speed 액세서리들은 512 bytes 이다. 안드로이드 액세서리 프로토콜은 단순한 하나의 논리적인 패킨에 양쪽의 스피드에 대해 패킷들이 함께 묶여져 있다.

액세서리와 통신 종료

액세서리와 통신을 끝마쳤을 경우 혹은 액세서리가 연결 해제되었을 경우 close() 메서드를 통해 file descriptor를 종료 시킨다. 액세서리가 연결 해제되는 이벤트를 수신하기 위하여 다음과 같은 브로드캐스트 리시버를 생성하여 등록한다.

 

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

 

이 리시버는 매니페스트를 통해 등록하여 생성하지 말고 코드 안에서 생성해야 한다. 왜냐하면 연결 해제에 대한 인벤트는 현재 액세서리와 동작하고 있는 앱에만 보내질뿐 모든 앱에 브로드캐스트되지 않기 때문이다.

 

[출처] http://hardroid.net/profiles/blogs/android-usb-accessory

Comments