Mon objectif est de créer un fichier XML sur le stockage interne puis de l'envoyer via le partage Intent.
Je peux créer un fichier XML en utilisant ce code
FileOutputStream outputStream = context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
PrintStream printStream = new PrintStream(outputStream);
String xml = this.writeXml(); // get XML here
printStream.println(xml);
printStream.close();
Je suis coincé à essayer de récupérer un Uri dans le fichier de sortie afin de le partager. J'ai d'abord essayé d'accéder au fichier en convertissant le fichier en Uri
File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);
Cela renvoie file: ///data/data/com.my.package/files/myfile.xml mais je n'arrive pas à le joindre à un e-mail, à un téléchargement, etc.
Si je vérifie manuellement la longueur du fichier, c'est correct et montre qu'il y a une taille de fichier raisonnable.
Ensuite, j'ai créé un fournisseur de contenu et essayé de référencer le fichier et ce n'est pas un descripteur valide pour le fichier. Le ContentProvider
ne semble jamais être appelé un point quelconque.
Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;
Cela renvoie content: //com.my.package.provider/myfile.xml mais je vérifie le fichier et sa longueur est nulle.
Comment accéder correctement aux fichiers? Dois-je créer le fichier avec le fournisseur de contenu? Si c'est le cas, comment?
Mise à jour
Voici le code que j'utilise pour partager. Si je sélectionne Gmail, il apparaît en tant que pièce jointe mais lorsque j'envoie, il donne une erreur Impossible d'afficher la pièce jointe et l'e-mail qui arrive n'a pas de pièce jointe .
public void onClick(View view) {
Log.d(TAG, "onClick " + view.getId());
switch (view.getId()) {
case R.id.share_cancel:
setResult(RESULT_CANCELED, getIntent());
finish();
break;
case R.id.share_share:
MyXml xml = new MyXml();
Uri uri;
try {
uri = xml.writeXmlToFile(getApplicationContext(), "myfile.xml");
//uri is "file:///data/data/com.my.package/files/myfile.xml"
Log.d(TAG, "Share URI: " + uri.toString() + " path: " + uri.getPath());
File f = new File(uri.getPath());
Log.d(TAG, "File length: " + f.length());
// shows a valid file size
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("text/plain");
startActivity(Intent.createChooser(shareIntent, "Share"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
break;
}
}
J'ai remarqué qu'il y a un Exception
jeté ici depuis l'intérieur createChooser (...), mais je ne peux pas comprendre pourquoi il est jeté.
E/ActivityThread (572): L'activité com.Android.internal.app.ChooserActivity a divulgué IntentReceiver com.Android.internal.app.ResolverActivity$1@4148d658 qui était initialement enregistré ici. Vous manquez un appel à unregisterReceiver ()?
J'ai recherché cette erreur et je ne trouve rien d'évident. Ces deux liens suggèrent que je dois désinscrire un récepteur.
J'ai une configuration de récepteur, mais c'est pour un AlarmManager
qui est défini ailleurs et ne nécessite pas l'application pour s'inscrire/se désinscrire.
Code pour openFile (...)
Au cas où cela serait nécessaire, voici le fournisseur de contenu que j'ai créé.
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileLocation = getContext().getCacheDir() + "/" + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
}
Il est possible d'exposer un fichier stocké dans le répertoire privé de vos applications via un ContentProvider. Voici un exemple de code que j'ai créé montrant comment créer un fournisseur de contenu qui peut le faire.
Manifeste
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
package="com.example.providertest"
Android:versionCode="1"
Android:versionName="1.0">
<uses-sdk Android:minSdkVersion="11" Android:targetSdkVersion="15" />
<application Android:label="@string/app_name"
Android:icon="@drawable/ic_launcher"
Android:theme="@style/AppTheme">
<activity
Android:name=".MainActivity"
Android:label="@string/app_name">
<intent-filter>
<action Android:name="Android.intent.action.MAIN" />
<category Android:name="Android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
Android:name="MyProvider"
Android:authorities="com.example.prov"
Android:exported="true"
/>
</application>
</manifest>
Dans votre ContentProvider, remplacez openFile pour renvoyer le ParcelFileDescriptor
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File cacheDir = getContext().getCacheDir();
File privateFile = new File(cacheDir, "file.xml");
return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
Assurez-vous d'avoir copié votre fichier xml dans le répertoire cache
private void copyFileToInternal() {
try {
InputStream is = getAssets().open("file.xml");
File cacheDir = getCacheDir();
File outFile = new File(cacheDir, "file.xml");
OutputStream os = new FileOutputStream(outFile.getAbsolutePath());
byte[] buff = new byte[1024];
int len;
while ((len = is.read(buff)) > 0) {
os.write(buff, 0, len);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace(); // TODO: should close streams properly here
}
}
Désormais, toutes les autres applications devraient pouvoir obtenir un InputStream pour votre fichier privé en utilisant l'URI de contenu (content: //com.example.prov/myfile.xml)
Pour un test simple, appelez le fournisseur de contenu à partir d'une application distincte similaire à la suivante
private class MyTask extends AsyncTask<String, Integer, String> {
@Override
protected String doInBackground(String... params) {
Uri uri = Uri.parse("content://com.example.prov/myfile.xml");
InputStream is = null;
StringBuilder result = new StringBuilder();
try {
is = getApplicationContext().getContentResolver().openInputStream(uri);
BufferedReader r = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = r.readLine()) != null) {
result.append(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (is != null) is.close(); } catch (IOException e) { }
}
return result.toString();
}
@Override
protected void onPostExecute(String result) {
Toast.makeText(CallerActivity.this, result, Toast.LENGTH_LONG).show();
super.onPostExecute(result);
}
}
Donc, la réponse de Rob est correcte, je suppose, mais je l'ai fait un peu différemment. Pour autant que je comprends, avec la mise en fournisseur:
Android:exported="true"
vous donnez un accès public à tous vos fichiers?! Quoi qu'il en soit, un moyen de n'accorder qu'un accès à certains fichiers consiste à définir les autorisations de chemin d'accès aux fichiers de la manière suivante:
<provider
Android:authorities="com.your.app.package"
Android:name="Android.support.v4.content.FileProvider"
Android:exported="false"
Android:grantUriPermissions="true">
<meta-data
Android:name="Android.support.FILE_PROVIDER_PATHS"
Android:resource="@xml/file_paths" />
</provider>
puis dans votre répertoire XML vous définissez le fichier file_paths.xml comme suit:
<paths xmlns:Android="http://schemas.Android.com/apk/res/Android">
<files-path path="/" name="allfiles" />
<files-path path="tmp/" name="tmp" />
</paths>
maintenant, le "allfiles" donne le même type d'autorisation publique que je suppose que l'option Android: exports = "true" mais vous ne voulez pas vraiment que je suppose que pour définir un sous-répertoire est la ligne suivante. Ensuite, tout ce que vous avez à faire est de stocker les fichiers que vous souhaitez partager, dans ce répertoire.
Ce que vous devez ensuite faire est, comme le dit également Rob, obtenir un URI pour ce fichier. Voici comment je l'ai fait:
Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);
Ensuite, lorsque j'ai cet URI, j'ai dû y attacher des autorisations pour qu'une autre application puisse l'utiliser. J'utilisais ou envoyais ce fichier URI à l'application appareil photo. Quoi qu'il en soit, c'est ainsi que j'ai obtenu les autres informations sur le package d'application et accordé des autorisations à l'URI:
PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() < 1) {
return;
}
String packageName = list.get(0).activityInfo.packageName;
grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
ClipData clipData = ClipData.newRawUri("CAMFILE", sharedFileUri);
cameraIntent.setClipData(clipData);
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cameraIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, GET_FROM_CAMERA);
J'ai laissé le code de la caméra car je ne voulais pas prendre un autre exemple sur lequel je ne travaillais pas. Mais de cette façon, vous voyez que vous pouvez attacher des autorisations à l'URI lui-même.
Le truc de la caméra est que je peux le configurer via ClipData, puis définir des autorisations supplémentaires. Je suppose que dans votre cas, vous n'avez besoin que de FLAG_GRANT_READ_URI_PERMISSION lorsque vous joignez un fichier à un e-mail.
Voici le lien pour aider FileProvider car j'ai basé tout mon post sur les informations que j'ai trouvées là-bas. J'ai eu du mal à trouver des informations sur le package de l'application appareil photo.
J'espère que ça aide.
Si vous devez autoriser d'autres applications à voir les fichiers privés de votre application (pour le partage ou autre), vous pourrez peut-être gagner du temps et simplement utiliser la bibliothèque de compatibilité v4 FileProvider