Filtro de intenciones de Android: asociar aplicación con extensión de archivo

Tengo un tipo de archivo personalizado / extensión con la que quiero asociar mi aplicación.

Por lo que sé, el elemento de datos se hace para este propósito, pero no puedo conseguir que funcione. Http://developer.android.com/guide/topics/manifest/data-element.html De acuerdo con los documentos, y un montón de mensajes en el foro, debe funcionar de esta manera:

<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:mimeType="application/pdf" /> </intent-filter> 

Bueno, no funciona. ¿Qué hice mal? Simplemente quiero declarar mi propio tipo de archivo.

Necesita filtros de intención múltiple para tratar la situación diferente que desea manejar.

Ejemplo 1, manejar solicitudes http sin mimetypes:

  <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http" /> <data android:host="*" /> <data android:pathPattern=".*\\.pdf" /> </intent-filter> 

Manejar con mimetypes, donde el sufijo es irrelevante:

  <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http" /> <data android:host="*" /> <data android:mimeType="application/pdf" /> </intent-filter> 

Manejar la intención desde una aplicación de explorador de archivos:

  <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:host="*" /> <data android:pathPattern=".*\\.pdf" /> </intent-filter> 

Las otras soluciones no funcionaron confiablemente para mí hasta que agregué:

 android:mimeType="*/*" 

Antes de que trabajó en algunas aplicaciones, en algunos no …

Solución completa para mí:

 <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" android:host="*" android:pathPattern=".*\\.EXT" android:mimeType="*/*" /> </intent-filter> 

El pathPattern

 <data android:pathPattern=".*\\.pdf" /> 

No funciona si la ruta del archivo contiene uno o más puntos antes de ".pdf".

Esto funcionará:

 <data android:pathPattern=".*\\.pdf" /> <data android:pathPattern=".*\\..*\\.pdf" /> <data android:pathPattern=".*\\..*\\..*\\.pdf" /> <data android:pathPattern=".*\\..*\\..*\\..*\\.pdf" /> 

Agregue más si desea apoyar más puntos.

Mis conclusiones:

Necesita varios filtros para tratar las diferentes formas de recuperar un archivo. Es decir, por gmail adjunto, por el explorador de archivos, por HTTP, por FTP … Todos envían intenciones muy diferentes.

Y necesita filtrar la intención que activa su actividad en su código de actividad.

Para el ejemplo siguiente, he creado un tipo de archivo falso new.mrz. Y lo recuperé de archivo adjunto de gmail y explorador de archivos.

Código de actividad agregado en el onCreate ():

  Intent intent = getIntent(); String action = intent.getAction(); if (action.compareTo(Intent.ACTION_VIEW) == 0) { String scheme = intent.getScheme(); ContentResolver resolver = getContentResolver(); if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) { Uri uri = intent.getData(); String name = getContentName(resolver, uri); Log.v("tag" , "Content intent detected: " + action + " : " + intent.getDataString() + " : " + intent.getType() + " : " + name); InputStream input = resolver.openInputStream(uri); String importfilepath = "/sdcard/My Documents/" + name; InputStreamToFile(input, importfilepath); } else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) { Uri uri = intent.getData(); String name = uri.getLastPathSegment(); Log.v("tag" , "File intent detected: " + action + " : " + intent.getDataString() + " : " + intent.getType() + " : " + name); InputStream input = resolver.openInputStream(uri); String importfilepath = "/sdcard/My Documents/" + name; InputStreamToFile(input, importfilepath); } else if (scheme.compareTo("http") == 0) { // TODO Import from HTTP! } else if (scheme.compareTo("ftp") == 0) { // TODO Import from FTP! } } 

Filtro de conexión de Gmail:

  <intent-filter android:label="@string/app_name"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="content" /> <data android:mimeType="application/octet-stream" /> </intent-filter> 
  • LOG: Se ha detectado la intención de contenido: android.intent.action.VIEW: content: //gmail-ls/[email protected]/messages/2950/attachments/0.1/BEST/false: application / octet-stream: new. Mrz

Filtro de explorador de archivos:

  <intent-filter android:label="@string/app_name"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:pathPattern=".*\\.mrz" /> </intent-filter> 
  • LOG: Se detectó la intención del archivo: android.intent.action.VIEW: file: ///storage/sdcard0/My%20Documents/new.mrz: null: new.mrz

Filtro HTTP:

  <intent-filter android:label="@string/rbook_viewer"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http" /> <data android:pathPattern=".*\\.mrz" /> </intent-filter> 

Funciones privadas utilizadas anteriormente:

 private String getContentName(ContentResolver resolver, Uri uri){ Cursor cursor = resolver.query(uri, null, null, null, null); cursor.moveToFirst(); int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); if (nameIndex >= 0) { return cursor.getString(nameIndex); } else { return null; } } private void InputStreamToFile(InputStream in, String file) { try { OutputStream out = new FileOutputStream(new File(file)); int size = 0; byte[] buffer = new byte[1024]; while ((size = in.read(buffer)) != -1) { out.write(buffer, 0, size); } out.close(); } catch (Exception e) { Log.e("MainActivity", "InputStreamToFile exception: " + e.getMessage()); } } 

Trate de añadir

 <action android:name="android.intent.action.VIEW"/> 

Las respuestas dadas por Phyrum Tea y yuku son muy informativas ya.

Quiero añadir que a partir de Android 7.0 Nougat hay un cambio en la forma de compartir archivos entre aplicaciones se maneja:

De los cambios oficiales de Android 7.0 :

Para aplicaciones orientadas a Android 7.0, el marco de Android impone la política de API de StrictMode que prohíbe exponer archivos: // URI fuera de la aplicación. Si una intención que contiene un archivo URI deja su aplicación, la aplicación falla con una excepción FileUriExposedException.

Para compartir archivos entre aplicaciones, debe enviar un contenido: // URI y conceder un permiso de acceso temporal en el URI. La forma más sencilla de conceder este permiso es mediante la clase FileProvider. Para obtener más información sobre permisos y compartir archivos, consulte Compartir archivos.

Si tienes tu propio archivo personalizado sin un tipo mime-type específico (o supongo que incluso con uno) puede que tenga que agregar un segundo valor de scheme a su intent-filter para que funcione con FileProviders también.

Ejemplo:

 <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="file" /> <data android:scheme="content" /> <data android:mimeType="*/*" /> <!-- Work around Android's ugly primitive PatternMatcher implementation that can't cope with finding a . early in the path unless it's explicitly matched. --> <data android:host="*" /> <data android:pathPattern=".*\\.sfx" /> <data android:pathPattern=".*\\..*\\.sfx" /> <data android:pathPattern=".*\\..*\\..*\\.sfx" /> <data android:pathPattern=".*\\..*\\..*\\..*\\.sfx" /> <!-- keep going if you need more --> </intent-filter> 

