Cómo enviar archivos a otro dispositivo con NFC

En esta lección, se muestra cómo diseñar tu app para enviar archivos grandes a otro dispositivo mediante la transferencia de archivos de Android Beam. Con el fin de enviar archivos, solicitas permiso para usar NFC y el almacenamiento externo, realizas pruebas para asegurarte de que tu dispositivo sea compatible con NFC y proporcionas URI a la transferencia de archivos de Android Beam.

La función de transferencia de archivos de Android Beam tiene los siguientes requisitos:

  1. La transferencia de archivos de Android Beam para archivos grandes solo está disponible en Android 4.1 (API nivel 16) y versiones posteriores.
  2. Los archivos que quieras transferir deberán encontrarse en el almacenamiento externo. Para obtener más información, consulta Cómo usar el almacenamiento externo.
  3. Cada archivo que quieras transferir deberá ser legible para cualquier usuario. Puedes establecer este permiso llamando al método File.setReadable(true,false).
  4. Deberás proporcionar un URI para los archivos que quieras transferir. La transferencia de archivos de Android Beam no podrá controlar los URI de contenido que genere FileProvider.getUriForFile.

Declara funciones en el manifiesto

Primero, edita el manifiesto de tu app para declarar los permisos y las funciones que necesite.

Solicita permisos

A fin de permitir que tu app use la transferencia de archivos de Android Beam para enviar archivos desde un almacenamiento externo mediante NFC, deberás solicitar los siguientes permisos en el manifiesto de tu app:

NFC
Permite que tu app envíe datos a través de NFC. Para especificar este permiso, agrega lo siguiente como elemento secundario de <manifest>:
        <uses-permission android:name="android.permission.NFC" />
    
READ_EXTERNAL_STORAGE
Permite que tu app acceda al almacenamiento externo. Para especificar este permiso, agrega el siguiente elemento como elemento secundario del elemento <manifest>:
        <uses-permission
                android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

Nota: A partir de Android 4.2.2 (API nivel 17), ya no se aplica este permiso. Las versiones futuras de la plataforma pueden requerirlo para apps que intentan leer desde un almacenamiento externo. Para garantizar compatibilidad con versiones futuras, solicita el permiso ahora, antes de que sea obligatorio.

Especifica la función NFC

Para especificar que tu app usa NFC, agrega un elemento <uses-feature> como elemento secundario del elemento <manifest>. Configura el atributo android:required en true a fin de indicar que tu app no funcionará a menos que esté presente la función NFC.

En el siguiente fragmento, se muestra cómo especificar el elemento <uses-feature>:

    <uses-feature
        android:name="android.hardware.nfc"
        android:required="true" />

Ten en cuenta que, si tu app solo usa NFC como opción, pero aún puede ejecutarse si no está presente esa función, deberás establecer android:required como false y probarlo para NFC en el código.

Especifica la transferencia de archivos de Android Beam

Dado que la transferencia de archivos de Android Beam solo está disponible en Android 4.1 (API nivel 16) y versiones posteriores, si una parte clave de la funcionalidad de tu app depende de la transferencia de archivos de Android Beam, deberás especificar el elemento <uses-sdk> con el atributo android:minSdkVersion="16". De lo contrario, puedes configurar android:minSdkVersion con otro valor según sea necesario y probar la versión de la plataforma en el código, como se describe en la siguiente sección.

Prueba de compatibilidad con la transferencia de archivos de Android Beam

Para especificar en el manifiesto de tu app que NFC es opcional, utiliza el siguiente elemento:

    <uses-feature android:name="android.hardware.nfc" android:required="false" />

Si configuras el atributo android:required="false", debes probar la compatibilidad con NFC y con la transferencia de archivos de Android Beam en el código.

Para probar la compatibilidad con la transferencia de archivos de Android Beam en el código, primero prueba que el dispositivo admita NFC. Para ello, llama a PackageManager.hasSystemFeature() con el argumento FEATURE_NFC. Luego, comprueba que la versión de Android sea compatible con la transferencia de archivos de Android Beam. Para ello, prueba el valor de SDK_INT. Si se admite la transferencia de archivos de Android Beam, obtendrás una instancia del controlador NFC, que te permitirá comunicarte con el hardware de NFC. Por ejemplo:

Kotlin

    class MainActivity : Activity() {
        ...
        private lateinit var nfcAdapter: NfcAdapter
        // Flag to indicate that Android Beam is available
        private var androidBeamAvailable = false
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            ...
            androidBeamAvailable = if (!packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
                // NFC isn't available on the device
                /*
                  * Disable NFC features here.
                  * For example, disable menu items or buttons that activate
                  * NFC-related features
                  */
                false
            // Android Beam file transfer isn't supported
            } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
                // If Android Beam isn't available, don't continue.
                androidBeamAvailable = false
                /*
                 * Disable Android Beam file transfer features here.
                 */
                ...
                false
            } else {
                // Android Beam file transfer is available, continue
                nfcAdapter = NfcAdapter.getDefaultAdapter(this)
                ...
                true
            }
        }
        ...
    }
    

Java

    public class MainActivity extends Activity {
        ...
        NfcAdapter nfcAdapter;
        // Flag to indicate that Android Beam is available
        boolean androidBeamAvailable  = false;
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
            // NFC isn't available on the device
            if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
                /*
                 * Disable NFC features here.
                 * For example, disable menu items or buttons that activate
                 * NFC-related features
                 */
                ...
            // Android Beam file transfer isn't supported
            } else if (Build.VERSION.SDK_INT <
                    Build.VERSION_CODES.JELLY_BEAN_MR1) {
                // If Android Beam isn't available, don't continue.
                androidBeamAvailable = false;
                /*
                 * Disable Android Beam file transfer features here.
                 */
                ...
            // Android Beam file transfer is available, continue
            } else {
                androidBeamAvailable = true;
                nfcAdapter = NfcAdapter.getDefaultAdapter(this);
            ...
            }
        }
        ...
    }
    

