Android 11 sharing file between apps
Asked Answered
D

4

8

Android 11 had enforce some rules for storage, link refer : Storage updates in Android 11

Use Case : I have 2 applications, application A will write file (.txt) into external storage, application B will read the file from external storage without user interaction. But exception was thrown when read / write on Android 11, stated that permission was denied.

So I did some research and found that only MediaStore API and Storage Access Framework allow access files that created by other application, link refer : Data and file storage overview

But both methods are not suitable for my use case:

  • MediaStore API can only access Media files(images, audio files, videos)
  • Storage Access Framework need user interaction

So is there any other way that I can access non-media files on external storage that created by different apps on Android 11?

Despite all my researches, I didn't find a solution to my problem.

Thank you for your help.

Updates

I tried FileProvider but when I tried start activity, it always show error

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.testapp, PID: 20141 android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.app2.action.RECEIVE dat=content://com.example.testapp.fileprovider/myfiles/default_user.txt flg=0x1 } at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2067) at android.app.Instrumentation.execStartActivity(Instrumentation.java:1727) at android.app.Activity.startActivityForResult(Activity.java:5320)

This is how I start App 2 activity from App 1

File filePath = new File(getFilesDir(), "files");
File newFile = new File(filePath, "default_user.txt");
Intent intent = new Intent();
intent.setAction("com.example.app2.action.RECEIVE");
intent.setData(FileProvider.getUriForFile(this, "com.example.testapp.fileprovider", newFile));
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);

App 1 Manifest

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
    android:allowBackup="true"
    android:requestLegacyExternalStorage="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.TestApp">
    <activity android:name=".StorageActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity android:name=".MainActivity">
    </activity>

    <service
        android:name=".service.TestService"
        android:enabled="true"
        android:exported="true" />

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.example.testapp.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>
    
</application>

App 2 Manifest

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:requestLegacyExternalStorage="true"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.TestApp">
    <activity android:name=".ReceiverActivity">
        <intent-filter>
            <action android:name="com.example.app2.action.RECEIVE"/>

            <category android:name="android.intent.category.DEFAULT"/>
            <data android:scheme="content"
                android:host="com.example.testapp" />
        </intent-filter>
    </activity>
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>
Domiciliary answered 25/1, 2021 at 3:9 Comment(19)
Can you clarify if you wrote/control both App A and App B. Or is one of them written by a third-party and you have no influence/official API?Hieronymus
Hi @MorrisonChang, both applications are wrote/control by me, thanks!Domiciliary
See: ContentProvidersHieronymus
Or FileProvider and related training on sharing filesHieronymus
Hi @MorrisonChang, thanks for the info, I will try on it and let you know the result shortlyDomiciliary
Hi @MorrisonChang, I tested both FileProvider & ContentProviders, for ContentProviders, it seems like MediaStore API Access media files from shared storage, and for FileProviders, it always failed to start ActivityDomiciliary
I will upload code snippet for my FileProviders implementationDomiciliary
Reminder that you need to install & run App2 with the provider at least once before running App1 and making a data request.Hieronymus
Which app is giving that error? O i see. You cannot even start your second app. All has nothing to do with using a file provider or not.Kinch
This is how I start App 2 activity from App 1 You mean: This is how I can not start App 2 activity from App 1Kinch
Hi @MorrisonChang, I'm not understand, App2 need to have which provider to make a data request? I'm passing file from App1 to App2, not request file from App2Domiciliary
Hi @blackapps, the App 1 is giving that error, and you're correct, when I start App2 activity from App1, the error occurred, can you help me with this, I refer to explicit start activity, and I can start without setData() & setFlag()Domiciliary
You should first try to start just app2. After that an activity of app2. When you master all add an uri.Kinch
Hi @blackapps, I already mastered how to explicit start an activity of app2 from app1, without setData() & setFlag(), but after adding setData(Uri uri) & setFlag(), the app give errorDomiciliary
Put the uri as an extra on the intent.Kinch
Hi @blackapps, I put the intent as an extra and pass to app2, but it show FileNotFoundException when trying to open the inputStream by using getContentResolver().openInputStream(uriContent)Domiciliary
I cannot see what you do as you did not post your new code.Kinch
Your app2 does not need intent filters.Kinch
Read this: https://mcmap.net/q/89292/-android-11-scoped-storage-permissionsRare
K
0

