I've developed an Android app that reads a file from the device, copies it into the app's internal storage and analyzes it. It has been working OK for almost 100% of my users/devices, but since a couple of months ago, for some specific users/devices is crashing reading the file.
This is how I request permissions.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mydomain.myapp" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
...
On MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUESTS);
}
}
...
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSIONS_REQUESTS) {
if ((grantResults.length == 0) || (grantResults[0] != PackageManager.PERMISSION_GRANTED)) {
if (ActivityCompat.shouldShowRequestPermissionRationale(ContainerActivity.this, permission.WRITE_EXTERNAL_STORAGE)) {
new Builder(this)
.setCancelable(false)
.setTitle("")
.setMessage(getResources().getString(R.string.REQUEST_WRITE_PERMISSION))
.setPositiveButton(getResources().getString(R.string.TXT_OK_BT), (dialog, which) -> ActivityCompat.requestPermissions(ContainerActivity.this, new String[]{permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUESTS))
.setNegativeButton(getResources().getString(R.string.TXT_DENY_BT), (dialog, which) -> finish())
.show();
} else {
finish();
}
}
}
}
To read the file I'm doing this in my ProcessFileFragment.java file:
private ActivityResultLauncher<Intent> filePickerLauncher;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
createFilePickerLauncher();
...
}
private void createFilePickerLauncher() {
filePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent iData = result.getData();
managePickedFile(iData);
}
});
}
private void goToFilePicker() {
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
} else {
intent = new Intent(Intent.ACTION_GET_CONTENT);
}
intent.setType("*/*");
filePickerLauncher.launch(intent);
}
private void managePickedFile(Intent iData) {
Uri sourceFileUri = iData.getData();
new CopyFileTask(ctxt, sourceFileUri).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private class CopyFileTask extends AsyncTask<Void, Float, String> {
private final WeakReference<Context> ctxtRef;
private final Uri fileUri;
public CopyFileTask(Context context, Uri fileUri) {
this.ctxtRef = new WeakReference<>(context);
this.fileUri = fileUri;
}
@Override
protected void onPreExecute() {
}
@Override
protected String doInBackground(Void... params) {
String destinationPath = "";
Context ctxt = ctxtRef.get();
if(ctxt != null) {
try {
destinationPath = copyFile(ctxt, fileUri);
} catch (IOException e) {
} catch (IllegalStateException e) {
}
}
return destinationPath;
}
@Override
protected void onProgressUpdate(Float... values) {
}
@Override
protected void onPostExecute(String result) {
// File copied successfully
}
@Override
protected void onCancelled() {
}
}
public static String copyFile(Context ctxt, Uri sourceFileUri) throws IOException {
InputStream in = ctxt.getContentResolver().openInputStream(sourceFileUri);
String destinationPath = ctxt.getFilesDir() + "/" + getUriName(ctxt, sourceFileUri);
OutputStream out = new FileOutputStream(destinationPath);
Log.e("MYAPP", "Copying files from "+sourceFileUri.getPath()+" to "+destinationPath);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
if (in != null) { in.close(); }
if (out != null){ out.close(); }
return destinationPath;
}
public static String getUriName(Context ctxt, Uri uri) {
String[] projection = { OpenableColumns.DISPLAY_NAME };
Cursor returnCursor = ctxt.getContentResolver().query(uri, projection, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String name = returnCursor.getString(nameIndex);
returnCursor.close();
return name;
}
The crashes are in this line:
InputStream in = ctxt.getContentResolver().openInputStream(sourceFileUri);
And these are some crashes reading the file:
- java.lang.SecurityException: com.android.providers.downloads has no access to content://media/external_primary/file/1000000454
- java.lang.SecurityException: com.samsung.android.providers.media has no access to content://media/external_primary/file/1000001204
- java.lang.SecurityException: com.android.externalstorage has no access to content://media/4756-1ac1/file/4632
According to Crashlytics, app has crashed 46 times to 5 users with this distribution:
Devices:
- 54% Samsung Galaxy S22 Ultra
- 24% Coosea DEMK4119
- 11% Samsung Galaxy S22
- 11% Samsung Galaxy S10+
Android OS:
- 67% Android 12
- 33% Android 13
I'm testing with different devices, specially with a Samsung Galaxy A51 and I'm having no problems, so it's difficult to know what is happening.
As far as I know, declaring WRITE_EXTERNAL_PERMISSION is not necessary to declare READ_EXTERNAL_PERMISSION, and after reading several posts similar to this I don't have any clue to what could I test. Any help would be very appreciated.
Due to security reasons Android restricted storage permission.
From Android Documentation
Target Android 11
Now you can't just copy paste thing anywhere you want. Now, the media files should be stored in their respective dirs. For example images should be stored in the Storage > Pictures dir.
No permissions needed if you only access your own media files
Access other apps' media files
To access only media files you should checkout: Access media files from shared storage
Request All files access
If your app is a file manager type app then only you can get the permission to manage all the files. Read more here Manage all files on a storage device