Crea un método de devolución de llamada que proporcione archivos

Una vez que hayas verificado que el dispositivo admite la transferencia de archivos de Android Beam, agrega un método de devolución de llamada que el sistema invoque cuando la transferencia de archivos Android Beam detecte que el usuario desea enviar archivos a otro dispositivo compatible con NFC. En este método de devolución de llamada, se muestra un arreglo de objetos Uri. La transferencia de archivos de Android Beam copia los archivos que representan esos URI en el dispositivo receptor.

Para agregar el método de devolución de llamada, implementa la interfaz NfcAdapter.CreateBeamUrisCallback y su método createBeamUris(). En el siguiente fragmento de código, se muestra cómo hacerlo:

Kotlin

    public class MainActivity : Activity() {
        ...
        // List of URIs to provide to Android Beam
        private val fileUris = mutableListOf<Uri>()
        ...
        /**
         * Callback that Android Beam file transfer calls to get
         * files to share
         */
        private inner class FileUriCallback : NfcAdapter.CreateBeamUrisCallback {
            /**
             * Create content URIs as needed to share with another device
             */
            override fun createBeamUris(event: NfcEvent): Array<Uri> {
                return fileUris.toTypedArray()
            }
        }
        ...
    }
    

Java

    public class MainActivity extends Activity {
        ...
        // List of URIs to provide to Android Beam
        private Uri[] fileUris = new Uri[10];
        ...
        /**
         * Callback that Android Beam file transfer calls to get
         * files to share
         */
        private class FileUriCallback implements
                NfcAdapter.CreateBeamUrisCallback {
            public FileUriCallback() {
            }
            /**
             * Create content URIs as needed to share with another device
             */
            @Override
            public Uri[] createBeamUris(NfcEvent event) {
                return fileUris;
            }
        }
        ...
    }
    

Una vez que implementes la interfaz, llama a setBeamPushUrisCallback() para proporcionar la devolución de llamada de Android Beam. En el siguiente fragmento de código, se muestra cómo hacerlo:

Kotlin

    class MainActivity : Activity() {
        ...
        private lateinit var nfcAdapter: NfcAdapter
        // Flag to indicate that Android Beam is available
        private var androidBeamAvailable = false
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            ...
            // Android Beam file transfer is available, continue
            nfcAdapter = NfcAdapter.getDefaultAdapter(this).apply {

                /*
                 * Instantiate a new FileUriCallback to handle requests for
                 * URIs
                 */
                fileUriCallback = FileUriCallback()
                // Set the dynamic callback for URI requests.
                nfcAdapter.setBeamPushUrisCallback(fileUriCallback, this@MainActivity)
            }
            ...
        }
        ...
    }
    

Java

    public class MainActivity extends Activity {
        ...
        // Instance that returns available files from this app
        private FileUriCallback fileUriCallback;
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
            // Android Beam file transfer is available, continue
            ...
            nfcAdapter = NfcAdapter.getDefaultAdapter(this);
            /*
             * Instantiate a new FileUriCallback to handle requests for
             * URIs
             */
            fileUriCallback = new FileUriCallback();
            // Set the dynamic callback for URI requests.
            nfcAdapter.setBeamPushUrisCallback(fileUriCallback,this);
            ...
        }
        ...
    }
    

Nota: También puedes proporcionar el arreglo de objetos Uri directamente al marco de trabajo NFC mediante la instancia NfcAdapter de tu app. Elige este enfoque si puedes definir los URI para transferir antes de que ocurra el evento táctil de NFC. Para obtener más información sobre este enfoque, consulta NfcAdapter.setBeamPushUris().

Especifica los archivos para enviar

Para transferir uno o más archivos a otro dispositivo compatible con NFC, obtén un URI de archivo (un URI con un esquema file) para cada archivo y luego agrega el URI a un arreglo de Uri. Para transferir un archivo, también debes tener acceso de lectura permanente al archivo. Por ejemplo, en el siguiente fragmento de código, se muestra cómo obtener un URI de archivo a partir de un nombre de archivo y luego agregar el URI al arreglo:

Kotlin

            /*
             * Create a list of URIs, get a File,
             * and set its permissions
             */
            val fileUris = mutableListOf<Uri>()
            val transferFile = "transferimage.jpg"
            val extDir = getExternalFilesDir(null)
            val requestFile = File(extDir, transferFile).apply {
                setReadable(true, false)
            }
            // Get a URI for the File and add it to the list of URIs
            Uri.fromFile(requestFile)?.also { fileUri ->
                fileUris += fileUri
            } ?: Log.e("My Activity", "No File URI available for file.")
    

Java

            /*
             * Create a list of URIs, get a File,
             * and set its permissions
             */
            private Uri[] fileUris = new Uri[10];
            String transferFile = "transferimage.jpg";
            File extDir = getExternalFilesDir(null);
            File requestFile = new File(extDir, transferFile);
            requestFile.setReadable(true, false);
            // Get a URI for the File and add it to the list of URIs
            fileUri = Uri.fromFile(requestFile);
            if (fileUri != null) {
                fileUris[0] = fileUri;
            } else {
                Log.e("My Activity", "No File URI available for file.");
            }
    

Para obtener más información relacionada, consulta Opciones de almacenamiento.

Para obtener un código de ejemplo relacionado con esta página, consulta el ejemplo de BeamLargeFiles de Android.