expo: [expo-image-picker] [Android] Unable to open camera, "user rejected permissions" despite having permissions

Summary

I am running the minimal reproducible example below on Android SDK 33, but am getting the following error:

Error: Call to function 'ExponentImagePicker.launchCameraAsync' has been rejected.
→ Caused by: User rejected permissions

status is returned as

{"canAskAgain": true, "expires": "never", "granted": true, "status": "granted"}

I’ve checked Settings -> Permissions in Android settings and my app has permission to use the camera.

I am running a bare project and have upgraded to Android SDK 33, so maybe that has something to do with it.

I was wondering if anyone else has run into a similar issue.

What platform(s) does this occur on?

Android

Environment

expo-env-info 1.0.5 environment info: System: OS: macOS 12.0.1 Shell: 5.8 - /bin/zsh Binaries: Node: 14.17.6 - ~/.nvm/versions/node/v14.17.6/bin/node Yarn: 1.22.19 - ~/.nvm/versions/node/v14.17.6/bin/yarn npm: 6.14.15 - ~/.nvm/versions/node/v14.17.6/bin/npm Watchman: 2022.02.28.00 - /usr/local/bin/watchman Managers: CocoaPods: 1.10.1 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5 IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8815526 Xcode: 13.4/13F17a - /usr/bin/xcodebuild npmPackages: @expo/webpack-config: ^0.17.0 => 0.17.3 expo: ~46.0.2 => 46.0.17 react: 18.0.0 => 18.0.0 react-dom: 18.0.0 => 18.0.0 react-native: 0.69.6 => 0.69.6 react-native-web: ~0.18.7 => 0.18.10 npmGlobalPackages: eas-cli: 0.57.0 expo-cli: 6.0.1 Expo Workflow: bare

Minimal reproducible example

npm i expo-image-picker@~13.3.1

import { Button } from 'react-native'
import * as ImagePicker from 'expo-image-picker'

export default () => {
  const [status, requestPermission] = ImagePicker.useCameraPermissions()

  const launchCamera = async () => {
    if (status && !status.granted) {
      await requestPermission()
    }
    
    const result = await ImagePicker.launchCameraAsync({
      allowsEditing: true,
      aspect: [3, 2],
      quality: 0.2
    })
  }

  return <Button title='Open camera' onPress={launchCamera} />
}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 30 (7 by maintainers)

Most upvoted comments

Found the issue https://github.com/expo/expo/blob/main/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt#L193

when compileSdkVersion is set to 33 permissionsResponse[Manifest.permission.WRITE_EXTERNAL_STORAGE]?.status is always resolves as BLOCKED

So it this error raised due to WRITE_EXTERNAL_STORAGE permission instead of CAMERA.

So I’ve patched ImagePickerModule.kt

--- a/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
+++ b/node_modules/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
@@ -141,9 +141,7 @@ class ImagePickerModule(
     }
 
     val permissionsResponseHandler = PermissionsResponseListener { permissionsResponse: Map<String, PermissionsResponse> ->
-      if (permissionsResponse[Manifest.permission.WRITE_EXTERNAL_STORAGE]?.status == PermissionsStatus.GRANTED &&
-        permissionsResponse[Manifest.permission.CAMERA]?.status == PermissionsStatus.GRANTED
-      ) {
+      if (permissionsResponse[Manifest.permission.CAMERA]?.status == PermissionsStatus.GRANTED) {
         launchCameraWithPermissionsGranted(promise, cameraIntent, pickerOptions)
       } else {
         promise.reject(SecurityException("User rejected permissions"))

For my case I have our own permissions check using ‘react-native-permissions’ package and we are on bare project

const requestWritePermission = async () => {
  if (Platform.OS === 'android' && Platform.constants.Version >= 33) {
    // always return true for Android 13+ as WRITE_EXTERNAL_STORAGE is always blocked there https://stackoverflow.com/questions/73620790/android-13-how-to-request-write-external-storage
    return true;
  }

  const res = await request(
    Platform.select({android: PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE, ios: PERMISSIONS.IOS.PHOTO_LIBRARY_ADD_ONLY}),
  );
  return res === RESULTS.GRANTED;
};

The patch @enagorny provided isn’t accurate for version 14.0.2. I updated the ensureCameraPermissionsAreGranted function as follows to get the camera working with expo 47 and build properties targeting Android api 33

  private suspend fun ensureCameraPermissionsAreGranted(): Unit = suspendCancellableCoroutine { continuation ->
    val permissions = appContext.permissions ?: throw ModuleNotFoundException("Permissions")

    permissions.askForPermissions(
      { permissionsResponse ->
        if (
          permissionsResponse[Manifest.permission.CAMERA]?.status == PermissionsStatus.GRANTED
        ) {
          continuation.resume(Unit)
        } else {
          continuation.resumeWithException(UserRejectedPermissionsException())
        }
      },
      Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA
    )
  }

@emilhdiaz This wouldn’t work with Expo Go as this patch modifies native kotlin file. This file used during native build. When you are using Expo Go all modifications to native files are ignore as native build comes from expo team and there is no way to modify it.

Best way to fix it - upgrade to the latest Expo.

Should be fixed in #20908

Neither working in expo 47, Is there any solution for this issue?

Same thing happening for me. Seems to only happen if the user has selected permission of ‘use once’.