web-dev-qa-db-de.com

Wie verwende ich den Support-FileProvider, um Inhalte für andere Apps freizugeben?

Ich suche nach einer Möglichkeit, eine interne Datei korrekt mit einer externen Anwendung zu teilen (nicht zu öffnen), indem ich Android Support Library's FileProvider verwende.

Dem Beispiel in den Dokumenten folgend,

<provider
    Android:name="Android.support.v4.content.FileProvider"
    Android:authorities="com.example.Android.supportv4.my_files"
    Android:grantUriPermissions="true"
    Android:exported="false">
    <meta-data
        Android:name="Android.support.FILE_PROVIDER_PATHS"
        Android:resource="@xml/my_paths" />
</provider>

und mit ShareCompat wie folgt eine Datei für andere Apps freigeben:

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

funktioniert nicht, da FLAG_GRANT_READ_URI_PERMISSION nur die Erlaubnis für den Uri erteilt, der auf dem data der Absicht angegeben ist, nicht für den Wert des EXTRA_STREAM extra (wie von setStream gesetzt).

Ich habe versucht, die Sicherheit zu gefährden, indem ich Android:exported to true für den Provider, aber FileProvider prüft intern, ob er selbst exportiert wird. In diesem Fall wird eine Ausnahme ausgelöst.

123

Wenn Sie FileProvider aus der Support-Bibliothek verwenden, müssen Sie die Berechtigungen (zur Laufzeit) für andere Apps manuell erteilen und entziehen, um bestimmte Uri-Dateien lesen zu können. Verwenden Sie die Methoden Context.grantUriPermission und Context.revokeUriPermission .

Beispielsweise:

//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

Wenn Sie als letzte Möglichkeit keinen Paketnamen angeben können, können Sie die Berechtigung allen Apps erteilen, die bestimmte Absichten verarbeiten können:

//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

Alternative Methode gemäß Dokumentation :

  • Fügen Sie den Inhalts-URI in einen Intent ein, indem Sie setData () aufrufen.
  • Rufen Sie als Nächstes die Methode Intent.setFlags () mit entweder FLAG_GRANT_READ_URI_PERMISSION oder FLAG_GRANT_WRITE_URI_PERMISSION oder beiden auf.
  • Senden Sie die Absicht schließlich an eine andere App. Am häufigsten rufen Sie dazu setResult () auf.

    Berechtigungen, die in einer Absicht erteilt wurden, bleiben wirksam, solange der Stapel der empfangenden Aktivität aktiv ist. Wenn der Stapel fertig ist, wird die
    Berechtigungen werden automatisch entfernt. Berechtigungen, die einem erteilt wurden
    Aktivitäten in einer Client-App werden automatisch auf andere erweitert
    Komponenten dieser App.

Btw. Bei Bedarf können Sie Quelle von FileProvider kopieren und die Methode attachInfo ändern, um zu verhindern, dass der Anbieter überprüft, ob er exportiert wird.

141
Leszek

Diese Lösung funktioniert bei mir seit OS 4.4. Damit es auf allen Geräten funktioniert, habe ich eine Problemumgehung für ältere Geräte hinzugefügt. Dies stellt sicher, dass immer die sicherste Lösung verwendet wird.

Manifest.xml:

    <provider
        Android:name="Android.support.v4.content.FileProvider"
        Android:authorities="com.package.name.fileprovider"
        Android:exported="false"
        Android:grantUriPermissions="true">
        <meta-data
            Android:name="Android.support.FILE_PROVIDER_PATHS"
            Android:resource="@xml/file_paths" />
    </provider>

file_paths.xml:

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

Java:

public static void sendFile(Context context) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    String dirpath = context.getFilesDir() + File.separator + "directory";
    File file = new File(dirpath + File.separator + "file.txt");
    Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // Workaround for Android bug.
    // grantUriPermission also needed for KitKat,
    // see https://code.google.com/p/Android/issues/detail?id=76683
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KitKat) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }
    if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
    }
}

public static void revokeFileReadPermission(Context context) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KitKat) {
        String dirpath = context.getFilesDir() + File.separator + "directory";
        File file = new File(dirpath + File.separator + "file.txt");
        Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
        context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

Die Berechtigung wird mit revokeFileReadPermission () in den Methoden onResume und onDestroy () des Fragments oder der Activity widerrufen.

23
Oliver Kranz

Voll funktionsfähiges Codebeispiel zum Freigeben von Dateien aus dem inneren App-Ordner. Getestet auf Android 7 und Android 5.

AndroidManifest.xml

</application>
   ....
    <provider
        Android:name="Android.support.v4.content.FileProvider"
        Android:authorities="Android.getqardio.com.gmslocationtest"
        Android:exported="false"
        Android:grantUriPermissions="true">
        <meta-data
            Android:name="Android.support.FILE_PROVIDER_PATHS"
            Android:resource="@xml/provider_paths"/>
    </provider>
</application>

xml/provider_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="share"
        path="external_files"/>
</paths>

Code selbst

    File imagePath = new File(getFilesDir(), "external_files");
    imagePath.mkdir();
    File imageFile = new File(imagePath.getPath(), "test.jpg");

    // Write data in your file

    Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);

    Intent intent = ShareCompat.IntentBuilder.from(this)
                .setStream(uri) // uri from FileProvider
                .setType("text/html")
                .getIntent()
                .setAction(Intent.ACTION_VIEW) //Change if needed
                .setDataAndType(uri, "image/*")
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

   startActivity(intent);
23
Divers

