Android6 互換モード(2):カメラAPIへのアクセスはRuntimeException
Android6.0でランタイムパミッションモデルについて、調査した事項を色々と情報共有しています。先日の「ランタイムパミッションの互換性(1)」の記事のタイトルに1とついていますが、今回はその2で、カメラAPIの互換モードについてです。互換モードとは、一言で言えば、「Android6.0以上の端末で、インストールタイムパーミッションモデルで実行しているアプリケーションが、端末の設定画面から、特定のパーミッションをはく奪された時の事を言います。」詳しくは「ランタイムパミッションの互換性(1)」を参照してください。
結論から言えば、飛びます。カメラアプリは要修正です。Android Developer Siteでは、互換モードでは、ContentProvider系では0件を返したりして、それなりに動くと書いてありますが、何故かカメラは飛びます。
以下調査結果です。
互換モードでどのような動作をするかを調査するために、targetSdkVersion=21で作成したアプリをAndroid OS 6.0の端末にインストールし、設定画面よりカメラのアクセス(CAMERA)を不許可にした時の動作を確認しました。
また、比較のため、同じコードをtargetSdkVersion=23に変更しAndroid 6.0の端末にインストールした時の動作も確認しました(requestPermissionはしていないので必ずSecurityException等なんらかのExceptionを吐く)
カメラグループとパミッション
パーミッショングループ | パーミッション | 説明 |
---|---|---|
CAMERA | CAMERA | 写真と動画の撮影 カメラでの写真と動画の撮影をアプリに許可します。これにより、アプリが確認なしでいつでもカメラを使用できるようになります。 |
カメラクラスを行うテストコード
AndroidManifest.xml
targetSdkVersion=xxx <uses-permission android:name="android.permission.CAMERA"/>ソース
// android.hardware.Camera.open()によるカメラアクセス(API level 20以前) Camera camera = Camera.open(); // android.hardware.Camera.open()によるカメラアクセス(API level 20以前、カメラID指定) Camera camera = Camera.open(0); // android.hardware.camera2.CameraManager#openCamera()によるカメラアクセス(API level 21 以降) CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE); manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); ….
MediaRecorderを利用するテストコード
AndroidManifest.xmltargetSdkVersion=xxx <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>ソース
MediaRecorder mRecorder = new MediaRecorder(); mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFile(“test.mp4”); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); mRecorder.setVideoFrameRate(30); mRecorder.setVideoSize(640, 480); mRecorder.setPreviewDisplay(mHolder.getSurface()); mRecorder.prepare(); mRecorder.start(); ….
テスト環境
- Nexsus 5をAndroid 6.0にバージョンアップした物
- イメージバージョン番号:MRA58K
※アンドキュメントな部分なので確認した環境です
結果
Android6.0上で設定メニューから「カメラ」パーミッショングループを不許可にした場合
targetSdkVersion=21
互換モードの動作です。
- Camera.open()呼び出し時に以下のRuntimeExceptionが発生
- java.lang.RuntimeException: Fail to connect to camera service
- CameraManager#openCamera()呼び出し時に以下のCameraAccessExceptionが発生
- android.hardware.camera2.CameraAccessException: The camera is disabled due to a device policy, and cannot be opened.
- MediaRecorder#start()呼び出し時に以下のRuntimeExceptionが発生
- java.lang.RuntimeException: start failed.
targetSdkVersion=23
本来requestPermissionすべきですがしてませんのでエラー系となるのが当然です。
- Camera.open()呼び出し時に以下のRuntimeExceptionが発生
- java.lang.RuntimeException: Fail to connect to camera service
- CameraManager#openCamera()呼び出し時に以下のエラーログおよびSecurityExceptionが発生
- Permission failure: android.permission.CAMERA from uid=10130 pid=25477
- Permission Denial: can't use the camera pid=25477, uid=10130
- java.lang.SecurityException
- MediaRecorder#setVideoSource()呼び出し時に以下のRuntimeExceptionが発生
- java.lang.RuntimeException: setVideoSource failed.
まとめ
targetSdkVersionの設定に関係無く、カメラパーミッションを必要とするメソッドは互換モードで実行された場合全て何らかのExceptionをthrowし、アプリがほぼ確実に異常終了するため対応は必須と言える
MediaRecorderを利用する場合、互換モードではtargetSdkVersionが23未満の場合MediaRecorder#start()がRuntimeExceptionをthrowするが、targetSdkVersionが23の場合前段階のMediaRecorder#setVideoSource()がRuntimeExceptionをthrowするなどthrowのタイミングが違うので少し注意が必要
カメラ機能を使っている人は、Camera2とか出てきて大変だなぁと思っていましたが、これを機にCamera2に移行!ついでにランタイムパーミッションに対応がいいのではないでしょうか?
またあくまでも互換モードの話なので、対処しないというのも有りだとは思います。もしくは今まで、ガリガリつくっていたけれども、Intentを利用して外部ガメラアプリを呼ぶ仕様にしてしまうのも有りだと思います。(パーミッションもいらないですし…)