Cómo recibir archivos desde otro dispositivo con NFC

La transferencia de archivos de Android Beam copia los archivos a un directorio especial en el dispositivo receptor. También escanea los archivos copiados con el escáner multimedia de Android y agrega entradas para los archivos multimedia al proveedor de MediaStore. En esta lección, se muestra cómo responder cuando se completa la copia del archivo y cómo ubicar los archivos copiados en el dispositivo receptor.

Cómo responder a una solicitud para mostrar datos

Cuando la transferencia de archivos de Android Beam termina de copiar archivos en el dispositivo receptor, publica una notificación que contiene un Intent con la acción ACTION_VIEW, el tipo de MIME del primer archivo que se transfirió y un URI que apunta al primer archivo. Cuando el usuario hace clic en la notificación, este Intent se envía al sistema. Para que la app responda a este intent, agrega un elemento <intent-filter> para el elemento <activity> de la Activity que debería responder. En el elemento <intent-filter>, agrega los siguientes elementos secundarios:

<action android:name="android.intent.action.VIEW" />
Coincide con el intent ACTION_VIEW enviado desde la notificación.
<category android:name="android.intent.category.CATEGORY_DEFAULT" />
Coincide con un Intent que no tiene una categoría explícita.
<data android:mimeType="mime-type" />
Coincide con un tipo de MIME. Especifica solo los tipos de MIME que la app puede controlar.

Por ejemplo, en el siguiente fragmento, se muestra cómo agregar un filtro de intents que active la actividad com.example.android.nfctransfer.ViewActivity:

        <activity
            android:name="com.example.android.nfctransfer.ViewActivity"
            android:label="Android Beam Viewer" >
            ...
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                ...
            </intent-filter>
        </activity>
    

Nota: La transferencia de archivos de Android Beam no es la única fuente de un intent ACTION_VIEW. Otras apps en el dispositivo receptor también pueden enviar un Intent con esta acción. En la sección Cómo obtener el directorio de una URI de contenido, se explica cómo controlar esta situación.

Cómo solicitar permisos de archivo

Para leer los archivos que la transferencia de archivos de Android Beam copia en el dispositivo, solicita el permiso READ_EXTERNAL_STORAGE. Por ejemplo:

        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Si quieres copiar los archivos transferidos al área de almacenamiento de tu propia app, en su lugar, solicita el permiso WRITE_EXTERNAL_STORAGE. WRITE_EXTERNAL_STORAGE incluye READ_EXTERNAL_STORAGE.

Nota: A partir de Android 4.2.2 (API nivel 17), el permiso READ_EXTERNAL_STORAGE solo se aplica si el usuario elige hacerlo. Es posible que en versiones futuras de la plataforma se requiera este permiso en todos los casos. Para garantizar la compatibilidad con las versiones futuras, solicita el permiso ahora, antes de que sea obligatorio.

Dado que tu app tiene control sobre su área de almacenamiento interno, no necesitas solicitar permiso de escritura para copiar un archivo transferido al área de almacenamiento interno.

Cómo obtener el directorio para los archivos copiados

La transferencia de archivos de Android Beam copia todos los archivos de una sola transferencia a un directorio en el dispositivo receptor. El URI del Intent de contenido enviado por la notificación de transferencia de archivos de Android Beam apunta al primer archivo transferido. Sin embargo, es posible que tu app también reciba un intent ACTION_VIEW de una fuente que no sea la transferencia de archivos de Android Beam. Para determinar cómo debes controlar el Intent entrante, debes examinar su esquema y su autoridad.

A fin de obtener el esquema del URI, llama a Uri.getScheme(). En el siguiente fragmento de código, se muestra cómo determinar el esquema y controlar correctamente el URI:

