Closed kperreau closed 1 year ago
same to me. i'm using android:requestLegacyExternalStorage="true". should i do somthing because of changed policy?
same to me. i'm using android:requestLegacyExternalStorage="true". should i do somthing because of changed policy?
Essentially, we have two options, according to Google:
Update your app to declare the All files access (MANAGE_EXTERNAL_STORAGE) permission in the manifest file, and complete the All files access permission declaration in Play
which will probably be frowned upon from a privacy standpoint by both Google and clients unless absolutely needed, and I can imagine Google will deny requests where they don't deem it necessary to use either (i.e. in the case of only accessing photos and videos).
The other option is that this library needs to be updated in advance before May 5th to allow developers using it time to update our apps to be compatible with Android 11+.
Aka implementing Scoped Storage Support needs to be done in the next 2 weeks if we want this library to remain compatible with Android 11+.
Does anyone know if there are any plans to do implementing Scoped Storage Support
?
I have the same problem. Is there any solution to fix it?
Based on the VERY short timeline here, I've posted a solution on SO
So it's working well on my side using the MediaStore API (not very intuitive API though) and the react-native-fs
package for scanning the file.
It can probably be adapted to CameraRoll and I'll be happy to submit a PR if the maintainers are ok with the solution.
Based on the VERY short timeline here, I've posted a solution on SO
So it's working well on my side using the MediaStore API (not very intuitive API though) and the
react-native-fs
package for scanning the file. It can probably be adapted to CameraRoll and I'll be happy to submit a PR if the maintainers are ok with the solution.
How to export this problem. It is very serious!
What do you mean @dvquoc ?
Regarding your solution @HugoGresse, what do you think? If we keep the logic inside the save method, this way the implementers won't have to do anything, just update the library. I based on your proposal and now I am working without requestLegacyExternalStorage flag and it works 🎉
If you are looking for a quick way to fix this while the library owners adjust it, you can do this
Remove from you android manifest.xml file the property:
android: requestLegacyExternalStorage = "true"
Make sure you have the following version of react-native-cameraroll in your package.json
"@react-native-community/cameraroll": "4.0.4",
Execute:
yarn add -d patch-package
In the root directoy from your project at the same herarchy of your node_modules create a new folder called patches
and paste this file inside
https://drive.google.com/file/d/1tHDpoDSLsP7ntt0fJSEjdaYVKEaG3_Uc/view?usp=sharing
Install your dependencies again to override add the logic to work without the requestLegacyExternalStorage
yarn install
After this all should work as normal
In the terminal you will see something like this
LGTM, will have done a PR rather than a patch though... possible improvements: move most of the patches to the native side:
Great recommendations @HugoGresse, i updated the patch https://drive.google.com/file/d/1tHDpoDSLsP7ntt0fJSEjdaYVKEaG3_Uc/view?usp=sharing
Hopefully the owner says if he agrees we can work on a PR thank you very much @HugoGresse
@kenMarquez i use your patch and solution. I deleted requestLegacyStorage and was able to save Photo to the Pictures directory on my Phone. But there is a problem with getting this Photo using uri returned from save method. The Photo not appear "Image source={{uri:path}} " . I think that that is a second problem, how to retrieve Photo using scope storage?
@HugoGresse's solution work only for images( What about videos?
@HugoGresse's solution work only for images( What about videos?
You need to look into MediaStore API for video but it shouldn't be much different, maybe just replace MediaStore.Images.
by MediaStore.Videos
or something like this, and the same for the EXTERNAL_CONTENT_URI
Hello @kursonix , how are you calling the save method? , did you called with await
syntax?
something like this:
const image = await CameraRoll.save(imagePath, {
album: ALBUM_NAME,
});
console.log('image', image);
@kenMarquez hi yes I use it like this. With requestLegacy flag it works
` public savePhoto = async (imagePath: string): Promise<string | undefined> => { if (Platform.OS === 'android' && !(await hasAndroidPermission())) { return; } const newPath = await CameraRoll.save(imagePath, { album: 'puzzleTracker', }); return getOrginalPath(newPath); };
async function getOrginalPath(rawPath: string): Promise${Platform.OS === 'android' ? 'file://' : ''}${ stat.originalFilepath }
;
}
return correctURI;
}`
getOrginalPath right now as I see is unnecessary.
But stil i'm unable to retrieve the photo
@bartolkaruza @Naturalclar would you mind taking a look at the proposed solution to fix this and let us know if you are somewhat ok so we can start the PR?
@HugoGresse thanks for taking up the issue, the proposed solution sounds fine to me, would you be able to start the PR?
I'll work on the PR tomorrow. In the meantime, I needed tochange the method to get the file path from the final MediaStore URI for some reason... here is the latest one I used:
// From https://stackoverflow.com/a/64359655/1377145
public static String getNameFromContentUri(Context context, Uri contentUri){
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(contentUri, null, null, null, null);
cursor.moveToFirst();
String document_id = cursor.getString(0);
document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
cursor.close();
cursor = contentResolver.query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
cursor.moveToFirst();
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
When using the new patch, I get images 90 degree angle now.
Also on Samsung Galaxy A21 running Android 10, we get a list of images, but cannot show them on the device.
@HugoGresse Hello, I have applied your changes and removed requestLegacyExternalStorage. But I can no longer get photos in download folder in Android 10, is it save to keep requestLegacyExternalStorage as Android 11 will ignore it? Thank you.
@lucaspang Hi. Right now the option with requestLegacyExternalStorage will work if you target api level 29(Android 10). For exising app in app store it is still valid until November. After that to update app you must use api level 30. For new apps in app store you must target api level 30. Can someone confirm that is true? I will not have problem after 5 May if i still use api level 29 with this flag
@lucaspang Hi. Right now the option with requestLegacyExternalStorage will work if you target api level 29(Android 10). For exising app in app store it is still valid until November. After that to update app you must use api level 30. For new apps in app store you must target api level 30. Can someone confirm that is true? I will not have problem after 5 May if i still use api level 29 with this flag
If that's the case, this gave us some time to finish the PR. In any case, I'll test in production the first solution I posted in SO starting next week on 10k users and keep you posted.
@lucaspang Hi. Right now the option with requestLegacyExternalStorage will work if you target api level 29(Android 10). For exising app in app store it is still valid until November. After that to update app you must use api level 30. For new apps in app store you must target api level 30. Can someone confirm that is true? I will not have problem after 5 May if i still use api level 29 with this flag
I understood the same as you, but the google warning in the console does not specify anything about whether it is for 30 or 29, it would be great if someone can confirm this with some documentation
@HugoGresse @pabloearg there is a thread about it on reddit https://www.reddit.com/r/androiddev/comments/mwaqn1/scoped_storage_recap/ and the answer from google tem member "This Google Play policy refers specifically to apps that target API level 30 and need the MANAGE_EXTERNAL_STORAGE permission (All Files Access). If you don’t use or plan to use this permission, this policy shouldn’t affect you. If you are currently targeting API level 29 and want to use this permission when you update to target API level 30, you will need to comply with this policy."
can somebody look also at my research posted in here and do some double checks? https://github.com/status-im/status-react/issues/12063
I just contact google support and this was their answer
so as long as you point to api 29, nothing should happen
So we can wait for complete update in this library until November with Api 29, right?
ext {
buildToolsVersion = "29.0.2"
minSdkVersion = 19
compileSdkVersion = 29
targetSdkVersion = 29
}
@kenMarquez @kursonix I used the patch but the retrieved images are not visible, I tried
<Image
source={{
uri: x.node.image.uri, //uri
}}>
it works when legacy storage flag is true.
Any idea why is this issue happening?
@kunalyelne Are you still with the problem?
Could you validate if the path you are receiving is starting with the file://
format ?
@kenMarquez Yeah still facing the problem. Also, I did check the uri and it is correct. It begins with file:///
. Do we have to access the file using Media Store APIs on Native side or something?
A better attempt might be to test directly from the pull request made for @HugoGresse and don't forget remove the patch file
yarn add https://github.com/HugoGresse/react-native-cameraroll.git#304-scopedStorage
@kenMarquez @kursonix I used the patch but the retrieved images are not visible, I tried
<Image source={{ uri: x.node.image.uri, //uri }}>
it works when legacy storage flag is true.
Any idea why is this issue happening?
Same, any update ?
A better attempt might be to test directly from the pull request made for @HugoGresse and don't forget remove the patch file
yarn add https://github.com/HugoGresse/react-native-cameraroll.git#304-scopedStorage
Nope, problem still persist. @kenMarquez
Following my PR, I've released PlantNet app to 5% of the users for a few days resulting in no issue reported on this subject after 7 days (~500k users / day). I've increased the release to 50%, will update you next week.
Are you keeping requestLegacyExternalStorage on android 10 ? In my side, images from camera roll aren't able to show in FastImage & Image component if use your PR and remove requestLegacyExternalStorage on android 10
I'm using the PR for android 10 and +, and fallback to cameraroll current state for below. (not keeping requestLegacyExternalStorage which was only needed for android 10) To be fair, I'm not using the exacte PR as I've released the app before making the pr here, so it's using thoses exacte methods only for Q+:
if (isAndroidQAndAbove) {
// Google ask that the requestLegacyExternalStorage is no longer used when targeting android 11, and use
// the scoped storage or the new global permission, see https://gitlab.inria.fr/floristic/pn-mobile-test/-/issues/417
// Solution here, custom module which use the MediaStore API and copy the file to the DCIM folders.
const segments = path.split('/')
const fileName = segments[segments.length - 1]
const fileUriPath = await Module.moveToMediaStore(path.replace('file://', ''), fileName)
if (!fileUriPath) {
return null
}
await RNFS.scanFile(fileUriPath)
if (fileUriPath.startsWith('file:///')) {
return fileUriPath
}
return `file://${fileUriPath}`
}
@ReactMethod
public void moveToMediaStore(String filePath, String fileName, Promise promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
promise.resolve(null);
return;
}
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/PlantNet");
values.put(MediaStore.MediaColumns.IS_PENDING, 1);
ContentResolver resolver = getReactApplicationContext().getContentResolver();
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try {
OutputStream fos = resolver.openOutputStream(imageUri);
copy(new File(filePath), fos);
values.clear();
values.put(MediaStore.Images.Media.IS_PENDING, 0);
resolver.update(imageUri, values, null, null);
promise.resolve(getNameFromContentUri(getReactApplicationContext(), imageUri));
} catch (Exception e) {
e.printStackTrace();
promise.reject(e);
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public static void copy(File src, OutputStream out) throws IOException {
try (InputStream in = new FileInputStream(src)) {
FileUtils.copy(in, out);
}
}
// From https://stackoverflow.com/a/64359655/1377145
public static String getNameFromContentUri(Context context, Uri contentUri){
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(contentUri, null, null, null, null);
cursor.moveToFirst();
String document_id = cursor.getString(0);
document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
cursor.close();
cursor = contentResolver.query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
cursor.moveToFirst();
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
Can verify app update has been approved with target sdk 29 and with the flag requestLegacyExternalStorage
in place.
Will be waiting for the next release that solves the scoped storage on sdk 30
Hello guys, is this issue still be considering?
Hi, We are getting this error: https://stackoverflow.com/questions/68207334/getting-error-while-running-react-native-run-andorid-in-react-native and the solution to that is to upgrade to version 30.
Any Update on this topic?
According to @HugoGresse 's proposed PR, is it working with both getPhotos()
and save()
function?
I will be updating my production app soon.
Following changes made in my last comment, PlantNet app is doing great, already have 4M users in July with no complaint for this part. I cannot guaranty this is without any risk though.
I'm using the PR for android 10 and +, and fallback to cameraroll current state for below. (not keeping requestLegacyExternalStorage which was only needed for android 10) To be fair, I'm not using the exacte PR as I've released the app before making the pr here, so it's using thoses exacte methods only for Q+:
if (isAndroidQAndAbove) { // Google ask that the requestLegacyExternalStorage is no longer used when targeting android 11, and use // the scoped storage or the new global permission, see https://gitlab.inria.fr/floristic/pn-mobile-test/-/issues/417 // Solution here, custom module which use the MediaStore API and copy the file to the DCIM folders. const segments = path.split('/') const fileName = segments[segments.length - 1] const fileUriPath = await Module.moveToMediaStore(path.replace('file://', ''), fileName) if (!fileUriPath) { return null } await RNFS.scanFile(fileUriPath) if (fileUriPath.startsWith('file:///')) { return fileUriPath } return `file://${fileUriPath}` }
@ReactMethod public void moveToMediaStore(String filePath, String fileName, Promise promise) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { promise.resolve(null); return; } ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/PlantNet"); values.put(MediaStore.MediaColumns.IS_PENDING, 1); ContentResolver resolver = getReactApplicationContext().getContentResolver(); Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); try { OutputStream fos = resolver.openOutputStream(imageUri); copy(new File(filePath), fos); values.clear(); values.put(MediaStore.Images.Media.IS_PENDING, 0); resolver.update(imageUri, values, null, null); promise.resolve(getNameFromContentUri(getReactApplicationContext(), imageUri)); } catch (Exception e) { e.printStackTrace(); promise.reject(e); } } @RequiresApi(api = Build.VERSION_CODES.Q) public static void copy(File src, OutputStream out) throws IOException { try (InputStream in = new FileInputStream(src)) { FileUtils.copy(in, out); } } // From https://stackoverflow.com/a/64359655/1377145 public static String getNameFromContentUri(Context context, Uri contentUri){ ContentResolver contentResolver = context.getContentResolver(); Cursor cursor = contentResolver.query(contentUri, null, null, null, null); cursor.moveToFirst(); String document_id = cursor.getString(0); document_id = document_id.substring(document_id.lastIndexOf(":") + 1); cursor.close(); cursor = contentResolver.query( android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null); cursor.moveToFirst(); String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); cursor.close(); return path; }
@HugoGresse Can you provide more detail on where to add this code? Mind sharing the full code? Ie - how do you get isAndroidQAndAbove? I'm a javascript dev :)
It could be better if this repo was updated officially. So, we could stay updated as well as use the PR.
November is near
Do we have any confirmation from the maintainers/contributors that someone is working to get this library updated for both reading images and saving images?
@mikbry @Naturalclar @doranteseduardo
@MarcoScabbiolo
I'll try to get it ready for Android 11 if I can find the time
I've set targetSdkVersion = 30
and interestingly CameraRoll.save()
seems to be working for me on an Android 11 device with no changes. However, on an Android 10 device it only seems to work with android:requestLegacyExternalStorage="true"
still specified in my manifest. If I remove that, I get a permission denied error. I guess no harm leaving that. Is this expected? I'm not using getPhotos(), so I can't speak to the impact there.
Hello. I just received this message from Play Store.
Any info to solve this? Why not simply use the Storage Access Framework?
Thanks for your answers.