AndroidでUSB機器を使用する場合パーミッションが必要ですが、マニフェストではUSB機器のパーミッションを設定できません。なので通常はアプリ内で実行時にパーミッションを要求する必要があります。
具体的な手順は昔の記事※を見てもらうことにして(^_^;)、パーミッションを要求すると、Androidのシステムがダイアログを表示してくれます。このダイアログには「このUSBデバイスにデフォルトで使用する」というオプションにチェックを入れることが出来るようになっています。
そんなの関係ねー
でもこのオプションにはちょっとした罠が仕掛けてあるのです ガ━━(;゚Д゚)━━ン!!
普通の人がこのオプションを見たら、これにチェックを入れとけば「そのUSB機器をつなぐ度に自動的にそのアプリにパーミッションを与えてくれる」みたいな風に考えませんか?
でもそういう動作をさせるにはある条件を満たさないとだめなのです。その条件を満たさない限りUSB機器を取り付けてもアプリに自動的にパーミッションが与えられることはなく、必然的に毎回パーミッション要求のダイアログを表示するはめになるのです。
そもそも何で調べたかって?
自分がGooglePlayで公開しているAndroid端末にUSBカメラを繋いで映像を見たり録画・静止画の撮影をするためのアプリ「UsbWebCamera」「UsbWebCameraPro」でもUSB機器のパーミションを要求しています。
このアプリでは元々UVCカメラを繋いだ時に自動で起動するようにするためにある設定をしていました。でも自動で起動させたくないなんていう人も居るので、有料のpro版についてはAndroid5.x対応をする時に少し実装を変えてオプションで選択できるようにしました(ホントは他にそのUVCカメラを使いたいアプリがあるんならそのアプリへデフォルトで使用する設定をすりゃいいはずなんですけどね。でもそのアプリも同じ罠にハマってたら・・・)。
それはともかくその修正の結果、先に書いた罠にハマったしまったのです。動作変わった〜って思ってた人ごめんなさいm(_ _)m 近日公開予定の次の更新で修正します。
広告付きの無料版の方は自動起動周りを変更してないので今までと同じように、一度「このUSBデバイスにデフォルトで使用する」オプションにチェックを入れればカメラを取り付けなおしても自動的にパーミッションを取得できます。
それでいったいなんでやねん
とりあえず罠を回避する条件を書きましょう。前置きが長すぎ? だって内容があんまり、ないよー(^^)
端的に言えば、「マニフェストでactivityへandroid.hardware.usb.action.USB_DEVICE_ATTACHEDを受け取るためのインテントフィルターを設定する」です。
具体的には下のようにしまするー。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
... <activity android:name="ターゲットとなるActivityの完全修飾名" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <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> ... |
こんな感じでアクティビティに対してインテントフィルターを設定していれば罠の発動条件を回避できます。
インテントフィルターはactivity, activity-alias, service, receiverに対しても設定できますが、少なくともservice, receiverではインテントフィルターを設定しても罠が発動しました↓
以前のアップデートの際にpro版ではserviceを生成してその中で自動起動のフラグチェックを行ってからアクティビティを起動するようにしちゃったので罠にハマっちゃったわけなんですね。
まぁAPIのレファレンスでUsbManager#requestPermissionについて見てみると、
Requests temporary permission for the given package to access the device. This may result in a system dialog being displayed to the user if permission had not already been granted. Success or failure is returned via the PendingIntent pi. If successful, this grants the caller permission to access the device only until the device is disconnected.
と書いてあるので、USB機器を取り外した時にパーミッションが無くなるのが正常な動作で、「このUSBデバイスにデフォルトで使用する」オプションについては何にも書いてないので、そういう仕様やねんと言われれば諦めるしかないんですけど。
内部動作を想像してみた
ランチャー等から普通にアプリを起動するとアクティビティは、アクションが”android.intent.action.MAIN”, カテゴリが”android.intent.category.LAUNCHER”のインテントを受け取ります。
一方、マニフェストにUSB_DEVICE_ATTACHEDのインテントフィルターを設定すると、UVC機器を接続した時に、アクションが”android.hardware.usb.action.USB_DEVICE_ATTACHED”となったインテントを受け取ります。
USB_DEVICE_ATTACHEDのインテントで起動された時には対象となったUSB機器へのパーミションが与えられています。どのUSB機器が対象になったかは、
1 |
final UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); |
ってすれば取得できます。
と言う事でここからが想像なんですが、AndroidのシステムがUSB機器の接続を検知してUSB_DEVICE_ATTACHEDのインテントをアクティビティに投げる時のみ「このUSBデバイスにデフォルトで使用する」オプションが設定してあればシステム側でパーミッションを自動的に取得してからインテントを投げてくれるんじゃないかと。
でもサービスやブロードキャストレシーバーへインテントを投げる時には「このUSBデバイスにデフォルトで使用する」オプションは無視される・・・仕様なんかバグなんかはよく判りません。
AOSPのソースを追っかけたわけでは無いのであくまで想像ですけどね。
まとめ
「このUSBデバイスにデフォルトで使用する」オプションを有効に使いたければ、「マニフェストでactivityへandroid.hardware.usb.action.USB_DEVICE_ATTACHEDを受け取るためのインテントフィルターを設定」しましょう(^o^)/
知ってた〜?
と言う事で、おしまい。お疲れ様でした。
コメント
[…] これ)、 […]