Lo importante aquí es la adición de

 <data android:scheme="content" /> 

Al filtro.

Tuve dificultades para averiguar acerca de este pequeño cambio que mantuvo mi actividad de la apertura de dispositivos Android 7.0, mientras que todo estaba bien en las versiones anteriores. Espero que esto ayude a alguien.

Markus Ressel está en lo cierto. Android 7.0 Nougat ya no permite compartir archivos entre aplicaciones usando un archivo URI. Se debe utilizar un URI de contenido. Sin embargo, un URI de contenido no permite compartir una ruta de archivo, sólo un tipo mime. Por lo tanto, no puede utilizar un URI de contenido para asociar su aplicación a su propia extensión de archivo.

Drobpox tiene un comportamiento interesante en Android 7.0. Cuando se reúne una extensión de archivo desconocido que parece formar una intención URI de archivo, pero en lugar de lanzar la intención que llama al sistema operativo para saber qué aplicaciones pueden aceptar la intención. Si sólo hay una aplicación que puede aceptar ese archivo URI envía un URI de contenido explícito directamente a esa aplicación. Así que para trabajar con Dropbox no es necesario cambiar los filtros de intenciones en su aplicación. No requiere un filtro de intenciones URI de contenido. Sólo asegúrate de que la aplicación pueda recibir un URI de contenido y tu aplicación con tu propia extensión de archivo funcionará con Dropbox tal como lo hizo antes de Android 7.0.