Kotlin

    class MainActivity : Activity() {
        ...
        // A File object containing the path to the transferred files
        private var parentPath: File? = null
        ...
        /*
         * Called from onNewIntent() for a SINGLE_TOP Activity
         * or onCreate() for a new Activity. For onNewIntent(),
         * remember to call setIntent() to store the most
         * current Intent
         *
         */
        private fun handleViewIntent() {
            ...
            /*
             * For ACTION_VIEW, the Activity is being asked to display data.
             * Get the URI.
             */
            if (TextUtils.equals(intent.action, Intent.ACTION_VIEW)) {
                // Get the URI from the Intent
                intent.data?.also { beamUri ->
                    /*
                     * Test for the type of URI, by getting its scheme value
                     */
                    parentPath = when (beamUri.scheme) {
                        "file" -> handleFileUri(beamUri)
                        "content" -> handleContentUri(beamUri)
                        else -> null
                    }
                }
            }
            ...
        }
        ...
    }
    

Java

    public class MainActivity extends Activity {
        ...
        // A File object containing the path to the transferred files
        private File parentPath;
        // Incoming Intent
        private Intent intent;
        ...
        /*
         * Called from onNewIntent() for a SINGLE_TOP Activity
         * or onCreate() for a new Activity. For onNewIntent(),
         * remember to call setIntent() to store the most
         * current Intent
         *
         */
        private void handleViewIntent() {
            ...
            // Get the Intent action
            intent = getIntent();
            String action = intent.getAction();
            /*
             * For ACTION_VIEW, the Activity is being asked to display data.
             * Get the URI.
             */
            if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
                // Get the URI from the Intent
                Uri beamUri = intent.getData();
                /*
                 * Test for the type of URI, by getting its scheme value
                 */
                if (TextUtils.equals(beamUri.getScheme(), "file")) {
                    parentPath = handleFileUri(beamUri);
                } else if (TextUtils.equals(
                        beamUri.getScheme(), "content")) {
                    parentPath = handleContentUri(beamUri);
                }
            }
            ...
        }
        ...
    }
    

Cómo obtener el directorio de un URI de archivo

Si el Intent entrante tiene un URI de archivo, ese URI contendrá el nombre absoluto del archivo, incluida la ruta de acceso completa al directorio y el nombre del archivo. Para la transferencia de archivos de Android Beam, la ruta de acceso del directorio apunta a la ubicación del resto de los archivos transferidos, si corresponde. Para obtener la ruta de acceso del directorio, obtén la parte de la ruta del URI, que contiene todo el URI, excepto el prefijo file:. Crea un File desde la parte de la ruta de acceso y luego obtén la ruta principal del File:

Kotlin

        ...
        fun handleFileUri(beamUri: Uri): File? =
                // Get the path part of the URI
                beamUri.path.let { fileName ->
                    // Create a File object for this filename
                    File(fileName)
                            // Get the file's parent directory
                            .parentFile
                }
        ...
    

Java

        ...
        public File handleFileUri(Uri beamUri) {
            // Get the path part of the URI
            String fileName = beamUri.getPath();
            // Create a File object for this filename
            File copiedFile = new File(fileName);
            // Get the file's parent directory
            return copiedFile.getParentFile();
        }
        ...
    

Cómo obtener el directorio de un URI de contenido

Si el Intent entrante contiene un URI de contenido, ese URI puede apuntar a un directorio y un nombre de archivo almacenados en el proveedor de contenido de MediaStore. Puedes detectar un URI de contenido para MediaStore probando el valor de autoridad del URI. Un URI de contenido de MediaStore puede provenir de la transferencia de archivos de Android Beam o de otra app, pero en ambos casos puedes recuperar un directorio y un nombre de archivo para el URI de contenido.

También puedes recibir un intent ACTION_VIEW entrante que contenga un URI de contenido para un proveedor de contenido que no sea MediaStore. En ese caso, el URI de contenido no incluirá el valor de autoridad de MediaStore y, por lo general, no apuntará a un directorio.

