page.title=Storage Access Framework @jd:body

Dalam dokumen ini tampilkan maksimal

  1. Ikhtisar
  2. Arus Kontrol
  3. Menulis Aplikasi Klien
    1. Mencari dokumen
    2. Memproses hasil
    3. Memeriksa metadata dokumen
    4. Membuka dokumen
    5. Membuat dokumen baru
    6. Menghapus dokumen
    7. Mengedit dokumen
    8. Mempertahankan izin
  4. Menulis Penyedia Dokumen Custom
    1. Manifes
    2. Kontrak
    3. Subkelas DocumentsProvider
    4. Keamanan

Kelas-kelas utama

  1. {@link android.provider.DocumentsProvider}
  2. {@link android.provider.DocumentsContract}

Video

  1. DevBytes: Android 4.4 Storage Access Framework: Penyedia
  2. DevBytes: Android 4.4 Storage Access Framework: Klien

Contoh Kode

  1. Penyedia Penyimpanan
  2. Klien Penyimpanan

Lihat Juga

  1. Dasar-Dasar Penyedia Konten

Android 4.4 (API level 19) memperkenalkan Storage Access Framework (SAF, Kerangka Kerja Akses Penyimpanan). SAF memudahkan pengguna menyusuri dan membuka dokumen, gambar, dan file lainnya di semua penyedia penyimpanan dokumen pilihannya. UI standar yang mudah digunakan memungkinkan pengguna menyusuri file dan mengakses yang terbaru dengan cara konsisten di antara berbagai aplikasi dan penyedia.

Layanan penyimpanan cloud atau lokal bisa dilibatkan dalam ekosistem ini dengan mengimplementasikan sebuah {@link android.provider.DocumentsProvider} yang membungkus layanannya. Aplikasi klien yang memerlukan akses ke dokumen sebuah penyedia bisa berintegrasi dengan SAF cukup dengan beberapa baris kode.

SAF terdiri dari berikut ini:

Beberapa fitur yang disediakan oleh SAF adalah sebagai berikut:

Ikhtisar

SAF berpusat di seputar penyedia konten yang merupakan subkelas dari kelas {@link android.provider.DocumentsProvider}. Dalam penyedia dokumen, data distrukturkan sebagai hierarki file biasa:

data model

Gambar 1. Model data penyedia dokumen. Root menunjuk ke satu Document, yang nanti memulai pemekaran seluruh pohon.

Perhatikan yang berikut ini:

Arus Kontrol

Seperti dinyatakan di atas, model data penyedia dokumen dibuat berdasarkan hierarki file biasa. Akan tetapi, Anda bisa menyimpan secara fisik data dengan cara apa pun yang disukai, selama data bisa diakses melalui API {@link android.provider.DocumentsProvider}. Misalnya, Anda bisa menggunakan penyimpanan cloud berbasis tag untuk data Anda.

Gambar 2 menampilkan contoh cara aplikasi foto bisa menggunakan SAF untuk mengakses data tersimpan:

app

Gambar 2. Arus Storage Access Framework

Perhatikan yang berikut ini:

Gambar 3 menunjukkan picker yang di digunakan pengguna mencari gambar telah memilih akun Google Drive:

picker

Gambar 3. Picker

Bila pengguna memilih Google Drive, gambar-gambar akan ditampilkan, seperti yang ditampilkan dalam gambar 4. Dari titik itu, pengguna bisa berinteraksi dengan gambar dengan cara apa pun yang didukung oleh penyedia dan aplikasi klien.

picker

Gambar 4. Gambar

Menulis Aplikasi Klien