He aquí un ejemplo de mi código de carga de archivos modificado para aceptar un URI de contenido:

 Uri uri = getIntent().getData(); if (uri != null) { File myFile = null; String scheme = uri.getScheme(); if (scheme.equals("file")) { String fileName = uri.getEncodedPath(); myFile = new File(filename); } else if (!scheme.equals("content")) { //error return; } try { InputStream inStream; if (myFile != null) inStream = new FileInputStream(myFile); else inStream = getContentResolver().openInputStream(uri); InputStreamReader rdr = new InputStreamReader(inStream); ... } } 
  <!-- Works for Files, Drive and DropBox --> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:mimeType="*/*" /> <data android:host="*" /> <data android:pathPattern=".*\\.teamz" /> </intent-filter> <!-- Works for Gmail --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT"/> <data android:host="gmail-ls" android:scheme="content" android:mimeType="application/octet-stream"/> </intent-filter> 

Tenga en cuenta que esto hará que su aplicación abra todos los archivos adjuntos de Gmail, no hay forma de evitarlo

He estado tratando de conseguir que esto funcione durante años y han intentado básicamente todas las soluciones sugeridas y todavía no puede conseguir Android para reconocer extensiones de archivos específicos. Tengo un filtro de intenciones con un tipo de mimetype "*/*" que es lo único que parece funcionar y los navegadores de archivos ahora listan mi aplicación como una opción para abrir archivos, pero ahora mi aplicación se muestra como una opción para abrir cualquier aplicación TIPO de archivo aunque he especificado extensiones de archivo específicas usando la etiqueta pathPattern. Esto va tan lejos que incluso cuando intento ver / corregir un contacto en mi lista de contactos Android me pregunta si quiero utilizar mi app para ver el contacto, y ése es apenas una de muchas situaciones donde esto ocurre, MUY MUY molesto.

Eventualmente, encontré este grupo de google post con una pregunta similar a la que un ingeniero de framework Android real respondió. Ella explica que Android simplemente no sabe nada acerca de las extensiones de archivo, solo MIME-types ( https://groups.google.com/forum/#!topic/android-developers/a7qsSl3vQq0 ).

Así que por lo que he visto, probado y leído, Android simplemente no puede distinguir entre las extensiones de archivo y la etiqueta pathPattern es básicamente una gigantesca pérdida de tiempo y energía. Si tienes la suerte de solo necesitar archivos de cierto tipo mime (diga texto, video o audio), puedes usar un filtro de intenciones con un tipo mime. Si necesitas una extensión de archivo específica o un tipo de mime desconocido por Android sin embargo, entonces no tienes suerte.

Si estoy equivocado acerca de esto, por favor dígame, hasta ahora he leído cada post y he probado todas las soluciones propuestas que pude encontrar pero ninguna ha funcionado.

Podría escribir otra página o dos sobre cómo común este tipo de cosas parecen estar en Android y cómo estropeó la experiencia del desarrollador es, pero te ahorraré mis enojados rantings;). Espero haber salvado a alguien algún problema.

Tenga en cuenta que usted puede (y tal vez debería) tener múltiples intent-filter . Vea mi otra respuesta:

https://stackoverflow.com/a/8638084/341091

Para el archivo adjunto de Gmail, puede utilizar:

 <intent-filter android:label="@string/app_name"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="content" /> <data android:mimeType="application/pdf" /> <!-- .pdf --> <data android:mimeType="application/msword" /> <!-- .doc / .dot --> <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" /> <!-- .docx --> <data android:mimeType="application/vnd.ms-excel" /> <!-- .xls / .xlt / .xla --> <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" /> <!-- .xlsx --> <data android:mimeType="application/vnd.ms-powerpoint" /> <!-- .ppt / .pps / .pot / .ppa --> <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" /> <!-- .pptx --> <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.slideshow" /> <!-- .ppsx --> <data android:mimeType="application/zip" /> <!-- .zip --> <data android:mimeType="image/jpeg" /> <!-- .jpeg --> <data android:mimeType="image/png" /> <!-- .png --> <data android:mimeType="image/gif" /> <!-- .gif --> <data android:mimeType="text/plain" /> <!-- .txt / .text / .log / .c / .c++ / ... --> 

Agregue tantos tipos de mime como sea necesario. Sólo los necesito para mi proyecto.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.