web-dev-qa-db-ja.com

他のアプリとコンテンツを共有するためにサポートFileProviderを使用する方法は?

Androidサポートライブラリの FileProvider を使用して、外部ファイルと内部ファイルを正しく(OPENではなく)共有する方法を探しています。

ドキュメントの例に従って、

<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>

shareCompatを使用して、次のように他のアプリとファイルを共有します。

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

fLAG_GRANT_READ_URI_PERMISSIONは、EXTRA_STREAM extraの値ではなく、dataに指定されたUriに対する許可のみを許可するため、機能しません(setStreamによって設定されたように)。

プロバイダーのAndroid:exportedtrueに設定することでセキュリティを侵害しようとしましたが、FileProviderがそれ自体がエクスポートされているかどうかを内部で確認し、エクスポートされると例外をスローします。

120

サポートライブラリからFileProviderを使用すると、特定のUriを読み取るために、他のアプリのアクセス許可を(実行時に)手動で付与および取り消しする必要があります。 Context.grantUriPermission および Context.revokeUriPermission メソッドを使用します。

例えば:

//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);

最後の手段として、パッケージ名を指定できない場合、特定の意図を処理できるすべてのアプリに許可を付与できます。

//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);
}

ドキュメント による代替方法

  • SetData()を呼び出して、コンテンツURIをIntentに入れます。
  • 次に、FLAG_GRANT_READ_URI_PERMISSIONまたはFLAG_GRANT_WRITE_URI_PERMISSIONのいずれか、または両方を使用して、メソッドIntent.setFlags()を呼び出します。
  • 最後に、別のアプリにインテントを送信します。ほとんどの場合、setResult()を呼び出してこれを行います。

    インテントで付与された許可は、受信アクティビティのスタックがアクティブな間、有効のままです。スタックが終了すると、
    許可は自動的に削除されます。一人に与えられた許可
    クライアントアプリのアクティビティは、他のアプリにも自動的に拡張されます
    そのアプリのコンポーネント

ところで必要に応じて、 FileProviderのソース をコピーし、attachInfoメソッドを変更して、プロバイダーがエクスポートされているかどうかを確認できないようにすることができます。

137
Leszek

このソリューションは、OS 4.4以降で機能します。すべてのデバイスで動作するように、古いデバイスの回避策を追加しました。これにより、常に最も安全なソリューションが使用されます。

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);
    }
}

許可は、フラグメントまたはアクティビティのonResumeおよびonDestroy()メソッドのrevokeFileReadPermission()で取り消されます。

23
Oliver Kranz

内部アプリフォルダーからファイルを共有する方法を完全に機能するコードサンプル。 Android 7および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>

コード自体

    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);
18
Divers

フィルが元の質問に関するコメントで述べているように、これは一意であり、GoogleのSOに関する他の情報がないため、結果も共有する必要があると考えました。

私のアプリでは、FileProviderはすぐに使用して、共有インテントを使用してファイルを共有しました。 FileProviderをセットアップするための特別な構成やコードは必要ありませんでした。 manifest.xmlに以下を配置しました:

    <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>

My_paths.xmlには次のものがあります。

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

私のコードには次のものがあります:

    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)));

また、アプリのプライベートストレージにあるファイルストアを、Gmailやgoogleドライブなどのアプリと問題なく共有できます。

14
Luke Sleeman

私が知る限り、これはAndroidの新しいバージョンでのみ機能するため、おそらく別の方法を見つけ出す必要があります。このソリューションは4.4では動作しますが、4.0または2.3.3では動作しないため、Androidデバイスで実行するアプリのコンテンツを共有するのに便利な方法ではありません。

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>

当局の指定方法に注意してください。 URIを作成して共有インテントを起動するアクティビティを指定する必要があります。この場合、アクティビティはSharingActivityと呼ばれます。この要件は、Googleのドキュメントからは明らかではありません。

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>

パスの指定方法に注意してください。上記はデフォルトでプライベート内部ストレージのルートになります。

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"));

この例では、JPEG画像を共有しています。

最後に、ファイルを適切に保存し、次のような方法でアクセスできることを確認することをお勧めします。

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

私のアプリではFileProviderは正常に機能し、filesディレクトリに保存されている内部ファイルをGmail、Yahooなどのメールクライアントに添付できます。

私が置いたAndroidドキュメントで言及されているように、私のマニフェストで:

<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>

また、私のファイルはルートファイルディレクトリに保存されていたため、filepaths.xmlは次のようになりました。

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

次にコードで:

 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();
                }
            }

これは私のために働いた。これが役立つことを願っています:)

7
Adarsh Chithran

カメラから画像を取得する場合、これらのソリューションはAndroid 4.4では機能しません。この場合、バージョンを確認することをお勧めします。

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

数日間私たちをブロックしたものを共有したい:ファイルプロバイダーコードMUSTはその後ではなく、アプリケーションタグの間に挿入されます。些細なことかもしれませんが、明記されていないので、誰かを助けることができると思いました! (piolo94に再び感謝)

1
Eric Draven

上記の回答を改善するために:NullPointerExを取得している場合:

コンテキストなしでgetApplicationContext()を使用することもできます

                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