Pada Android 4.3 dan yang lebih rendah, jika Anda ingin aplikasi mengambil file dari aplikasi lain, aplikasi Anda harus memanggil intent seperti {@link android.content.Intent#ACTION_PICK} atau {@link android.content.Intent#ACTION_GET_CONTENT}. Pengguna nanti harus memilih satu aplikasi yang akan digunakan untuk mengambil file dan aplikasi yang dipilih harus menyediakan antarmuka pengguna bagi untuk menyusuri dan mengambil dari file yang tersedia.

Pada Android 4.4 dan yang lebih tinggi, Anda mempunyai opsi tambahan dalam menggunakan intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, yang menampilkan UI picker yang dikontrol oleh sistem yang memungkinkan pengguna menyusuri semua file yang disediakan aplikasi lain. Dari satu UI ini, pengguna bisa mengambil file dari aplikasi apa saja yang didukung.

{@link android.content.Intent#ACTION_OPEN_DOCUMENT} tidak dimaksudkan untuk menjadi pengganti {@link android.content.Intent#ACTION_GET_CONTENT}. Yang harus Anda gunakan bergantung pada kebutuhan aplikasi:

Bagian ini menjelaskan cara menulis aplikasi klien berdasarkan {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan intent {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.

Cuplikan berikut menggunakan {@link android.content.Intent#ACTION_OPEN_DOCUMENT} untuk mencari penyedia dokumen yang berisi file gambar:

private static final int READ_REQUEST_CODE = 42;
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
public void performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones)
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only images, using the image MIME data type.
    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
    // To search for all documents available via installed storage providers,
    // it would be "*/*".
    intent.setType("image/*");

    startActivityForResult(intent, READ_REQUEST_CODE);
}

Perhatikan yang berikut ini:

Memproses Hasil

Setelah pengguna memilih dokumen di picker, {@link android.app.Activity#onActivityResult onActivityResult()} akan dipanggil. URI yang menunjuk ke dokumen yang dipilih dimasukkan dalam parameter {@code resultData} . Ekstrak URI dengan {@link android.content.Intent#getData getData()}. Setelah mendapatkannya, Anda bisa menggunakannya untuk mengambil dokumen yang diinginkan pengguna. Misalnya :

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            Log.i(TAG, "Uri: " + uri.toString());
            showImage(uri);
        }
    }
}

Memeriksa metadata dokumen

Setelah Anda memiliki URI untuk dokumen, Anda akan mendapatkan akses ke metadatanya. Cuplikan ini memegang metadata sebuah dokumen yang disebutkan oleh URI, dan mencatatnya:

public void dumpImageMetaData(Uri uri) {

    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since we want
    // all fields for one document.
    Cursor cursor = getActivity().getContentResolver()
            .query(uri, null, null, null, null, null);

    try {
    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
    // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name".  This is
            // provider-specific, and might not necessarily be the file name.
            String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);

            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
            // If the size is unknown, the value stored is null.  But since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "unpredictable".  So as
            // a rule, check if it's null before assigning to an int.  This will
            // happen often:  The storage API allows for remote files, whose
            // size might not be locally known.
            String size = null;
            if (!cursor.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                size = cursor.getString(sizeIndex);
            } else {
                size = "Unknown";
            }
            Log.i(TAG, "Size: " + size);
        }
    } finally {
        cursor.close();
    }
}

Membuka dokumen

Setelah mendapatkan URI dokumen, Anda bisa membuka dokumen atau melakukan apa saja yang diinginkan padanya.

Bitmap

Berikut ini adalah contoh cara membuka {@link android.graphics.Bitmap}:

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}

Perhatikan bahwa Anda tidak boleh melakukan operasi ini pada thread UI. Lakukan hal ini di latar belakang , dengan menggunakan {@link android.os.AsyncTask}. Setelah membuka bitmap, Anda bisa menampilkannya dalam {@link android.widget.ImageView}.

Mendapatkan InputStream

Berikut ini adalah contoh cara mendapatkan {@link java.io.InputStream} dari URI. Dalam cuplikan ini , baris-baris file dibaca ke dalam sebuah string:

private String readTextFromUri(Uri uri) throws IOException {
    InputStream inputStream = getContentResolver().openInputStream(uri);
    BufferedReader reader = new BufferedReader(new InputStreamReader(
            inputStream));
    StringBuilder stringBuilder = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        stringBuilder.append(line);
    }
    fileInputStream.close();
    parcelFileDescriptor.close();
    return stringBuilder.toString();
}

