Android SecurityException accesing content

3.8k Views Asked by At

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.

2

There are 2 best solutions below

3
Prem Thakur On

Due to security reasons Android restricted storage permission.

From Android Documentation

If your app targets Android 11, both the WRITE_EXTERNAL_STORAGE permission and the WRITE_MEDIA_STORAGE privileged permission no longer provide any additional access.

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

On devices that run Android 10 or higher, you don't need any storage-related permissions to access and modify media files that your app owns, including files in the MediaStore.Downloads collection. If you're developing a camera app, for example, you don't need to request storage-related permissions because your app owns the images that you're writing to the media store.

Access other apps' media files

To access media files that other apps have created, you must declare the appropriate storage-related permissions, and the files must reside in one of the following media collections

To access only media files you should checkout: Access media files from shared storage

Request All files access

An app can request All files access from the user by doing the following: Declare the MANAGE_EXTERNAL_STORAGE permission in the manifest. Use the ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent action to direct users to a system settings page where they can enable the following option for your app: Allow access to manage all files. To determine whether your app has been granted the MANAGE_EXTERNAL_STORAGE permission, call Environment.isExternalStorageManager().

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

0
Learn OpenGL ES On

There's an issue on some Samsung devices and I've seen this happening with my app in production.

A possible fix is to ask your users to do the following:

  1. Go into Settings in your phone and search for "All files access".
  2. Open it and then tap the 3-dot menu in the top-right corner, and tap "Show System".
  3. Then, find and tap "External Storage" and make sure "Allow access to manage all files" is enabled."

Reference: https://issuetracker.google.com/issues/258270138