Da dies, wie Phil in seinem Kommentar zur ursprünglichen Frage sagt, einzigartig ist und es keine weiteren Informationen zu SO in Google gibt, dachte ich, ich sollte auch meine Ergebnisse teilen:

In meiner App hat FileProvider sofort funktioniert, um Dateien mit der Freigabeabsicht freizugeben. Darüber hinaus war keine spezielle Konfiguration oder Code erforderlich, um den FileProvider einzurichten. In meiner manifest.xml habe ich Folgendes platziert:

    <provider
        Android:name="Android.support.v4.content.FileProvider"
        Android:authorities="com.my.apps.package.files"
        Android:exported="false"
        Android:grantUriPermissions="true" >
        <meta-data
            Android:name="Android.support.FILE_PROVIDER_PATHS"
            Android:resource="@xml/my_paths" />
    </provider>

In my_paths.xml habe ich:

<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <files-path name="files" path="." />
</paths>

In meinem Code habe ich:

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setType("application/xml");

    Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

    startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));

Und ich kann meine im privaten Speicher meiner Apps gespeicherten Dateien problemlos für Apps wie Google Mail und Google Drive freigeben.

15
Luke Sleeman

Soweit ich weiß, funktioniert dies nur mit neueren Versionen von Android, daher müssen Sie wahrscheinlich einen anderen Weg finden, dies zu tun. Diese Lösung funktioniert für mich mit Version 4.4, jedoch nicht mit Version 4.0 oder 2.3.3. Daher ist dies kein nützlicher Weg, um Inhalte für eine App freizugeben, die auf einem Android Gerät) ausgeführt werden soll .

In manifest.xml:

<provider
    Android:name="Android.support.v4.content.FileProvider"
    Android:authorities="com.mydomain.myapp.SharingActivity"
    Android:exported="false"
    Android:grantUriPermissions="true">
    <meta-data
        Android:name="Android.support.FILE_PROVIDER_PATHS"
        Android:resource="@xml/file_paths" />
</provider>

Beachten Sie genau, wie Sie die Behörden angeben. Sie müssen die Aktivität angeben, aus der Sie den URI erstellen und die Freigabeabsicht starten möchten. In diesem Fall heißt die Aktivität SharingActivity. Diese Anforderung ist aus den Google-Dokumenten nicht ersichtlich!

file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <files-path name="just_a_name" path=""/>
</paths>

Achten Sie darauf, wie Sie den Pfad angeben. Die oben genannten Einstellungen beziehen sich standardmäßig auf das Stammverzeichnis Ihres privaten internen Speichers.

In SharingActivity.Java:

Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));

In diesem Beispiel teilen wir ein JPEG-Bild.

Zum Schluss ist es wahrscheinlich eine gute Idee, sich zu vergewissern, dass Sie die Datei ordnungsgemäß gespeichert haben und auf sie folgendermaßen zugreifen können:

File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
    Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
    Log.w(TAG, "File not found!");
}
14
Rasmusob

In meiner App funktioniert FileProvider einwandfrei und ich kann interne Dateien, die im Dateiverzeichnis gespeichert sind, an E-Mail-Clients wie Google Mail, Yahoo usw. anhängen.

In meinem Manifest, wie in der Android Dokumentation erwähnt, habe ich Folgendes platziert:

<provider
        Android:name="Android.support.v4.content.FileProvider"
        Android:authorities="com.package.name.fileprovider"
        Android:grantUriPermissions="true"
        Android:exported="false">
        <meta-data
            Android:name="Android.support.FILE_PROVIDER_PATHS"
            Android:resource="@xml/filepaths" />
    </provider>

Und da meine Dateien im Stammverzeichnis gespeichert wurden, lautete die Datei "filepaths.xml" wie folgt:

 <paths>
<files-path path="." name="name" />

Jetzt im Code:

 File file=new File(context.getFilesDir(),"test.txt");

 Intent shareIntent = new Intent(Android.content.Intent.ACTION_SEND_MULTIPLE);

 shareIntent.putExtra(Android.content.Intent.EXTRA_SUBJECT,
                                     "Test");

 shareIntent.setType("text/plain");

 shareIntent.putExtra(Android.content.Intent.EXTRA_EMAIL,
                                 new String[] {"email-address you want to send the file to"});

   Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
                                                   file);

                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);

                shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
                                                        uris);


                try {
                   context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));                                                      


                }
                catch(ActivityNotFoundException e) {
                    Toast.makeText(context,
                                   "Sorry No email Application was found",
                                   Toast.LENGTH_SHORT).show();
                }
            }

Das hat bei mir funktioniert. Hoffentlich hilft das :)

7
Adarsh Chithran

Wenn Sie ein Bild von der Kamera erhalten, funktioniert keine dieser Lösungen für Android 4.4. In diesem Fall ist es besser, die Versionen zu überprüfen.

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Lollipop) {
        uri = Uri.fromFile(file);
    } else {
        uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST);
}
2
CoolMind

Ich möchte etwas freigeben, das uns für ein paar Tage blockiert hat: Der Dateiprovider-Code MUSS zwischen den Anwendungs-Tags eingefügt werden, nicht danach. Es mag trivial sein, aber es wurde nie spezifiziert, und ich dachte, ich hätte jemandem helfen können! (Nochmals vielen Dank an piolo94)

1
Eric Draven

nur um die oben angegebene Antwort zu verbessern: Wenn Sie NullPointerEx erhalten:

sie können getApplicationContext () auch ohne Kontext verwenden

                List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
1
Deepak Singh