Android SecurityException accesing content
Asked Answered
N

2

6

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.

Neuralgia answered 23/11, 2022 at 12:52 Comment(8)
managePickedFile(iData); We dont see how you try to read your file. I suppose the exceptions are thrown there. Further you do not need any permission in manifest or request them at runtime to use ACTION_OPEN_DOCUMENT and ACTION_GET_CONTENT. So please remove all that code as it is irrelevant.Toodleoo
copies it into the app's internal storage Depending on what you consider to be apps internal storage you might need write permission (manifest and such). Please tell. But anyhow we will gladly assume that you got the permission to write to storage so it is still not necessary to post all that permission code.Toodleoo
com.android.providers.downloads has no access to content://media/external_primary/file/1000000454 Pretty strange how two providers get mixed up. You should post the -relevant- code that produces that error.Toodleoo
Yes, sorry, I've realized I didn't add the relevant code. Now I've edited the question to show the code to read and copy the file.Neuralgia
The exception is not thrown by getUriName()? I dont know that function. Please post code.Toodleoo
I've added getUriName method to the post, but the crash happens in the openInputStream call.Neuralgia
Well i'm baffled. No idea why two providers are mixed. Sorry, dont know how to help you.Toodleoo
Please did you find solution? I face exactly the same problem. ContentResolver.openInputStream seems to be bugged or I dont know. It throws no access to content which was created by the same application which created it. Od course the folder has granted SAF permissions.Socinian
T
3

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

Teakettle answered 30/1, 2023 at 15:54 Comment(0)
P
-2

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

Pokorny answered 23/11, 2022 at 19:33 Comment(3)
My app supports txt and csv files and they can be stored anywhere (the user could have downloaded them into any folder, or could have stored them in GDrive, etc...). That's why I'm using ACTION_OPEN_DOCUMENT, to show a file picker so the user can browse to the file (wherever it is) and open it. For example, the file could be stored in the Download folder of the device. Does this affect your answer?Neuralgia
By the way, my app targets android 11.Neuralgia
@Wonton, neglect this answer. It has nothing to do with your problem.Toodleoo

© 2022 - 2024 — McMap. All rights reserved.