Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: [Android] Opening camera/gallery does not work on Android 11 #3420

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:requestLegacyExternalStorage="true"
kidroca marked this conversation as resolved.
Show resolved Hide resolved
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
Expand Down
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ PODS:
- React-Core
- react-native-document-picker (5.1.0):
- React-Core
- react-native-image-picker (2.3.4):
- react-native-image-picker (4.0.3):
- React-Core
- react-native-netinfo (5.9.10):
- React-Core
Expand Down Expand Up @@ -769,7 +769,7 @@ SPEC CHECKSUMS:
React-jsinspector: 500a59626037be5b3b3d89c5151bc3baa9abf1a9
react-native-config: d8b45133fd13d4f23bd2064b72f6e2c08b2763ed
react-native-document-picker: 0e3602a4064da040321bafad6848d8b0edcb1d55
react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
react-native-image-picker: 4089335b89b625d4e34d53fb249c48a7a791b3ea
react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d
react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f
react-native-plaid-link-sdk: 1a6593e2d3d790e8113c29178d883eb883f8c032
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"react-native-document-picker": "^5.1.0",
"react-native-gesture-handler": "1.9.0",
"react-native-image-pan-zoom": "^2.1.12",
"react-native-image-picker": "^2.3.3",
"react-native-image-picker": "^4.0.3",
"react-native-keyboard-spacer": "^0.4.1",
"react-native-modal": "^11.10.0",
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#1e82e592032c6d0ede8e40f08beb6be790d149e8",
Expand Down
34 changes: 20 additions & 14 deletions src/components/AttachmentPicker/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import React, {Component} from 'react';
import {Alert, Linking, View} from 'react-native';
import RNImagePicker from 'react-native-image-picker';
import {launchImageLibrary} from 'react-native-image-picker';
import RNDocumentPicker from 'react-native-document-picker';
import basePropTypes from './AttachmentPickerPropTypes';
import styles from '../../styles/styles';
Expand All @@ -13,6 +13,7 @@ import {Camera, Gallery, Paperclip} from '../Icon/Expensicons';
import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import compose from '../../libs/compose';
import launchCamera from './launchCamera';

const propTypes = {
...basePropTypes,
Expand All @@ -25,9 +26,9 @@ const propTypes = {
* for ImagePicker configuration options
*/
const imagePickerOptions = {
storageOptions: {
skipBackup: true,
},
includeBase64: false,
saveToPhotos: false,
selectionLimit: 1,
};

/**
Expand Down Expand Up @@ -70,12 +71,12 @@ class AttachmentPicker extends Component {
{
icon: Camera,
text: this.props.translate('attachmentPicker.takePhoto'),
pickAttachment: () => this.showImagePicker(RNImagePicker.launchCamera),
pickAttachment: () => this.showImagePicker(launchCamera),
},
{
icon: Gallery,
text: this.props.translate('attachmentPicker.chooseFromGallery'),
pickAttachment: () => this.showImagePicker(RNImagePicker.launchImageLibrary),
pickAttachment: () => this.showImagePicker(launchImageLibrary),
},
{
icon: Paperclip,
Expand All @@ -95,7 +96,7 @@ class AttachmentPicker extends Component {
* @param {ImagePickerResponse|DocumentPickerResponse} attachment
*/
pickAttachment(attachment) {
if (attachment && !attachment.didCancel && !attachment.error) {
if (attachment) {
kidroca marked this conversation as resolved.
Show resolved Hide resolved
if (attachment.width === -1 || attachment.height === -1) {
this.showImageCorruptionAlert();
return;
Expand Down Expand Up @@ -135,20 +136,25 @@ class AttachmentPicker extends Component {
showImagePicker(imagePickerFunc) {
return new Promise((resolve, reject) => {
imagePickerFunc(imagePickerOptions, (response) => {
if (response.error) {
switch (response.error) {
case 'Camera permissions not granted':
case 'Permissions weren\'t granted':
if (response.didCancel) {
// When the user cancelled resolve with no attachment
return resolve();
}
if (response.errorCode) {
switch (response.errorCode) {
case 'permission':
this.showPermissionsAlert();
break;
default:
this.showGeneralAlert(response.error);
this.showGeneralAlert();
kidroca marked this conversation as resolved.
Show resolved Hide resolved
break;
}
reject(new Error(`Error during attachment selection: ${response.error}`));

return reject(new Error(`Error during attachment selection: ${response.errorMessage}`));
}

resolve(response);
// Resolve with the first (and only) selected file
return resolve(response.assets[0]);
});
});
}
Expand Down
32 changes: 32 additions & 0 deletions src/components/AttachmentPicker/launchCamera.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {PermissionsAndroid} from 'react-native';
import {launchCamera} from 'react-native-image-picker';

/**
* Launching the camera for Android involves checking for permissions
* And only then starting the camera
* If the user deny permission the callback will be called with an error response
* in the same format as the error returned by react-native-image-picker
* @param {CameraOptions} options
* @param {function} callback - callback called with the result
*/
export default function launchCameraAndroid(options, callback) {
Copy link
Contributor Author

@kidroca kidroca Jun 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per react-native-image-picker: https://github.com/react-native-image-picker/react-native-image-picker#android

Note: This library does not require Manifest.permission.CAMERA, if your app declares as using this permission in manifest then you have to obtain the permission before using launchCamera.

E.cash has Manifest.permission.CAMERA declared in the Manifest

I've also found out that if the user manually revokes the camera permission the "Take Photo" button
would stop working, but the "General" alert "An error occurred while selecting an attachment, please try again" would be displayed for the user, instead the user should see "Expensify.cash does not have access to your camera, please enable the permission and try again." with a link to "Settings". This code achieves the desired behavior

Before extracting launchCamera.android.js

image

// Checks current camera permissions and prompts the user in case they aren't granted
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA)
.then((permission) => {
if (permission !== PermissionsAndroid.RESULTS.GRANTED) {
const error = new Error('User did not grant permissions');
error.errorCode = 'permission';
throw error;
}

launchCamera(options, callback);
})
.catch((error) => {
/* Intercept the permission error as well as any other errors and call the callback
* follow the same pattern expected for image picker results */
callback({
errorMessage: error.message,
errorCode: error.errorCode || 'others',
});
});
}
3 changes: 3 additions & 0 deletions src/components/AttachmentPicker/launchCamera.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {launchCamera} from 'react-native-image-picker';

export default launchCamera;