こんにちは、kintone開発チームのAndroid担当のトニオ(@tonionagauzzi)です。
本日は、Androidアプリ開発中にカメラの権限を調査していてわかったことを共有します。
概要
Androidアプリで写真や動画を撮影する手段として、ACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
のインテントを用いてカメラアプリで撮影する方法が公式ドキュメントに書かれています。
その場合、Manifest.permission.CAMERA
の権限は宣言せずインテントを呼び出すべきことも公式ドキュメントに書かれています。
そこだけ読むと、ACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
とCAMERA
の権限は無関係のように読めるのですが、実はそうではありません。
他機能のためにCAMERA
の権限を宣言しており、CAMERA
の権限をユーザーが許可していない場合、ACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
のインテントでカメラアプリを起動しようとするとSecurityException
が発生してしまうのです。
背景
Androidアプリで写真や動画を撮影したい場合、MediaStore
のACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
のアクションを使えます。
private fun takePicture(savePictureUri: Uri) { val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, savePictureUri) startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) } private fun takeVideo(saveVideoUri: Uri) { val takeVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, saveVideoUri) startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE) }
このインテントを呼ぶと、プリインのカメラアプリが起動します。
写真や動画を撮影すると、撮影データがsavePictureUri
やsaveVideoUri
に保存されます。
撮影後はonActivityResult
が呼ばれ、撮影したデータが受け取れます。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) { val pictureUri = data?.data // ここで写真URIを使って何かを行うか、保存するなどの処理を行う } else if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == Activity.RESULT_OK) { val videoUri = data?.data // ここで動画URIを使って何かを行うか、保存するなどの処理を行う } }
ActivityResultContract
を使う実装も可能ですが、今回は省略します。
本題
さて、この実装は以下のようなManifest.permission.CAMERA
の実装を必要とするのでしょうか。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.yourpackage"> <uses-permission android:name="android.permission.CAMERA" /> <application ... </application> </manifest>
private fun checkCameraPermission() { if ( ContextCompat.checkSelfPermission( this, Manifest.permission.CAMERA ) != PackageManager.PERMISSION_GRANTED ) { // 権限が許可されていない場合、ダイアログを表示してユーザーに許可を求める ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE ) } else { // 権限がすでに許可されている場合、カメラの処理を実行する takePicture(savePictureUri = createSavePictureUri()) } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray, ) { if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // ユーザーが権限を許可した場合、カメラの処理を実行する takePicture(savePictureUri = createSavePictureUri()) } else { // ユーザーが権限を拒否した場合、適切なエラーハンドリングを行うか、 // 権限要求を再試行する方法を提供する ... } } }
結論から言えば、他の箇所でandroid.permission.CAMERA
を使っていなければ、AndroidManifestに書く必要はなく、requestPermission
も必要ありません。
公式ドキュメントでは、以下のように書かれています。
プリインストールされているシステム カメラアプリを使用して、ユーザーがアプリで写真を撮影できる場合があります。 このような場合、
CAMERA
権限を宣言しないでください。代わりに、ACTION_IMAGE_CAPTURE
インテントのアクションを呼び出します。
ところが、他の箇所のためにandroid.permission.CAMERA
をAndroidManifestで宣言していて、引き続き宣言が必要な場合、事情が違います。ACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
でもrequestPermission
が必要です。
それはMediaStoreのドキュメントに書いてあります。
Note: if you app targets M and above and declares as using the
Manifest.permission.CAMERA
permission which is not granted, then attempting to use this action will result in aSecurityException
.
カメラの権限を宣言していてユーザーが許可していない場合、ACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
を呼ぶとSecurityException
が発生してしまうのです。
GoogleのIssueTrackerによると、ユーザーがカメラについて意思表示をしていない場合とは異なり、ユーザーが明示的にカメラを拒否しているのであれば、カメラを起動するような処理は一切してはならないという判断のようです。
拒否しているはずのカメラが起動できてしまった、と考えるユーザーもいるのですね。
であれば初期状態ではユーザーはまだ意思表示していないから起動できるのでは、と思いますが、AndroidManifestにandroid.permission.CAMERA
を宣言している時点で、初期状態は「許可しない」、つまり拒否状態と同じようです。
アプリインストール時にアプリに与えられた権限を読んで、それに同意していないということは、許可しない意思表示だと扱われているんですね。
なので、許可状態にするためにrequestPermission
が必要なのです。
まとめ
他の箇所でandroid.permission.CAMERA
を必要としない場合、ACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
のためにandroid.permission.CAMERA
をAndroidManifestに書く必要もrequestPermission
する必要もありません。
しかし、他の箇所のためにandroid.permission.CAMERA
の使用を宣言している場合、初期状態ではACTION_IMAGE_CAPTURE
やACTION_VIDEO_CAPTURE
を含むカメラに関する操作が拒否されているので、requestPermission
する必要があります。