Membuat dokumen baru

Aplikasi Anda bisa membuat dokumen baru dalam penyedia dokumen dengan menggunakan intent {@link android.content.Intent#ACTION_CREATE_DOCUMENT} . Untuk membuat file, Anda memberikan satu tipe MIME dan satu nama file pada intent, dan menjalankannya dengan kode permintaan yang unik. Selebihnya akan diurus untuk Anda:

// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");

// Unique request code.
private static final int WRITE_REQUEST_CODE = 43;
...
private void createFile(String mimeType, String fileName) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);

    // Filter to only show results that can be "opened", such as
    // a file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Create a file with the requested MIME type.
    intent.setType(mimeType);
    intent.putExtra(Intent.EXTRA_TITLE, fileName);
    startActivityForResult(intent, WRITE_REQUEST_CODE);
}

Setelah membuat dokumen baru, Anda bisa mendapatkan URI-nya dalam {@link android.app.Activity#onActivityResult onActivityResult()}, sehingga Anda bisa terus menulis ke dokumen itu.

Menghapus dokumen

Jika Anda memiliki URI dokumen dan {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} dokumen berisi {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, Anda bisa menghapus dokumen tersebut. Misalnya:

DocumentsContract.deleteDocument(getContentResolver(), uri);

Mengedit dokumen

Anda bisa menggunakan SAF untuk mengedit dokumen teks langsung di tempatnya. Cuplikan ini memicu intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan menggunakan kategori {@link android.content.Intent#CATEGORY_OPENABLE} untuk menampilkan dokumen yang bisa dibuka saja. Ini akan menyaring lebih jauh untuk menampilkan file teks saja:

private static final int EDIT_REQUEST_CODE = 44;
/**
 * Open a file for writing and append some text to it.
 */
 private void editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only text files.
    intent.setType("text/plain");

    startActivityForResult(intent, EDIT_REQUEST_CODE);
}

Berikutnya, dari {@link android.app.Activity#onActivityResult onActivityResult()} (lihat Memproses hasil) Anda bisa memanggil kode untuk mengedit. Cuplikan berikut mendapatkan {@link java.io.FileOutputStream} dari {@link android.content.ContentResolver}. Secara default, snipet menggunakan mode “tulis”. Inilah praktik terbaik untuk meminta jumlah akses minimum yang Anda perlukan, jadi jangan meminta baca/tulis jika yang Anda perlukan hanyalah tulis:

private void alterDocument(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getActivity().getContentResolver().
                openFileDescriptor(uri, "w");
        FileOutputStream fileOutputStream =
                new FileOutputStream(pfd.getFileDescriptor());
        fileOutputStream.write(("Overwritten by MyCloud at " +
                System.currentTimeMillis() + "\n").getBytes());
        // Let the document provider know you're done by closing the stream.
        fileOutputStream.close();
        pfd.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Mempertahankan izin

Bila aplikasi Anda membuka file untuk membaca atau menulis, sistem akan memberi aplikasi Anda izin URI untuk file itu. Pemberian ini berlaku hingga perangkat pengguna di-restart. Namun anggaplah aplikasi Anda adalah aplikasi pengeditan gambar, dan Anda ingin pengguna bisa mengakses 5 gambar terakhir yang dieditnya, langsung dari aplikasi Anda. Jika perangkat pengguna telah di-restart, maka Anda harus mengirim pengguna kembali ke picker sistem untuk menemukan file, hal ini jelas tidak ideal.

Untuk mencegah terjadinya hal ini, Anda bisa mempertahankan izin yang diberikan sistem ke aplikasi Anda. Secara efektif, aplikasi Anda akan "mengambil" pemberian izin URI yang bisa dipertahankan yang ditawarkan oleh sistem. Hal ini memberi pengguna akses kontinu ke file melalui aplikasi Anda, sekalipun perangkat telah di-restart:

final int takeFlags = intent.getFlags()
            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);

Ada satu langkah akhir. Anda mungkin telah menyimpan URI terbaru yang diakses aplikasi, namun URI itu mungkin tidak lagi valid,—aplikasi lain mungkin telah menghapus atau memodifikasi dokumen. Karena itu, Anda harus selalu memanggil {@code getContentResolver().takePersistableUriPermission()} untuk memeriksa data terbaru.

Menulis Penyedia Dokumen Custom

Jika Anda sedang mengembangkan aplikasi yang menyediakan layanan penyimpanan untuk file (misalnya layanan penyimpanan cloud), Anda bisa menyediakan file melalui SAF dengan menulis penyedia dokumen custom. Bagian ini menjelaskan caranya.

Manifes

Untuk mengimplementasikan penyedia dokumen custom, tambahkan yang berikut ini ke manifes aplikasi Anda:

Berikut ini adalah kutipan contoh manifes berisi penyedia yang:

<manifest... >
    ...
    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
        ....
        <provider
            android:name="com.example.android.storageprovider.MyCloudProvider"
            android:authorities="com.example.android.storageprovider.documents"
            android:grantUriPermissions="true"
            android:exported="true"
            android:permission="android.permission.MANAGE_DOCUMENTS"
            android:enabled="@bool/atLeastKitKat">
            <intent-filter>
                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
            </intent-filter>
        </provider>
    </application>

</manifest>

Mendukung perangkat yang menjalankan Android 4.3 dan yang lebih rendah

Intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT} hanya tersedia pada perangkat yang menjalankan Android 4.4 dan yang lebih tinggi. Jika ingin aplikasi Anda mendukung {@link android.content.Intent#ACTION_GET_CONTENT} untuk mengakomodasi perangkat yang menjalankan Android 4.3 dan yang lebih rendah, Anda harus menonaktifkan filter inten {@link android.content.Intent#ACTION_GET_CONTENT} dalam manifes untuk perangkat yang menjalankan Android 4.4 atau yang lebih tinggi. Penyedia dokumen dan {@link android.content.Intent#ACTION_GET_CONTENT} harus dianggap saling eksklusif. Jika Anda mendukung keduanya sekaligus, aplikasi Anda akan muncul dua kali dalam UI picker sistem, yang menawarkan dua cara mengakses data tersimpan Anda. Hal ini akan membingungkan pengguna.

Berikut ini adalah cara yang disarankan untuk menonaktifkan filter intent {@link android.content.Intent#ACTION_GET_CONTENT} untuk perangkat yang menjalankan Android versi 4.4 atau yang lebih tinggi:

  1. Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values/}, tambahkan baris ini:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values-v19/}, tambahkan baris ini:
    <bool name="atMostJellyBeanMR2">false</bool>
  3. Tambahkan alias aktivitas untuk menonaktifkan filter intent {@link android.content.Intent#ACTION_GET_CONTENT} bagi versi 4.4 (API level 19) dan yang lebih tinggi. Misalnya:
    <!-- This activity alias is added so that GET_CONTENT intent-filter
         can be disabled for builds on API level 19 and higher. -->
    <activity-alias android:name="com.android.example.app.MyPicker"
            android:targetActivity="com.android.example.app.MyActivity"
            ...
            android:enabled="@bool/atMostJellyBeanMR2">
        <intent-filter>
            <action android:name="android.intent.action.GET_CONTENT" />
            <category android:name="android.intent.category.OPENABLE" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="image/*" />
            <data android:mimeType="video/*" />
        </intent-filter>
    </activity-alias>
    

Kontrak

Biasanya bila Anda menulis penyedia konten custom, salah satu tugas adalah mengimplementasikan kelas kontrak, seperti dijelaskan dalam panduan pengembang Penyedia Konten. Kelas kontrak adalah kelas {@code public final} yang berisi definisi konstanta untuk URI, nama kolom, tipe MIME, dan metadata lain yang berkenaan dengan penyedia. SAF menyediakan kelas-kelas kontrak ini untuk Anda, jadi Anda tidak perlu menulisnya sendiri:

Misalnya, berikut ini adalah kolom-kolom yang bisa Anda hasilkan di kursor bila penyedia dokumen Anda membuat query dokumen atau akar:

private static final String[] DEFAULT_ROOT_PROJECTION =
        new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
        Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
        Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
        Root.COLUMN_AVAILABLE_BYTES,};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
        String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
        Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
        Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};

Subkelas DocumentsProvider

Langkah berikutnya dalam menulis penyedia dokumen custom adalah menjadikan kelas abstrak sebagai subkelas {@link android.provider.DocumentsProvider}. Setidaknya, Anda perlu mengimplementasikan metode berikut:

Hanya inilah metode yang diwajibkan kepada Anda secara ketat untuk diimplementasikan, namun ada banyak lagi yang mungkin Anda inginkan. Lihat {@link android.provider.DocumentsProvider} untuk detailnya.

Mengimplementasikan queryRoots

Implementasi {@link android.provider.DocumentsProvider#queryRoots queryRoots()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua direktori akar penyedia dokumen, dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Root}.

Dalam cuplikan berikut, parameter {@code projection} mewakili bidang-bidang tertentu yang ingin didapatkan kembali oleh pemanggil. Cuplikan ini membuat kursor baru dan menambahkan satu baris ke satu akar— kursor, satu direktori level atas, seperti Downloads atau Images. Kebanyakan penyedia hanya mempunyai satu akar. Anda bisa mempunyai lebih dari satu, misalnya, jika ada banyak akun pengguna. Dalam hal itu, cukup tambahkan sebuah baris kedua ke kursor.

@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {

    // Create a cursor with either the requested fields, or the default
    // projection if "projection" is null.
    final MatrixCursor result =
            new MatrixCursor(resolveRootProjection(projection));

    // If user is not logged in, return an empty root cursor.  This removes our
    // provider from the list entirely.
    if (!isUserLoggedIn()) {
        return result;
    }

    // It's possible to have multiple roots (e.g. for multiple accounts in the
    // same app) -- just add multiple cursor rows.
    // Construct one row for a root called "MyCloud".
    final MatrixCursor.RowBuilder row = result.newRow();
    row.add(Root.COLUMN_ROOT_ID, ROOT);
    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));

    // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
    // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
    // recently used documents will show up in the "Recents" category.
    // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
    // shares.
    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
            Root.FLAG_SUPPORTS_RECENTS |
            Root.FLAG_SUPPORTS_SEARCH);

    // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
    row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));

    // This document id cannot change once it's shared.
    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));

    // The child MIME types are used to filter the roots and only present to the
    //  user roots that contain the desired type somewhere in their file hierarchy.
    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);

    return result;
}