You should directly start app2 using a launch intent for it.

                        try {
                            File file = new File( .... );
                            Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileprovider", file);

                            String apkPackage = "com.example.app2";

                            Intent intent = context.getPackageManager().getLaunchIntentForPackage(apkPackage);

                            if ( intent==null )
                            {
                                Toast.makeText(context, "Sorry, could not get launch intent for: " + apkPackage, Toast.LENGTH_LONG).show();

                                return;
                            }

                            intent.setAction(Intent.ACTION_VIEW);
                            intent.setDataAndType(uri, mimeType);

                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

                            context.startActivity(intent);
                        }
                        catch ( IllegalArgumentException e)
                        {
                            e.printStackTrace();
                            Toast.makeText(context, "IllegalArgumentException: " + e.getMessage(), Toast.LENGTH_LONG).show();

                        }
                        catch ( Exception e)
                        {
                            e.printStackTrace();

                            Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
                        }

You do not need intent-filters in manifest of app2.

The receiving side can get the uri with:

Uri uri = getIntent().getData();
Kinch answered 26/1, 2021 at 11:3 Comment(10)
This modality it's auto or by user confirm action?Radioscopy
I have no idea what you ask.Kinch
The launch of the intent via the FLAG_GRANT_READ_URI_PERMISSION flag will it also generate the opening of a "confirmation" window on which the user must "enable" the operation or everything happens automatically, in a "silent" way? .... I'm interested because in my case the There are many files to read (it is a restore / backup managerial data) and I would not like to overwhelm the poor user with "confirmations" ... the more I document about these new Android 11 restrictions. ..to read a file, not even in 1984 was that cumbersome, I hope they take it back on all of this.Radioscopy
App2 is launched. That flag will generate nothing. The user has to confirm nothing. And the rest app2 will do is out of control of app1.Kinch
When on app2 you go to use it that file you passed as path? Will it let it use?will you be able to read and write the file it refers to?Radioscopy
I think so as my answer is accepted. But why do you ask? Test it yourself i would say. Why didnt you already?Kinch
But I don't think so ... if the android restrictions (sdk30) documentation is correct this thing should work only for some types of files (media) .... you will not be able to read a file created by the other app in the external directories .... if it works then what should the restrictions be for? ... I can't do "tests" to decide how to move on very complex apps, I have to read up a lot, and that's why I ask questions about these aspects. thank you.Radioscopy
This is not to compare with reading a file from another app. This is just reading content delivered by a provider. FileProvider is used. And app2 knows nothing about the file behind the uri. Using providers is to overcome those restrictions. To test this you only need to program one small app2. The intent to start app2 you can temporally add to any app you already made.Kinch
If app2 tried to read a file of app1 (without app1 knowing that) then the action started with app2 and will not go. But using a file provider app1 tells app2: here is my file, read it. And not a path is used but an uri.Kinch
OK, this is an aspect that interests me ... I had never fully understood the difference in the use of "provider" I had limited myself to adding the part in the manifest to maintain the functioning that I had always adopted up to now, such as was a "fix" (android 4,5,6,7,8,9) could you explain yourself better? So is it profoundly different? I'm saying that then that "provider" specified in the manifest only affects files used via "uri"?Radioscopy
I
1
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {


    PDFiumHelper mPdFiumHelper;
    private void copyAssets() {
        AssetManager assetManager = getAssets();
        String[] files = null;
        try {
            files = assetManager.list("");
        } catch (IOException e) {
            Log.e("tag", "Failed to get asset file list.", e);
        }
        for (String filename : files) {
            InputStream in = null;
            OutputStream out = null;
            try {
                in = assetManager.open(filename);

                String outDir = getFilesDir().getAbsolutePath();

                File outFile = new File(outDir, filename);

                out = new FileOutputStream(outFile);
                copyFile(in, out);
                in.close();
                in = null;
                out.flush();
                out.close();
                out = null;
            } catch (IOException e) {
                Log.e("tag", "Failed to copy asset file: " + filename, e);
            }
        }
    }

    private void copyFile(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        copyAssets();
        mPdFiumHelper = new PDFiumHelper(this);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
       return mPdFiumHelper.onKeyDown(keyCode,event);
    }
}

Helper class

import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.FileProvider;

import com.github.barteksc.pdfviewer.PDFView;
import com.github.barteksc.pdfviewer.listener.OnErrorListener;
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener;
import com.github.barteksc.pdfviewer.util.FitPolicy;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;

import java.io.File;
import java.util.Calendar;
import java.util.List;

public class PDFiumHelper implements MultiplePermissionsListener {

    AppCompatActivity mAppCompatActivity;

    public PDFiumHelper(AppCompatActivity mAppCompatActivity) {
        this.mAppCompatActivity = mAppCompatActivity;
        onCreate();
    }
    public PDFiumHelper() {
    }
    Button btnFile1, btnFile2;

    long firstTime;
    PDFView pdfView;
    CommonUtility mCommonUtility;
    boolean hasPermissions;
    FloatingActionButton ibtn_share;
    File localPDFFile;
    String pdfTitle = "Mathematics Paper 2020";
    String pdfExtraText = "this paper is made using the quantum paper.";
    ConstraintLayout root;
    String path1,path2;
    protected void onCreate() {

        path1 = mAppCompatActivity.getFilesDir() + "/SatsangDiksha.pdf";
        path2 = mAppCompatActivity.getFilesDir() + "/sample.pdf";

        btnFile1 = (Button) mAppCompatActivity.findViewById(R.id.btnFile1);
        btnFile2 = (Button) mAppCompatActivity.findViewById(R.id.btnFile2);

        root = mAppCompatActivity.findViewById(R.id.root);

        pdfView = new PDFView(mAppCompatActivity, null);
        pdfView.setLayoutParams(new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        root.addView(pdfView);

        ibtn_share = new FloatingActionButton(mAppCompatActivity);
        ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        int margin = convertDpToPixel(20);
        params.setMargins(margin, margin, margin, margin);
        //this will constrain FAB programmatically as like XML.
        params.bottomToBottom = root.getId();
        params.endToEnd = root.getId();
        ibtn_share.setLayoutParams(params);
        //change FAB icon here
        ibtn_share.setImageResource(R.drawable.ic_share);
        //change background color of FAB here
        ibtn_share.setBackgroundTintList(ColorStateList.valueOf(mAppCompatActivity.getResources().getColor(R.color.purple_500)));
        //FAB icon color can be change from here
        ibtn_share.setColorFilter(Color.WHITE);

        root.addView(ibtn_share);


        btnFile1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadPdfFromFile(path1);
            }
        });
        btnFile2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadPdfFromFile(path2);
            }
        });


        mCommonUtility = new CommonUtility(mAppCompatActivity);
        if (hasPermissions) {
        } else {
            mCommonUtility.askForPermissionBeforeStart(this);
        }

        ibtn_share.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (hasPermissions) {
                    shareFile();
                } else {
                    mCommonUtility.askForPermissionBeforeStart(PDFiumHelper.this);
                }
            }
        });

    }

    boolean flag = false;

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_DPAD_LEFT)) {
            if (flag) {
                loadPdfFromFile(path1);
                flag = false;
            } else {
                loadPdfFromFile(path2);
                flag = true;
            }
//            Toast.makeText(MainActivity.this, "Left Arrow Pressed", Toast.LENGTH_SHORT).show();
            return false;
        }
        return false;
    }

    public void shareFile() {
        Intent intentShareFile = new Intent(Intent.ACTION_SEND);
        if (localPDFFile.exists()) {
            intentShareFile.setType("application/pdf");
            intentShareFile.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(mAppCompatActivity, BuildConfig.APPLICATION_ID + ".provider", localPDFFile));
            intentShareFile.putExtra(Intent.EXTRA_SUBJECT,
                    pdfTitle);
            intentShareFile.putExtra(Intent.EXTRA_TEXT, pdfExtraText);
            mAppCompatActivity.startActivity(Intent.createChooser(intentShareFile, "Share File using"));
        } else {
            Toast.makeText(mAppCompatActivity, "File not loaded.", Toast.LENGTH_SHORT).show();
        }
    }

    public long showCurrentTime() {
        Calendar c = Calendar.getInstance();
        int hours = c.get(Calendar.HOUR);
        int minutes = c.get(Calendar.MINUTE);
        int seconds = c.get(Calendar.SECOND);
        int mseconds = c.get(Calendar.MILLISECOND);
//        Log.e(TAG, "showCurrentTime: Current Time :- " + hours + ":" + minutes + ":" + seconds + ":" + mseconds);
        return System.currentTimeMillis();
    }


    public void loadPdfFromFile(String path) {
        firstTime = showCurrentTime();
        try {
            localPDFFile = new File(path);
            pdfView.fromFile(localPDFFile)
                    .enableAntialiasing(true)
                    .enableDoubletap(true)
                    .onLoad(new OnLoadCompleteListener() {
                        @Override
                        public void loadComplete(int nbPages) {
                            double timee = firstTime - showCurrentTime();
                            double ms = Math.abs(timee) / 1000;
                            Log.e("===", "onDocumentLoaded: Time Taken :- " + ms + " Seconds");
                            Toast.makeText(mAppCompatActivity, ms + " Seconds", Toast.LENGTH_SHORT).show();
                        }
                    })
                    .onError(new OnErrorListener() {
                        @Override
                        public void onError(Throwable t) {
                            t.printStackTrace();
                            Toast.makeText(mAppCompatActivity, "Load some file.", Toast.LENGTH_SHORT).show();
                        }
                    })
                    .scrollHandle(new DefaultScrollHandle(mAppCompatActivity))
                    .pageFitPolicy(FitPolicy.WIDTH)
                    .autoSpacing(false)
                    .load();
        } catch (Throwable th) {

//            th.printStackTrace();
        }
    }

    @Override
    public void onPermissionsChecked(MultiplePermissionsReport multiplePermissionsReport) {
        // check if all permissions are granted
        if (multiplePermissionsReport.areAllPermissionsGranted()) {
            // do work
            hasPermissions = true;
            if (hasPermissions) {
            }
        }
        if (multiplePermissionsReport.isAnyPermissionPermanentlyDenied()) {
            // permission is denied permenantly, navigate user to app settings
            mCommonUtility.showSettingsDialog();
        }
    }

    @Override
    public void onPermissionRationaleShouldBeShown(List<PermissionRequest> list, PermissionToken permissionToken) {
        mCommonUtility.showPermissionRationale(permissionToken);
    }

    public int convertDpToPixel(int dp) {
        return dp * (mAppCompatActivity.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
    }

    public int convertPixelsToDp(int px) {
        return px / (mAppCompatActivity.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
    }

}

Provider paths file.

<?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="external"
            path="." />
        <external-files-path
            name="external_files"
            path="." />
        <cache-path
            name="cache"
            path="." />
        <external-cache-path
            name="external_cache"
            path="." />
        <files-path
            name="files"
            path="." />
    </paths>

Android Manifest File.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.qp.pdfiumproject">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.PdfiumProject">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>

    </application>

</manifest>
Importune answered 16/8, 2021 at 4:56 Comment(0)
U
1

With Android 11, each app can only read and write files in its own private directories. This applies for both Java File class and native code. These are the methods I currently know to work around this restriction:

  1. You can use SAF for all file accesses. In some cases this is not possible, e.g. when using SQLite. SQLite only accepts datatype File, not DocumentFile.
  2. You can use SAF to copy all files you need to your private directories and access them there using File or native code. If you want to change them, you must afterwards use SAF to copy them back to their original location.
  3. You can ask for MANAGE_EXTERNAL_STORAGE permission (why "external"?!?). In that case you cannot publish your application in Play Store.
  4. You can use target SDK Android 10. In that case you cannot publish your application in Play Store.
  5. You can make Android believe that your files (maybe "bla.txt" and "labre.db") are media files. Rename them to "bla.txt.m4a" and "labre.db.jpg". As side effect your media databases will be polluted with unwanted entries, i.e. bad audio and bad pictures, which may inadvertently appear in picture or music albums.

In my opinion Google could have find a better solution that does not cause so much work for developers. BTW: SAF is extreeeeemly slooooow, even when just transferring raw data.

Unsightly answered 19/1, 2022 at 23:7 Comment(4)
Thank you for an overview based on hands-on experience. If you could add something else years later, please extend your answer.Exscind
Since Android 13 write and read permission become deprecated and replaced with more granular ones. developer.android.com/about/versions/13/…Exscind
It seems that Android 13 makes another step into the wrong direction.Unsightly
I don't think so in terms of general platform growth, they made a good improvement when started to allow 3rd party apps to have access to specific photos instead of to a whole media dataset. I do not like too this fragmentation across API versions and overal storage API become cumbersome, they have made attempt to fix it github.com/google/modernstorage, but this library is still in alpha and work had stopped a more than year ago. Using it without ongoing maintenance in production doesn't worth it, in my opinion.Exscind
K
0

You should directly start app2 using a launch intent for it.

                        try {
                            File file = new File( .... );
                            Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileprovider", file);

                            String apkPackage = "com.example.app2";

                            Intent intent = context.getPackageManager().getLaunchIntentForPackage(apkPackage);

                            if ( intent==null )
                            {
                                Toast.makeText(context, "Sorry, could not get launch intent for: " + apkPackage, Toast.LENGTH_LONG).show();

                                return;
                            }

                            intent.setAction(Intent.ACTION_VIEW);
                            intent.setDataAndType(uri, mimeType);

                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

                            context.startActivity(intent);
                        }
                        catch ( IllegalArgumentException e)
                        {
                            e.printStackTrace();
                            Toast.makeText(context, "IllegalArgumentException: " + e.getMessage(), Toast.LENGTH_LONG).show();

                        }
                        catch ( Exception e)
                        {
                            e.printStackTrace();

                            Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
                        }

You do not need intent-filters in manifest of app2.

The receiving side can get the uri with:

Uri uri = getIntent().getData();
Kinch answered 26/1, 2021 at 11:3 Comment(10)
This modality it's auto or by user confirm action?Radioscopy
I have no idea what you ask.Kinch
The launch of the intent via the FLAG_GRANT_READ_URI_PERMISSION flag will it also generate the opening of a "confirmation" window on which the user must "enable" the operation or everything happens automatically, in a "silent" way? .... I'm interested because in my case the There are many files to read (it is a restore / backup managerial data) and I would not like to overwhelm the poor user with "confirmations" ... the more I document about these new Android 11 restrictions. ..to read a file, not even in 1984 was that cumbersome, I hope they take it back on all of this.Radioscopy
App2 is launched. That flag will generate nothing. The user has to confirm nothing. And the rest app2 will do is out of control of app1.Kinch
When on app2 you go to use it that file you passed as path? Will it let it use?will you be able to read and write the file it refers to?Radioscopy
I think so as my answer is accepted. But why do you ask? Test it yourself i would say. Why didnt you already?Kinch
But I don't think so ... if the android restrictions (sdk30) documentation is correct this thing should work only for some types of files (media) .... you will not be able to read a file created by the other app in the external directories .... if it works then what should the restrictions be for? ... I can't do "tests" to decide how to move on very complex apps, I have to read up a lot, and that's why I ask questions about these aspects. thank you.Radioscopy
This is not to compare with reading a file from another app. This is just reading content delivered by a provider. FileProvider is used. And app2 knows nothing about the file behind the uri. Using providers is to overcome those restrictions. To test this you only need to program one small app2. The intent to start app2 you can temporally add to any app you already made.Kinch
If app2 tried to read a file of app1 (without app1 knowing that) then the action started with app2 and will not go. But using a file provider app1 tells app2: here is my file, read it. And not a path is used but an uri.Kinch
OK, this is an aspect that interests me ... I had never fully understood the difference in the use of "provider" I had limited myself to adding the part in the manifest to maintain the functioning that I had always adopted up to now, such as was a "fix" (android 4,5,6,7,8,9) could you explain yourself better? So is it profoundly different? I'm saying that then that "provider" specified in the manifest only affects files used via "uri"?Radioscopy
E
0

I have been working passed week on backup & restore feature for my app and seems is the only safe&robust option is to use SAF with ACTION_CREATE_DOCUMENT and ACTION_OPEN_DOCUMENT across all API levels since Android 5 and until Android 13.

Fragmentation across API versions for operations with external storage become a serious issue.

Storage Access Framework is the only tool on the latest android versions except media files cases.

Since Android 30 you don't need a write permission to write into a public folder

At least since Android 30 (and it was told since Android Kitkat 4.4) you can not delete file in public folder created by different app. (which also means if user delete and reinstall app again, its files in public folders do not belong to this app anymore)

Until Android 30 you still need request runtime permission to write a file to a public folder, but on Android 29 it is not even enough - app has permission, but it still can not write a file

Default download app doesn't show files added by your app mostly on all android versions. 3rd party file explorer show this files, but at least on Android 30 and Android 33 you can choose it as an option after intent launch

val intent  = Intent(Intent.ACTION_OPEN_DOCUMENT) 
intent.addCategory(Intent.CATEGORY_OPENABLE)

One experienced engineer recommends to use only Storage Access Framework: https://mcmap.net/q/1472901/-file-saved-to-download-folder-doesn-39-t-show-up-in-quot-downloads-quot-app

Google team has made attempt to fix fragmentation issues https://github.com/google/modernstorage, but this library is still in alpha and work had stopped a more than an year ago. Using it without ongoing maintenance in production doesn't worth it, in my opinion.

Exscind answered 31/7, 2023 at 12:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.