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.