Nota: Para la transferencia de archivos de Android Beam, recibes un URI de contenido en el intent ACTION_VIEW si el primer archivo entrante tiene un tipo de MIME "audio/*", "image/*" o "video/*", lo que indica que el archivo es un archivo multimedia. La transferencia de archivos de Android Beam indexa los archivos multimedia que transfiere ejecutando el escáner multimedia en el directorio donde almacena los archivos transferidos. El escáner multimedia escribe los resultados en el proveedor de contenido MediaStore y luego pasa un URI de contenido para el primer archivo a la transferencia de archivos de Android Beam. Este URI de contenido es el que recibes en el Intent de notificación. Para obtener el directorio del primer archivo, lo recuperas de MediaStore utilizando el URI de contenido.

Cómo determinar el proveedor de contenido

Para determinar si puedes recuperar un directorio de archivos desde el URI de contenido, determina el proveedor de contenido asociado con el URI llamando a Uri.getAuthority() para obtener la autoridad correspondiente. El resultado tiene dos valores posibles:

MediaStore.AUTHORITY
El URI es para uno o varios archivos con seguimiento de MediaStore. Recupera el nombre completo del archivo de MediaStore y obtiene el directorio del nombre del archivo.
Cualquier otro valor de autoridad
Un URI de contenido de otro proveedor de contenido. Muestra los datos asociados con el URI de contenido, pero no obtiene el directorio de archivos.

A fin de obtener el directorio de un URI de contenido de MediaStore, ejecuta una consulta que especifique el URI de contenido entrante correspondiente al argumento Uri y la columna MediaColumns.DATA para la proyección. El Cursor que se muestra contiene la ruta de acceso completa y el nombre del archivo representados por el URI. Esta ruta de acceso también contiene todos los demás archivos que la transferencia de archivos de Android Beam copió en el dispositivo.

En el siguiente fragmento, se muestra cómo probar la autoridad de la URI de contenido y recuperar la ruta y el nombre del archivo transferido:

Kotlin

        ...
        private fun handleContentUri(beamUri: Uri): File? =
                // Test the authority of the URI
                if (beamUri.authority == MediaStore.AUTHORITY) {
                    /*
                     * Handle content URIs for other content providers
                     */
                    ...
                // For a MediaStore content URI
                } else {
                    // Get the column that contains the file name
                    val projection = arrayOf(MediaStore.MediaColumns.DATA)
                    val pathCursor = contentResolver.query(beamUri, projection, null, null, null)
                    // Check for a valid cursor
                    if (pathCursor?.moveToFirst() == true) {
                        // Get the column index in the Cursor
                        pathCursor.getColumnIndex(MediaStore.MediaColumns.DATA).let { filenameIndex ->
                            // Get the full file name including path
                            pathCursor.getString(filenameIndex).let { fileName ->
                                // Create a File object for the filename
                                File(fileName)
                            }.parentFile // Return the parent directory of the file
                        }
                    } else {
                        // The query didn't work; return null
                        null
                    }
                }
        ...
    

Java

        ...
        public String handleContentUri(Uri beamUri) {
            // Position of the filename in the query Cursor
            int filenameIndex;
            // File object for the filename
            File copiedFile;
            // The filename stored in MediaStore
            String fileName;
            // Test the authority of the URI
            if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) {
                /*
                 * Handle content URIs for other content providers
                 */
            // For a MediaStore content URI
            } else {
                // Get the column that contains the file name
                String[] projection = { MediaStore.MediaColumns.DATA };
                Cursor pathCursor =
                        getContentResolver().query(beamUri, projection,
                        null, null, null);
                // Check for a valid cursor
                if (pathCursor != null &&
                        pathCursor.moveToFirst()) {
                    // Get the column index in the Cursor
                    filenameIndex = pathCursor.getColumnIndex(
                            MediaStore.MediaColumns.DATA);
                    // Get the full file name including path
                    fileName = pathCursor.getString(filenameIndex);
                    // Create a File object for the filename
                    copiedFile = new File(fileName);
                    // Return the parent directory of the file
                    return copiedFile.getParentFile();
                 } else {
                    // The query didn't work; return null
                    return null;
                 }
            }
        }
        ...
    

Para obtener más información sobre cómo recuperar datos de un proveedor de contenido, consulta la sección Cómo recuperar los datos del proveedor.

Para obtener información adicional relacionada, consulta: