Crear y compartir un archivo desde el almacenamiento interno

Mi objective es crear un archivo XML en el almacenamiento interno y luego enviarlo a través del recurso compartido Compartir.

Puedo crear un archivo XML usando este código

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

Estoy atrapado tratando de recuperar un Uri en el archivo de salida para poder compartirlo. Primero intenté acceder al archivo convirtiendo el archivo a un URI

 File outFile = context.getFileStreamPath(fileName); return Uri.fromFile(outFile); 

Esto devuelve el archivo: ///data/data/com.my.package/files/myfile.xml pero no puedo adjuntarlo a un correo electrónico, cargarlo, etc.

Si verifico manualmente la longitud del archivo, es correcto y muestra que hay un tamaño de archivo razonable.

Luego, creé un proveedor de contenido e intenté hacer referencia al archivo y no es un identificador válido para el archivo. El ContentProvider nunca parece llamarse un punto.

 Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName); return uri; 

Esto devuelve contenido: //com.my.package.provider/myfile.xml pero verifico el archivo y su longitud es cero.

¿Cómo accedo a los archivos correctamente? ¿Necesito crear el archivo con el proveedor de contenido? ¿Si es así, cómo?

Actualizar

Aquí está el código que estoy usando para compartir. Si selecciono Gmail, se muestra como un archivo adjunto, pero cuando lo envío muestra un error. No se pudo mostrar el archivo adjunto y el correo electrónico que llega no tiene datos adjuntos.

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

Noté que hay una Exception lanzada aquí dentro de createChooser (…) , pero no puedo entender por qué se lanza.

E / ActivityThread (572): Activity com.android.internal.app.ChooserActivity ha filtrado IntentReceiver com.android.internal.app.ResolverActivity$1@4148d658 que se registró originalmente aquí. ¿Te estás perdiendo una llamada para anular el registro de Receiver ()?

Investigué este error y no puedo encontrar nada obvio. Ambos enlaces sugieren que necesito anular el registro de un receptor.

  • ChooserActivity ha filtrado IntentReceiver
  • ¿Por qué Intent.createChooser () necesita un BroadcastReceiver y cómo implementarlo?

Tengo una configuración de receptor, pero es para un AlarmManager que está configurado en otro lugar y no requiere que la aplicación se registre / anule el registro.

Código para openFile (…)

En caso de que sea necesario, aquí está el proveedor de contenido que he creado.

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

Es posible exponer un archivo almacenado en su directorio privado de aplicaciones a través de ContentProvider. Aquí hay un código de ejemplo que hice que muestra cómo crear un proveedor de contenido que puede hacer esto.

Manifiesto

             

En su ContentProvider anule el OpenFile para devolver el 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); } 

Asegúrese de haber copiado su archivo xml en el directorio de caché

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

Ahora cualquier otra aplicación debería poder obtener un InputStream para su archivo privado usando el contenido uri (content: //com.example.prov/myfile.xml)

Para una prueba simple, llame al proveedor de contenido desde una aplicación separada similar a la siguiente

  private class MyTask extends AsyncTask { @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); } } 

Entonces la respuesta de Rob es correcta, supongo, pero lo hice de manera diferente. Por lo que yo entiendo, con la configuración en el proveedor:

 android:exported="true" 

estás dando acceso público a todos tus archivos ?! De todos modos, una forma de dar acceso únicamente a algunos archivos es definir permisos de ruta de archivos de la siguiente manera:

    

y luego en su directorio XML usted define el archivo file_paths.xml de la siguiente manera:

     

ahora, los “allfiles” otorgan el mismo tipo de permiso público supongo que la opción android: exported = “true”, pero en realidad no quieres eso, supongo que definir un subdirectorio es la próxima línea. Entonces, todo lo que tienes que hacer es almacenar los archivos que deseas compartir, allí en ese directorio.

Luego, lo que tienes que hacer es, como también dice Rob, obtener un URI para este archivo. Así es como lo hice:

 Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile); 

Luego, cuando tengo este URI, tuve que adjuntarle permisos para que otra aplicación lo use. Estaba usando o enviando este archivo URI a la aplicación de la cámara. De todos modos, esta es la forma en que obtuve la información del otro paquete de la aplicación y otorgé permisos al URI:

 PackageManager packageManager = getPackageManager(); List 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); 

Dejé el código para la cámara porque no quería tomar otro ejemplo en el que no trabajé. Pero de esta forma puede ver que puede adjuntar permisos al URI mismo.

La cuestión de la cámara es que puedo configurarlo a través de ClipData y luego configurar permisos adicionales. Supongo que en su caso solo necesita FLAG_GRANT_READ_URI_PERMISSION porque está adjuntando un archivo a un correo electrónico.

Aquí está el enlace para ayudar con FileProvider, ya que basé toda mi publicación en la información que encontré allí. Tuve algunos problemas para encontrar un paquete de información para la aplicación de cámara.

Espero eso ayude.

Si necesita permiso para que otras aplicaciones vean los archivos privados de su aplicación (para Compartir, o de lo contrario), puede ahorrar algo de tiempo y solo usar el FileProvider de la biblioteca v4 compat .