Mengimplementasikan queryChildDocuments

Implementasi {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua file dalam direktori yang ditentukan, dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Document}.

Metode ini akan dipanggil bila Anda memilih akar aplikasi dalam picker UI. Metode mengambil dokumen anak dari direktori di bawah akar. Metode ini bisa dipanggil pada level apa saja dalam hierarki file, bukan hanya akar. Cuplikan ini membuat kursor baru dengan kolom-kolom yang diminta, lalu menambahkan informasi tentang setiap anak langsung dalam direktori induk ke kursor. Satu anak bisa berupa gambar, direktori lain—file apa saja:

@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
                              String sortOrder) throws FileNotFoundException {

    final MatrixCursor result = new
            MatrixCursor(resolveDocumentProjection(projection));
    final File parent = getFileForDocId(parentDocumentId);
    for (File file : parent.listFiles()) {
        // Adds the file's display name, MIME type, size, and so on.
        includeFile(result, null, file);
    }
    return result;
}

Mengimplementasikan queryDocument

Implementasi {@link android.provider.DocumentsProvider#queryDocument queryDocument()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke file yang disebutkan, dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Document}.

Metode {@link android.provider.DocumentsProvider#queryDocument queryDocument()} menghasilkan informasi yang sama yang diteruskan dalam {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, namun untuk file tertentu:

@Override
public Cursor queryDocument(String documentId, String[] projection) throws
        FileNotFoundException {

    // Create a cursor with the requested projection, or the default projection.
    final MatrixCursor result = new
            MatrixCursor(resolveDocumentProjection(projection));
    includeFile(result, documentId, null);
    return result;
}

Mengimplementasikan openDocument

Anda harus mengimplementasikan {@link android.provider.DocumentsProvider#openDocument openDocument()} untuk menghasilkan {@link android.os.ParcelFileDescriptor} yang mewakili file yang disebutkan. Aplikasi lain bisa menggunakan {@link android.os.ParcelFileDescriptor} yang dihasilkan untuk mengalirkan data. Sistem memanggil metode ini setelah pengguna memilih file dan aplikasi klien meminta akses ke file itu dengan memanggil {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. Misalnya:

@Override
public ParcelFileDescriptor openDocument(final String documentId,
                                         final String mode,
                                         CancellationSignal signal) throws
        FileNotFoundException {
    Log.v(TAG, "openDocument, mode: " + mode);
    // It's OK to do network operations in this method to download the document,
    // as long as you periodically check the CancellationSignal. If you have an
    // extremely large file to transfer from the network, a better solution may
    // be pipes or sockets (see ParcelFileDescriptor for helper methods).

    final File file = getFileForDocId(documentId);

    final boolean isWrite = (mode.indexOf('w') != -1);
    if(isWrite) {
        // Attach a close listener if the document is opened in write mode.
        try {
            Handler handler = new Handler(getContext().getMainLooper());
            return ParcelFileDescriptor.open(file, accessMode, handler,
                        new ParcelFileDescriptor.OnCloseListener() {
                @Override
                public void onClose(IOException e) {

                    // Update the file with the cloud server. The client is done
                    // writing.
                    Log.i(TAG, "A file with id " +
                    documentId + " has been closed!
                    Time to " +
                    "update the server.");
                }

            });
        } catch (IOException e) {
            throw new FileNotFoundException("Failed to open document with id "
            + documentId + " and mode " + mode);
        }
    } else {
        return ParcelFileDescriptor.open(file, accessMode);
    }
}

Keamanan

Anggaplah penyedia dokumen Anda sebuah layanan penyimpanan cloud yang dilindungi kata sandi dan Anda ingin memastikan bahwa pengguna sudah login sebelum Anda mulai berbagi file mereka. Apakah yang harus dilakukan aplikasi Anda jika pengguna tidak login? Solusinya adalah menghasilkan akar nol dalam implementasi {@link android.provider.DocumentsProvider#queryRoots queryRoots()} Anda. Yakni, sebuah kursor akar kosong:

public Cursor queryRoots(String[] projection) throws FileNotFoundException {
...
    // If user is not logged in, return an empty root cursor.  This removes our
    // provider from the list entirely.
    if (!isUserLoggedIn()) {
        return result;
}

Langkah lainnya adalah memanggil {@code getContentResolver().notifyChange()}. Ingat {@link android.provider.DocumentsContract}? Kita menggunakannya untuk membuat URI ini. Cuplikan berikut memberi tahu sistem untuk membuat query akar penyedia dokumen Anda kapan saja status login pengguna berubah. Jika pengguna tidak login, panggilan ke {@link android.provider.DocumentsProvider#queryRoots queryRoots()} akan menghasilkan kursor kosong, seperti yang ditampilkan di atas. Cara ini akan memastikan bahwa dokumen penyedia hanya tersedia jika pengguna login ke penyedia itu.

private void onLoginButtonClick() {
    loginOrLogout();
    getContentResolver().notifyChange(DocumentsContract
            .buildRootsUri(AUTHORITY), null);
}