Menerima File dari Perangkat Lain dengan NFC

Proses transfer file Android Beam akan menyalin file ke direktori khusus di perangkat penerima. Proses tersebut juga memindai file yang disalin menggunakan Android Media Scanner dan menambahkan entri untuk file media ke penyedia MediaStore. Pelajaran ini menunjukkan cara memberikan respons ketika proses penyalinan file selesai, dan cara menemukan file salinan di perangkat penerima.

Merespons permintaan untuk menampilkan data

Setelah transfer file Android Beam selesai menyalin file ke perangkat penerima, akan muncul notifikasi yang memuat Intent dengan tindakan ACTION_VIEW, jenis MIME file pertama yang ditransfer, dan URI yang mengarah ke file pertama. Ketika pengguna mengklik notifikasi tersebut, intent ini akan dikirim ke sistem. Agar aplikasi merespons intent ini, tambahkan elemen <intent-filter> untuk elemen <activity> dari Activity yang harus merespons. Di elemen <intent-filter>, tambahkan elemen turunan berikut:

<action android:name="android.intent.action.VIEW" />
Mencocokkan inten ACTION_VIEW yang dikirim dari notifikasi.
<category android:name="android.intent.category.CATEGORY_DEFAULT" />
Mencocokkan Intent yang tidak memiliki kategori eksplisit.
<data android:mimeType="mime-type" />
Mencocokkan jenis MIME. Hanya menentukan jenis MIME yang dapat ditangani aplikasi Anda.

Sebagai contoh, cuplikan berikut ini menunjukkan cara menambahkan filter intent yang memicu aktivitas 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>
    

Catatan: Transfer file Android Beam bukanlah satu-satunya sumber intent ACTION_VIEW. Aplikasi lain di perangkat penerima juga dapat mengirimkan Intent dengan tindakan ini. Penanganan situasi ini dibahas di bagian Mendapatkan direktori dari URI konten.

Meminta izin file

Untuk membaca file yang ditransfer oleh file Android Beam ke perangkat, minta izin READ_EXTERNAL_STORAGE. Contoh:

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

Jika ingin menyalin file yang ditransfer ke area penyimpanan aplikasi Anda sendiri, minta izin WRITE_EXTERNAL_STORAGE sebagai gantinya. WRITE_EXTERNAL_STORAGE mencakup READ_EXTERNAL_STORAGE.

Catatan: Mulai Android 4.2.2 (API level 17), izin READ_EXTERNAL_STORAGE hanya diberlakukan jika pengguna memilih untuk melakukannya. Versi platform selanjutnya mungkin memerlukan izin ini dalam semua situasi. Untuk memastikan kompatibilitas maju, minta izin tersebut sekarang sebelum diminta.

Karena aplikasi Anda dapat mengontrol area penyimpanan internalnya, Anda tidak perlu meminta izin tulis untuk menyalin file yang ditransfer ke area penyimpanan internal.

Mendapatkan direktori untuk file yang disalin

Transfer file Android Beam menyalin semua file dalam sekali transfer ke satu direktori di perangkat penerima. URI dalam Intent konten yang dikirim oleh notifikasi transfer file Android Beam mengarah ke file pertama yang ditransfer. Namun, aplikasi Anda juga dapat menerima intent ACTION_VIEW dari sumber selain transfer file Android Beam. Untuk menentukan bagaimana Anda harus menangani Intent yang masuk, periksa skema dan otoritasnya.

Untuk mendapatkan skema URI, panggil Uri.getScheme(). Cuplikan kode berikut menunjukkan cara menentukan skema dan menangani URI setelahnya:

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

Mendapatkan direktori dari URI file

Jika Intent yang masuk berisi URI file, URI tersebut berisi nama absolut suatu file, termasuk jalur direktori lengkap dan nama file tersebut. Untuk transfer file Android Beam, lokasi direktorinya mengarah ke lokasi file lain yang ditransfer, jika ada. Untuk mendapatkan jalur direktori, dapatkan bagian jaluri URI, yang berisi semua URI kecuali awalan file:. Buat File dari bagian jalur, lalu dapatkan jalur induk 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();
        }
        ...
    

Mendapatkan direktori dari URI konten

Jika Intent yang masuk berisi URI konten, URI dapat mengarah ke salah satu direktori dan nama file yang tersimpan dalam penyedia konten MediaStore. Anda dapat mendeteksi URI konten untuk MediaStore dengan menguji nilai otoritas URI. URI konten untuk MediaStore dapat berasal dari transfer file Android Beam atau dari aplikasi lain, tetapi dalam kedua situasi ini, Anda dapat memperoleh direktori dan nama file untuk URI konten.

Anda juga dapat menerima intent ACTION_VIEW masuk yang berisi URI konten untuk penyedia konten selain MediaStore. Dalam hal ini, URI konten tidak berisi nilai otoritas MediaStore, dan URI konten biasanya tidak mengarah ke suatu direktori.

Catatan: Untuk transfer file Android Beam, Anda menerima URI konten dalam intent ACTION_VIEW jika file pertama yang masuk memiliki jenis MIME untuk "audio/*", "image/*", atau "video/*", yang menunjukkan bahwa file tersebut terkait dengan media. Transfer file Android Beam mengindeks file media yang ditransfernya dengan menjalankan Pemindai Media pada direktori tempatnya menyimpan file yang ditransfer. Pemindai Media akan menulis hasilnya ke penyedia konten MediaStore, lalu mengembalikan URI konten untuk file pertama ke transfer file Android Beam. URI konten ini adalah URI yang Anda terima dalam Intent notifikasi. Untuk mendapatkan direktori file pertama, ambil dari MediaStore menggunakan URI konten.

Menentukan penyedia konten

Untuk menentukan apakah Anda dapat mengambil direktori file dari URI konten, tentukan penyedia konten yang terkait dengan URI dengan memanggil Uri.getAuthority() guna mendapatkan otoritas URI. Terdapat dua kemungkinan nilai untuk hasilnya:

MediaStore.AUTHORITY
URI tersebut ditujukan bagi satu atau beberapa file yang dilacak oleh MediaStore. Ambil nama file lengkap dari MediaStore dan dapatkan direktori dari nama file tersebut.
Nilai otoritas lainnya
URI konten dari penyedia konten lain. Tampilkan data yang terkait dengan URI konten, tetapi jangan dapatkan direktori file.

Guna mendapatkan direktori untuk URI konten MediaStore, jalankan kueri yang menentukan URI konten yang masuk untuk argumen Uri dan kolom MediaColumns.DATA untuk proyeksi. Cursor yang ditampilkan berisi jalur dan nama lengkap untuk file yang direpresentasikan oleh URI. Jalur ini juga berisi semua file lain yang baru saja disalin oleh transfer file Android Beam ke perangkat.

Cuplikan berikut menunjukkan cara menguji otoritas URI konten serta mendapatkan jalur dan nama file untuk file yang ditransfer:

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

Untuk mempelajari lebih lanjut cara mengambil data dari penyedia konten, lihat bagian Mengambil data dari penyedia.

Untuk informasi terkait lainnya, lihat: