Home | History | Annotate | Download | only in providers
      1 page.title=Storage Access Framework
      2 @jd:body
      3 <div id="qv-wrapper">
      4 <div id="qv">
      5 
      6 <h2>Dalam dokumen ini
      7  <a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle">
      8         <span class="more">tampilkan maksimal</span>
      9         <span class="less" style="display:none">tampilkan minimal</span></a></h2>
     10 <ol id="toc44" class="hide-nested">
     11     <li>
     12         <a href="#overview">Ikhtisar</a>
     13     </li>
     14     <li>
     15         <a href="#flow">Arus Kontrol</a>
     16     </li>
     17     <li>
     18         <a href="#client">Menulis Aplikasi Klien</a>
     19         <ol>
     20         <li><a href="#search">Mencari dokumen</a></li>
     21         <li><a href="#process">Memproses hasil</a></li>
     22         <li><a href="#metadata">Memeriksa metadata dokumen</a></li>
     23         <li><a href="#open">Membuka dokumen</a></li>
     24         <li><a href="#create">Membuat dokumen baru</a></li>
     25         <li><a href="#delete">Menghapus dokumen</a></li>
     26         <li><a href="#edit">Mengedit dokumen</a></li>
     27         <li><a href="#permissions">Mempertahankan izin</a></li>
     28         </ol>
     29     </li>
     30     <li><a href="#custom">Menulis Penyedia Dokumen Custom</a>
     31         <ol>
     32         <li><a href="#manifest">Manifes</a></li>
     33         <li><a href="#contract">Kontrak</a></li>
     34         <li><a href="#subclass">Subkelas DocumentsProvider</a></li>
     35         <li><a href="#security">Keamanan</a></li>
     36         </ol>
     37     </li>
     38 
     39 </ol>
     40 <h2>Kelas-kelas utama</h2>
     41 <ol>
     42     <li>{@link android.provider.DocumentsProvider}</li>
     43     <li>{@link android.provider.DocumentsContract}</li>
     44 </ol>
     45 
     46 <h2>Video</h2>
     47 
     48 <ol>
     49     <li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4">
     50 DevBytes: Android 4.4 Storage Access Framework: Penyedia</a></li>
     51      <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ">
     52 DevBytes: Android 4.4 Storage Access Framework: Klien</a></li>
     53 </ol>
     54 
     55 
     56 <h2>Contoh Kode</h2>
     57 
     58 <ol>
     59     <li><a href="{@docRoot}samples/StorageProvider/index.html">
     60 Penyedia Penyimpanan</a></li>
     61      <li><a href="{@docRoot}samples/StorageClient/index.html">
     62 Klien Penyimpanan</a></li>
     63 </ol>
     64 
     65 <h2>Lihat Juga</h2>
     66 <ol>
     67     <li>
     68         <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
     69         Dasar-Dasar Penyedia Konten
     70         </a>
     71     </li>
     72 </ol>
     73 
     74 </div>
     75 </div>
     76 
     77 
     78 <p>Android 4.4 (API level 19) memperkenalkan Storage Access Framework (SAF, Kerangka Kerja Akses Penyimpanan). SAF
     79  memudahkan pengguna menyusuri dan membuka dokumen, gambar, dan file lainnya
     80 di semua penyedia penyimpanan dokumen pilihannya. UI standar yang mudah digunakan
     81 memungkinkan pengguna menyusuri file dan mengakses yang terbaru dengan cara konsisten di antara berbagai aplikasi dan penyedia.</p>
     82 
     83 <p>Layanan penyimpanan cloud atau lokal bisa dilibatkan dalam ekosistem ini dengan mengimplementasikan sebuah
     84 {@link android.provider.DocumentsProvider} yang membungkus layanannya. Aplikasi klien
     85 yang memerlukan akses ke dokumen sebuah penyedia bisa berintegrasi dengan SAF cukup dengan beberapa
     86 baris kode.</p>
     87 
     88 <p>SAF terdiri dari berikut ini:</p>
     89 
     90 <ul>
     91 <li><strong>Penyedia dokumen</strong>&mdash;Penyedia konten yang memungkinkan
     92 layanan penyimpanan (seperti Google Drive) untuk menampilkan file yang dikelolanya. Penyedia dokumen
     93 diimplementasikan sebagai subkelas dari kelas {@link android.provider.DocumentsProvider}.
     94 Skema penyedia dokumen berdasarkan hierarki file biasa,
     95 walaupun cara penyedia dokumen Anda secara fisik menyimpan data adalah terserah Anda.
     96 Platform Android terdiri dari beberapa penyedia dokumen bawaan, seperti
     97 Downloads, Images, dan Videos.</li>
     98 
     99 <li><strong>Aplikasi klien</strong>&mdash;Aplikasi custom yang memanggil intent 
    100 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan/atau
    101 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} dan menerima
    102 file yang dihasilkan penyedia dokumen.</li>
    103 
    104 <li><strong>Picker</strong>&mdash;UI sistem yang memungkinkan pengguna mengakses dokumen dari semua
    105 penyedia dokumen yang memenuhi kriteria pencarian aplikasi klien.</li>
    106 </ul>
    107 
    108 <p>Beberapa fitur yang disediakan oleh SAF adalah sebagai berikut:</p>
    109 <ul>
    110 <li>Memungkinkan pengguna menyusuri konten dari semua penyedia dokumen, bukan hanya satu aplikasi.</li>
    111 <li>Memungkinkan aplikasi Anda memiliki akses jangka panjang dan tetap ke
    112  dokumen yang dimiliki oleh penyedia dokumen. Melalui akses ini pengguna bisa menambah, mengedit,
    113  menyimpan, dan menghapus file pada penyedia.</li>
    114 <li>Mendukung banyak akun pengguna dan akar jangka pendek seperti penyedia penyimpanan
    115 USB, yang hanya muncul jika drive itu dipasang. </li>
    116 </ul>
    117 
    118 <h2 id ="overview">Ikhtisar</h2>
    119 
    120 <p>SAF berpusat di seputar penyedia konten yang merupakan
    121 subkelas dari kelas {@link android.provider.DocumentsProvider}. Dalam <em>penyedia dokumen</em>, data
    122 distrukturkan sebagai hierarki file biasa:</p>
    123 <p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p>
    124 <p class="img-caption"><strong>Gambar 1.</strong> Model data penyedia dokumen. Root menunjuk ke satu Document,
    125 yang nanti memulai pemekaran seluruh pohon.</p>
    126 
    127 <p>Perhatikan yang berikut ini:</p>
    128 <ul>
    129 
    130 <li>Setiap penyedia dokumen melaporkan satu atau beberapa
    131 "akar" yang merupakan titik awal penyusuran pohon dokumen.
    132 Masing-masing akar memiliki sebuah {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} yang unik,
    133 dan menunjuk ke satu dokumen (satu direktori)
    134 yang mewakili konten di bawah akar itu.
    135 Akar sengaja dibuat dinamis untuk mendukung kasus penggunaan seperti multiakun,
    136 perangkat penyimpanan USB jangka pendek, atau masuk/keluar pengguna.</li>
    137 
    138 <li>Di bawah tiap akar terdapat satu dokumen. Dokumen itu menunjuk ke dokumen-dokumen 1-ke-<em>N</em>,
    139 yang nanti masing-masing bisa menunjuk ke dokumen 1-ke-<em>N</em>. </li>
    140 
    141 <li>Tiap backend penyimpanan memunculkan
    142 masing-masing file dan direktori dengan mengacunya lewat sebuah
    143 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} yang unik.
    144 ID dokumen harus unik dan tidak berubah setelah dibuat, karena ID ini digunakan untuk
    145 URI persisten yang diberikan pada saat reboot perangkat.</li>
    146 
    147 
    148 <li>Dokumen bisa berupa file yang bisa dibuka (dengan tipe MIME tertentu), atau
    149 direktori yang berisi dokumen tambahan (dengan tipe MIME
    150 {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li>
    151 
    152 <li>Tiap dokumen bisa mempunyai kemampuan berbeda, sebagaimana yang dijelaskan oleh
    153 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}.
    154 Misalnya, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE},
    155 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, dan
    156 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}.
    157 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} yang sama bisa
    158 dimasukkan dalam beberapa direktori.</li>
    159 </ul>
    160 
    161 <h2 id="flow">Arus Kontrol</h2>
    162 <p>Seperti dinyatakan di atas, model data penyedia dokumen dibuat berdasarkan hierarki file
    163 biasa. Akan tetapi, Anda bisa menyimpan secara fisik data dengan cara apa pun yang disukai,
    164 selama data bisa diakses melalui API {@link android.provider.DocumentsProvider}. Misalnya, Anda
    165 bisa menggunakan penyimpanan cloud berbasis tag untuk data Anda.</p>
    166 
    167 <p>Gambar 2 menampilkan contoh cara aplikasi foto bisa menggunakan SAF
    168 untuk mengakses data tersimpan:</p>
    169 <p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p>
    170 
    171 <p class="img-caption"><strong>Gambar 2.</strong> Arus Storage Access Framework</p>
    172 
    173 <p>Perhatikan yang berikut ini:</p>
    174 <ul>
    175 
    176 <li>Di SAF, penyedia dan klien tidak berinteraksi
    177 secara langsung. Klien meminta izin untuk berinteraksi
    178 dengan file (yakni, membaca, mengedit, membuat, atau menghapus file).</li>
    179 
    180 <li>Interaksi dimulai bila sebuah aplikasi (dalam contoh ini adalah aplikasi foto) mengeluarkan intent
    181 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} atau {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Intent bisa berisi filter
    182 untuk mempersempit kriteria&mdash;misalnya, "beri saya semua file yang bisa dibuka
    183 yang memiliki tipe MIME 'gambar'".</li>
    184 
    185 <li>Setelah intent dibuat, picker sistem akan pergi ke setiap penyedia yang terdaftar
    186 dan menunjukkan kepada pengguna akar konten yang cocok.</li>
    187 
    188 <li>Picker memberi pengguna antarmuka standar untuk mengakses dokumen,
    189 walaupun penyedia dokumen dasar bisa sangat berbeda. Misalnya, gambar 2
    190 menunjukkan penyedia Google Drive, penyedia USB, dan penyedia cloud.</li>
    191 </ul>
    192 
    193 <p>Gambar 3 menunjukkan picker yang di digunakan pengguna mencari gambar telah memilih
    194 akun Google Drive:</p>
    195 
    196 <p><img src="{@docRoot}images/providers/storage_picker.png" width="340" alt="picker" style="border:2px solid #ddd" /></p>
    197 
    198 <p class="img-caption"><strong>Gambar 3.</strong> Picker</p>
    199 
    200 <p>Bila pengguna memilih Google Drive, gambar-gambar akan ditampilkan, seperti yang ditampilkan dalam
    201 gambar 4. Dari titik itu, pengguna bisa berinteraksi dengan gambar dengan cara apa pun
    202 yang didukung oleh penyedia dan aplikasi klien.
    203 
    204 <p><img src="{@docRoot}images/providers/storage_photos.png" width="340" alt="picker" style="border:2px solid #ddd" /></p>
    205 
    206 <p class="img-caption"><strong>Gambar 4.</strong> Gambar</p>
    207 
    208 <h2 id="client">Menulis Aplikasi Klien</h2>
    209 
    210 <p>Pada Android 4.3 dan yang lebih rendah, jika Anda ingin aplikasi mengambil file dari
    211 aplikasi lain, aplikasi Anda harus memanggil intent seperti {@link android.content.Intent#ACTION_PICK}
    212 atau {@link android.content.Intent#ACTION_GET_CONTENT}. Pengguna nanti harus memilih
    213 satu aplikasi yang akan digunakan untuk mengambil file dan aplikasi yang dipilih harus menyediakan antarmuka pengguna
    214 bagi untuk menyusuri dan mengambil dari file yang tersedia. </p>
    215 
    216 <p>Pada Android 4.4 dan yang lebih tinggi, Anda mempunyai opsi tambahan dalam menggunakan intent
    217 {@link android.content.Intent#ACTION_OPEN_DOCUMENT},
    218 yang menampilkan UI picker yang dikontrol oleh sistem yang memungkinkan pengguna
    219 menyusuri semua file yang disediakan aplikasi lain. Dari satu UI ini, pengguna
    220 bisa mengambil file dari aplikasi apa saja yang didukung.</p>
    221 
    222 <p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT}
    223 tidak dimaksudkan untuk menjadi pengganti {@link android.content.Intent#ACTION_GET_CONTENT}.
    224 Yang harus Anda gunakan bergantung pada kebutuhan aplikasi:</p>
    225 
    226 <ul>
    227 <li>Gunakan {@link android.content.Intent#ACTION_GET_CONTENT} jika Anda ingin aplikasi
    228 cuma membaca/mengimpor data. Dengan pendekatan ini, aplikasi akan mengimpor salinan data,
    229 misalnya file gambar.</li>
    230 
    231 <li>Gunakan {@link android.content.Intent#ACTION_OPEN_DOCUMENT} jika Anda ingin aplikasi
    232 memiliki akses jangka panjang dan jangka pendek ke dokumen yang dimiliki oleh penyedia
    233 dokumen. Contohnya adalah aplikasi pengeditan foto yang memungkinkan pengguna mengedit
    234 gambar yang tersimpan dalam penyedia dokumen. </li>
    235 
    236 </ul>
    237 
    238 
    239 <p>Bagian ini menjelaskan cara menulis aplikasi klien berdasarkan
    240 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan
    241 intent {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p>
    242 
    243 
    244 <h3 id="search">Mencari dokumen</h3>
    245 
    246 <p>
    247 Cuplikan berikut menggunakan {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
    248 untuk mencari penyedia dokumen yang
    249 berisi file gambar:</p>
    250 
    251 <pre>private static final int READ_REQUEST_CODE = 42;
    252 ...
    253 /**
    254  * Fires an intent to spin up the &quot;file chooser&quot; UI and select an image.
    255  */
    256 public void performFileSearch() {
    257 
    258     // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    259     // browser.
    260     Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    261 
    262     // Filter to only show results that can be &quot;opened&quot;, such as a
    263     // file (as opposed to a list of contacts or timezones)
    264     intent.addCategory(Intent.CATEGORY_OPENABLE);
    265 
    266     // Filter to show only images, using the image MIME data type.
    267     // If one wanted to search for ogg vorbis files, the type would be &quot;audio/ogg&quot;.
    268     // To search for all documents available via installed storage providers,
    269     // it would be &quot;*/*&quot;.
    270     intent.setType(&quot;image/*&quot;);
    271 
    272     startActivityForResult(intent, READ_REQUEST_CODE);
    273 }</pre>
    274 
    275 <p>Perhatikan yang berikut ini:</p>
    276 <ul>
    277 <li>Saat aplikasi mengeluarkan intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
    278 , aplikasi akan menjalankan picker yang menampilkan semua penyedia dokumen yang cocok.</li>
    279 
    280 <li>Menambahkan kategori {@link android.content.Intent#CATEGORY_OPENABLE} ke
    281 intent akan menyaring hasil agar hanya menampilkan dokumen yang bisa dibuka, seperti file gambar.</li>
    282 
    283 <li>Pernyataan {@code intent.setType("image/*")} menyaring lebih jauh agar hanya
    284 menampilkan dokumen yang memiliki tipe data MIME gambar.</li>
    285 </ul>
    286 
    287 <h3 id="results">Memproses Hasil</h3>
    288 
    289 <p>Setelah pengguna memilih dokumen di picker,
    290 {@link android.app.Activity#onActivityResult onActivityResult()} akan dipanggil.
    291 URI yang menunjuk ke dokumen yang dipilih dimasukkan dalam parameter {@code resultData}
    292 . Ekstrak URI dengan {@link android.content.Intent#getData getData()}.
    293 Setelah mendapatkannya, Anda bisa menggunakannya untuk mengambil dokumen yang diinginkan pengguna. Misalnya
    294 :</p>
    295 
    296 <pre>&#64;Override
    297 public void onActivityResult(int requestCode, int resultCode,
    298         Intent resultData) {
    299 
    300     // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    301     // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    302     // response to some other intent, and the code below shouldn't run at all.
    303 
    304     if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
    305         // The document selected by the user won't be returned in the intent.
    306         // Instead, a URI to that document will be contained in the return intent
    307         // provided to this method as a parameter.
    308         // Pull that URI using resultData.getData().
    309         Uri uri = null;
    310         if (resultData != null) {
    311             uri = resultData.getData();
    312             Log.i(TAG, "Uri: " + uri.toString());
    313             showImage(uri);
    314         }
    315     }
    316 }
    317 </pre>
    318 
    319 <h3 id="metadata">Memeriksa metadata dokumen</h3>
    320 
    321 <p>Setelah Anda memiliki URI untuk dokumen, Anda akan mendapatkan akses ke metadatanya. Cuplikan
    322 ini memegang metadata sebuah dokumen yang disebutkan oleh URI, dan mencatatnya:</p>
    323 
    324 <pre>public void dumpImageMetaData(Uri uri) {
    325 
    326     // The query, since it only applies to a single document, will only return
    327     // one row. There's no need to filter, sort, or select fields, since we want
    328     // all fields for one document.
    329     Cursor cursor = getActivity().getContentResolver()
    330             .query(uri, null, null, null, null, null);
    331 
    332     try {
    333     // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
    334     // &quot;if there's anything to look at, look at it&quot; conditionals.
    335         if (cursor != null &amp;&amp; cursor.moveToFirst()) {
    336 
    337             // Note it's called &quot;Display Name&quot;.  This is
    338             // provider-specific, and might not necessarily be the file name.
    339             String displayName = cursor.getString(
    340                     cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
    341             Log.i(TAG, &quot;Display Name: &quot; + displayName);
    342 
    343             int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
    344             // If the size is unknown, the value stored is null.  But since an
    345             // int can't be null in Java, the behavior is implementation-specific,
    346             // which is just a fancy term for &quot;unpredictable&quot;.  So as
    347             // a rule, check if it's null before assigning to an int.  This will
    348             // happen often:  The storage API allows for remote files, whose
    349             // size might not be locally known.
    350             String size = null;
    351             if (!cursor.isNull(sizeIndex)) {
    352                 // Technically the column stores an int, but cursor.getString()
    353                 // will do the conversion automatically.
    354                 size = cursor.getString(sizeIndex);
    355             } else {
    356                 size = &quot;Unknown&quot;;
    357             }
    358             Log.i(TAG, &quot;Size: &quot; + size);
    359         }
    360     } finally {
    361         cursor.close();
    362     }
    363 }
    364 </pre>
    365 
    366 <h3 id="open-client">Membuka dokumen</h3>
    367 
    368 <p>Setelah mendapatkan URI dokumen, Anda bisa membuka dokumen atau melakukan apa saja
    369 yang diinginkan padanya.</p>
    370 
    371 <h4>Bitmap</h4>
    372 
    373 <p>Berikut ini adalah contoh cara membuka {@link android.graphics.Bitmap}:</p>
    374 
    375 <pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    376     ParcelFileDescriptor parcelFileDescriptor =
    377             getContentResolver().openFileDescriptor(uri, "r");
    378     FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    379     Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    380     parcelFileDescriptor.close();
    381     return image;
    382 }
    383 </pre>
    384 
    385 <p>Perhatikan bahwa Anda tidak boleh melakukan operasi ini pada thread UI. Lakukan hal ini di latar belakang
    386 , dengan menggunakan {@link android.os.AsyncTask}. Setelah membuka bitmap, Anda
    387 bisa menampilkannya dalam {@link android.widget.ImageView}.
    388 </p>
    389 
    390 <h4>Mendapatkan InputStream</h4>
    391 
    392 <p>Berikut ini adalah contoh cara mendapatkan {@link java.io.InputStream} dari URI. Dalam cuplikan ini
    393 , baris-baris file dibaca ke dalam sebuah string:</p>
    394 
    395 <pre>private String readTextFromUri(Uri uri) throws IOException {
    396     InputStream inputStream = getContentResolver().openInputStream(uri);
    397     BufferedReader reader = new BufferedReader(new InputStreamReader(
    398             inputStream));
    399     StringBuilder stringBuilder = new StringBuilder();
    400     String line;
    401     while ((line = reader.readLine()) != null) {
    402         stringBuilder.append(line);
    403     }
    404     fileInputStream.close();
    405     parcelFileDescriptor.close();
    406     return stringBuilder.toString();
    407 }
    408 </pre>
    409 
    410 <h3 id="create">Membuat dokumen baru</h3>
    411 
    412 <p>Aplikasi Anda bisa membuat dokumen baru dalam penyedia dokumen dengan menggunakan intent
    413 {@link android.content.Intent#ACTION_CREATE_DOCUMENT}
    414 . Untuk membuat file, Anda memberikan satu tipe MIME dan satu nama file pada intent, dan
    415 menjalankannya dengan kode permintaan yang unik. Selebihnya akan diurus untuk Anda:</p>
    416 
    417 
    418 <pre>
    419 // Here are some examples of how you might call this method.
    420 // The first parameter is the MIME type, and the second parameter is the name
    421 // of the file you are creating:
    422 //
    423 // createFile("text/plain", "foobar.txt");
    424 // createFile("image/png", "mypicture.png");
    425 
    426 // Unique request code.
    427 private static final int WRITE_REQUEST_CODE = 43;
    428 ...
    429 private void createFile(String mimeType, String fileName) {
    430     Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    431 
    432     // Filter to only show results that can be &quot;opened&quot;, such as
    433     // a file (as opposed to a list of contacts or timezones).
    434     intent.addCategory(Intent.CATEGORY_OPENABLE);
    435 
    436     // Create a file with the requested MIME type.
    437     intent.setType(mimeType);
    438     intent.putExtra(Intent.EXTRA_TITLE, fileName);
    439     startActivityForResult(intent, WRITE_REQUEST_CODE);
    440 }
    441 </pre>
    442 
    443 <p>Setelah membuat dokumen baru, Anda bisa mendapatkan URI-nya dalam
    444 {@link android.app.Activity#onActivityResult onActivityResult()}, sehingga Anda
    445 bisa terus menulis ke dokumen itu.</p>
    446 
    447 <h3 id="delete">Menghapus dokumen</h3>
    448 
    449 <p>Jika Anda memiliki URI dokumen dan 
    450 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS}
    451  dokumen berisi
    452 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},
    453 Anda bisa menghapus dokumen tersebut. Misalnya:</p>
    454 
    455 <pre>
    456 DocumentsContract.deleteDocument(getContentResolver(), uri);
    457 </pre>
    458 
    459 <h3 id="edit">Mengedit dokumen</h3>
    460 
    461 <p>Anda bisa menggunakan SAF untuk mengedit dokumen teks langsung di tempatnya.
    462 Cuplikan ini memicu
    463 intent {@link android.content.Intent#ACTION_OPEN_DOCUMENT} dan menggunakan
    464 kategori {@link android.content.Intent#CATEGORY_OPENABLE} untuk menampilkan
    465 dokumen yang bisa dibuka saja. Ini akan menyaring lebih jauh untuk menampilkan file teks saja:</p>
    466 
    467 <pre>
    468 private static final int EDIT_REQUEST_CODE = 44;
    469 /**
    470  * Open a file for writing and append some text to it.
    471  */
    472  private void editDocument() {
    473     // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    474     // file browser.
    475     Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    476 
    477     // Filter to only show results that can be &quot;opened&quot;, such as a
    478     // file (as opposed to a list of contacts or timezones).
    479     intent.addCategory(Intent.CATEGORY_OPENABLE);
    480 
    481     // Filter to show only text files.
    482     intent.setType(&quot;text/plain&quot;);
    483 
    484     startActivityForResult(intent, EDIT_REQUEST_CODE);
    485 }
    486 </pre>
    487 
    488 <p>Berikutnya, dari {@link android.app.Activity#onActivityResult onActivityResult()}
    489 (lihat <a href="#results">Memproses hasil</a>) Anda bisa memanggil kode untuk mengedit.
    490 Cuplikan berikut mendapatkan {@link java.io.FileOutputStream}
    491 dari {@link android.content.ContentResolver}. Secara default, snipet menggunakan mode tulis.
    492 Inilah praktik terbaik untuk meminta jumlah akses minimum yang Anda perlukan, jadi jangan meminta
    493 baca/tulis jika yang Anda perlukan hanyalah tulis:</p>
    494 
    495 <pre>private void alterDocument(Uri uri) {
    496     try {
    497         ParcelFileDescriptor pfd = getActivity().getContentResolver().
    498                 openFileDescriptor(uri, "w");
    499         FileOutputStream fileOutputStream =
    500                 new FileOutputStream(pfd.getFileDescriptor());
    501         fileOutputStream.write(("Overwritten by MyCloud at " +
    502                 System.currentTimeMillis() + "\n").getBytes());
    503         // Let the document provider know you're done by closing the stream.
    504         fileOutputStream.close();
    505         pfd.close();
    506     } catch (FileNotFoundException e) {
    507         e.printStackTrace();
    508     } catch (IOException e) {
    509         e.printStackTrace();
    510     }
    511 }</pre>
    512 
    513 <h3 id="permissions">Mempertahankan izin</h3>
    514 
    515 <p>Bila aplikasi Anda membuka file untuk membaca atau menulis, sistem akan memberi
    516 aplikasi Anda izin URI untuk file itu. Pemberian ini berlaku hingga perangkat pengguna di-restart.
    517 Namun anggaplah aplikasi Anda adalah aplikasi pengeditan gambar, dan Anda ingin pengguna bisa
    518 mengakses 5 gambar terakhir yang dieditnya, langsung dari aplikasi Anda. Jika perangkat pengguna telah
    519 di-restart, maka Anda harus mengirim pengguna kembali ke picker sistem untuk menemukan
    520 file, hal ini jelas tidak ideal.</p>
    521 
    522 <p>Untuk mencegah terjadinya hal ini, Anda bisa mempertahankan izin yang diberikan
    523 sistem ke aplikasi Anda. Secara efektif, aplikasi Anda akan "mengambil" pemberian izin URI yang bisa dipertahankan
    524 yang ditawarkan oleh sistem. Hal ini memberi pengguna akses kontinu ke file
    525 melalui aplikasi Anda, sekalipun perangkat telah di-restart:</p>
    526 
    527 
    528 <pre>final int takeFlags = intent.getFlags()
    529             &amp; (Intent.FLAG_GRANT_READ_URI_PERMISSION
    530             | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    531 // Check for the freshest data.
    532 getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre>
    533 
    534 <p>Ada satu langkah akhir. Anda mungkin telah menyimpan
    535 URI terbaru yang diakses aplikasi, namun URI itu mungkin tidak lagi valid,&mdash;aplikasi lain
    536 mungkin telah menghapus atau memodifikasi dokumen. Karena itu, Anda harus selalu memanggil
    537 {@code getContentResolver().takePersistableUriPermission()} untuk memeriksa
    538 data terbaru.</p>
    539 
    540 <h2 id="custom">Menulis Penyedia Dokumen Custom</h2>
    541 
    542 <p>
    543 Jika Anda sedang mengembangkan aplikasi yang menyediakan layanan penyimpanan untuk file (misalnya
    544 layanan penyimpanan cloud), Anda bisa menyediakan file melalui
    545 SAF dengan menulis penyedia dokumen custom.  Bagian ini menjelaskan
    546 caranya.</p>
    547 
    548 
    549 <h3 id="manifest">Manifes</h3>
    550 
    551 <p>Untuk mengimplementasikan penyedia dokumen custom, tambahkan yang berikut ini ke manifes aplikasi
    552 Anda:</p>
    553 <ul>
    554 
    555 <li>Target berupa API level 19 atau yang lebih tinggi.</li>
    556 
    557 <li>Elemen <code>&lt;provider&gt;</code> yang mendeklarasikan penyedia penyimpanan custom
    558 Anda. </li>
    559 
    560 <li>Nama penyedia Anda, yaitu nama kelasnya, termasuk nama paket.
    561 Misalnya: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li>
    562 
    563 <li>Nama otoritas Anda, yaitu nama paket Anda (dalam contoh ini,
    564 <code>com.example.android.storageprovider</code>) plus tipe penyedia konten
    565 (<code>documents</code>). Misalnya, {@code com.example.android.storageprovider.documents}.</li>
    566 
    567 <li>Atribut <code>android:exported</code> yang diatur ke <code>&quot;true&quot;</code>.
    568 Anda harus mengekspor penyedia sehingga aplikasi lain bisa membacanya.</li>
    569 
    570 <li>Atribut <code>android:grantUriPermissions</code> yang diatur ke
    571 <code>&quot;true&quot;</code>. Pengaturan ini memungkinkan sistem memberi aplikasi lain akses
    572 ke konten dalam penyedia Anda. Untuk pembahasan cara mempertahankan pemberian bagi
    573 dokumen tertentu, lihat <a href="#permissions">Mempertahankan izin</a>.</li>
    574 
    575 <li>Izin {@code MANAGE_DOCUMENTS}. Secara default, penyedia tersedia
    576 bagi siapa saja. Menambahkan izin ini akan membatasi penyedia Anda pada sistem.
    577 Pembatasan ini penting untuk keamanan.</li>
    578 
    579 <li>Atribut {@code android:enabled} yang diatur ke nilai boolean didefinisikan dalam file
    580 sumber daya. Tujuan atribut ini adalah menonaktifkan penyedia pada perangkat yang menjalankan Android 4.3 atau yang lebih rendah.
    581 Misalnya, {@code android:enabled="@bool/atLeastKitKat"}. Selain
    582 memasukkan atribut ini dalam manifes, Anda perlu melakukan hal-hal berikut:
    583 <ul>
    584 <li>Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values/}, tambahkan
    585 baris ini: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;false&lt;/bool&gt;</pre></li>
    586 
    587 <li>Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values-v19/}, tambahkan
    588 baris ini: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;true&lt;/bool&gt;</pre></li>
    589 </ul></li>
    590 
    591 <li>Sebuah filter intent berisi tindakan
    592 {@code android.content.action.DOCUMENTS_PROVIDER}, agar penyedia Anda
    593 muncul dalam picker saat sistem mencari penyedia.</li>
    594 
    595 </ul>
    596 <p>Berikut ini adalah kutipan contoh manifes berisi penyedia yang:</p>
    597 
    598 <pre>&lt;manifest... &gt;
    599     ...
    600     &lt;uses-sdk
    601         android:minSdkVersion=&quot;19&quot;
    602         android:targetSdkVersion=&quot;19&quot; /&gt;
    603         ....
    604         &lt;provider
    605             android:name=&quot;com.example.android.storageprovider.MyCloudProvider&quot;
    606             android:authorities=&quot;com.example.android.storageprovider.documents&quot;
    607             android:grantUriPermissions=&quot;true&quot;
    608             android:exported=&quot;true&quot;
    609             android:permission=&quot;android.permission.MANAGE_DOCUMENTS&quot;
    610             android:enabled=&quot;&#64;bool/atLeastKitKat&quot;&gt;
    611             &lt;intent-filter&gt;
    612                 &lt;action android:name=&quot;android.content.action.DOCUMENTS_PROVIDER&quot; /&gt;
    613             &lt;/intent-filter&gt;
    614         &lt;/provider&gt;
    615     &lt;/application&gt;
    616 
    617 &lt;/manifest&gt;</pre>
    618 
    619 <h4 id="43">Mendukung perangkat yang menjalankan Android 4.3 dan yang lebih rendah</h4>
    620 
    621 <p>Intent
    622 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} hanya tersedia
    623 pada perangkat yang menjalankan Android 4.4 dan yang lebih tinggi.
    624 Jika ingin aplikasi Anda mendukung {@link android.content.Intent#ACTION_GET_CONTENT}
    625 untuk mengakomodasi perangkat yang menjalankan Android 4.3 dan yang lebih rendah, Anda harus
    626 menonaktifkan filter inten {@link android.content.Intent#ACTION_GET_CONTENT} dalam
    627 manifes untuk perangkat yang menjalankan Android 4.4 atau yang lebih tinggi. Penyedia
    628 dokumen dan {@link android.content.Intent#ACTION_GET_CONTENT} harus dianggap
    629 saling eksklusif. Jika Anda mendukung keduanya sekaligus, aplikasi Anda akan
    630 muncul dua kali dalam UI picker sistem, yang menawarkan dua cara mengakses
    631 data tersimpan Anda. Hal ini akan membingungkan pengguna.</p>
    632 
    633 <p>Berikut ini adalah cara yang disarankan untuk menonaktifkan
    634 filter intent {@link android.content.Intent#ACTION_GET_CONTENT} untuk perangkat
    635 yang menjalankan Android versi 4.4 atau yang lebih tinggi:</p>
    636 
    637 <ol>
    638 <li>Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values/}, tambahkan
    639 baris ini: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;true&lt;/bool&gt;</pre></li>
    640 
    641 <li>Dalam file sumber daya {@code bool.xml} Anda di bawah {@code res/values-v19/}, tambahkan
    642 baris ini: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;false&lt;/bool&gt;</pre></li>
    643 
    644 <li>Tambahkan
    645 <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">alias
    646 aktivitas</a> untuk menonaktifkan filter intent {@link android.content.Intent#ACTION_GET_CONTENT}
    647 bagi versi 4.4 (API level 19) dan yang lebih tinggi. Misalnya:
    648 
    649 <pre>
    650 &lt;!-- This activity alias is added so that GET_CONTENT intent-filter
    651      can be disabled for builds on API level 19 and higher. --&gt;
    652 &lt;activity-alias android:name=&quot;com.android.example.app.MyPicker&quot;
    653         android:targetActivity=&quot;com.android.example.app.MyActivity&quot;
    654         ...
    655         android:enabled=&quot;@bool/atMostJellyBeanMR2&quot;&gt;
    656     &lt;intent-filter&gt;
    657         &lt;action android:name=&quot;android.intent.action.GET_CONTENT&quot; /&gt;
    658         &lt;category android:name=&quot;android.intent.category.OPENABLE&quot; /&gt;
    659         &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;
    660         &lt;data android:mimeType=&quot;image/*&quot; /&gt;
    661         &lt;data android:mimeType=&quot;video/*&quot; /&gt;
    662     &lt;/intent-filter&gt;
    663 &lt;/activity-alias&gt;
    664 </pre>
    665 </li>
    666 </ol>
    667 <h3 id="contract">Kontrak</h3>
    668 
    669 <p>Biasanya bila Anda menulis penyedia konten custom, salah satu tugas adalah
    670 mengimplementasikan kelas kontrak, seperti dijelaskan dalam panduan pengembang
    671 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass">
    672 Penyedia Konten</a>. Kelas kontrak adalah kelas {@code public final}
    673 yang berisi definisi konstanta untuk URI, nama kolom, tipe MIME, dan
    674 metadata lain yang berkenaan dengan penyedia. SAF
    675 menyediakan kelas-kelas kontrak ini untuk Anda, jadi Anda tidak perlu menulisnya
    676 sendiri:</p>
    677 
    678 <ul>
    679    <li>{@link android.provider.DocumentsContract.Document}</li>
    680    <li>{@link android.provider.DocumentsContract.Root}</li>
    681 </ul>
    682 
    683 <p>Misalnya, berikut ini adalah kolom-kolom yang bisa Anda hasilkan di kursor bila
    684 penyedia dokumen Anda membuat query dokumen atau akar:</p>
    685 
    686 <pre>private static final String[] DEFAULT_ROOT_PROJECTION =
    687         new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
    688         Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
    689         Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
    690         Root.COLUMN_AVAILABLE_BYTES,};
    691 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
    692         String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
    693         Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
    694         Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
    695 </pre>
    696 
    697 <h3 id="subclass">Subkelas DocumentsProvider</h3>
    698 
    699 <p>Langkah berikutnya dalam menulis penyedia dokumen custom adalah menjadikan
    700 kelas abstrak sebagai subkelas {@link android.provider.DocumentsProvider}. Setidaknya, Anda perlu
    701  mengimplementasikan metode berikut:</p>
    702 
    703 <ul>
    704 <li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li>
    705 
    706 <li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li>
    707 
    708 <li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li>
    709 
    710 <li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li>
    711 </ul>
    712 
    713 <p>Hanya inilah metode yang diwajibkan kepada Anda secara ketat untuk diimplementasikan, namun ada
    714 banyak lagi yang mungkin Anda inginkan. Lihat {@link android.provider.DocumentsProvider}
    715 untuk detailnya.</p>
    716 
    717 <h4 id="queryRoots">Mengimplementasikan queryRoots</h4>
    718 
    719 <p>Implementasi {@link android.provider.DocumentsProvider#queryRoots
    720 queryRoots()} oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua
    721 direktori akar penyedia dokumen, dengan menggunakan kolom-kolom yang didefinisikan dalam
    722 {@link android.provider.DocumentsContract.Root}.</p>
    723 
    724 <p>Dalam cuplikan berikut, parameter {@code projection} mewakili bidang-bidang
    725 tertentu yang ingin didapatkan kembali oleh pemanggil. Cuplikan ini membuat kursor baru
    726 dan menambahkan satu baris ke satu akar&mdash; kursor, satu direktori level atas, seperti
    727 Downloads atau Images.  Kebanyakan penyedia hanya mempunyai satu akar. Anda bisa mempunyai lebih dari satu,
    728 misalnya, jika ada banyak akun pengguna. Dalam hal itu, cukup tambahkan sebuah
    729 baris kedua ke kursor.</p>
    730 
    731 <pre>
    732 &#64;Override
    733 public Cursor queryRoots(String[] projection) throws FileNotFoundException {
    734 
    735     // Create a cursor with either the requested fields, or the default
    736     // projection if "projection" is null.
    737     final MatrixCursor result =
    738             new MatrixCursor(resolveRootProjection(projection));
    739 
    740     // If user is not logged in, return an empty root cursor.  This removes our
    741     // provider from the list entirely.
    742     if (!isUserLoggedIn()) {
    743         return result;
    744     }
    745 
    746     // It's possible to have multiple roots (e.g. for multiple accounts in the
    747     // same app) -- just add multiple cursor rows.
    748     // Construct one row for a root called &quot;MyCloud&quot;.
    749     final MatrixCursor.RowBuilder row = result.newRow();
    750     row.add(Root.COLUMN_ROOT_ID, ROOT);
    751     row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
    752 
    753     // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
    754     // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
    755     // recently used documents will show up in the &quot;Recents&quot; category.
    756     // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
    757     // shares.
    758     row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
    759             Root.FLAG_SUPPORTS_RECENTS |
    760             Root.FLAG_SUPPORTS_SEARCH);
    761 
    762     // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
    763     row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
    764 
    765     // This document id cannot change once it's shared.
    766     row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
    767 
    768     // The child MIME types are used to filter the roots and only present to the
    769     //  user roots that contain the desired type somewhere in their file hierarchy.
    770     row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
    771     row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
    772     row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
    773 
    774     return result;
    775 }</pre>
    776 
    777 <h4 id="queryChildDocuments">Mengimplementasikan queryChildDocuments</h4>
    778 
    779 <p>Implementasi
    780 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
    781 olehAnda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke semua file dalam
    782 direktori yang ditentukan, dengan menggunakan kolom-kolom yang didefinisikan dalam
    783 {@link android.provider.DocumentsContract.Document}.</p>
    784 
    785 <p>Metode ini akan dipanggil bila Anda memilih akar aplikasi dalam picker UI.
    786 Metode mengambil dokumen anak dari direktori di bawah akar.  Metode ini bisa dipanggil pada level apa saja dalam
    787 hierarki file, bukan hanya akar. Cuplikan ini
    788 membuat kursor baru dengan kolom-kolom yang diminta, lalu menambahkan informasi tentang
    789 setiap anak langsung dalam direktori induk ke kursor.
    790 Satu anak bisa berupa gambar, direktori lain&mdash;file apa saja:</p>
    791 
    792 <pre>&#64;Override
    793 public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
    794                               String sortOrder) throws FileNotFoundException {
    795 
    796     final MatrixCursor result = new
    797             MatrixCursor(resolveDocumentProjection(projection));
    798     final File parent = getFileForDocId(parentDocumentId);
    799     for (File file : parent.listFiles()) {
    800         // Adds the file's display name, MIME type, size, and so on.
    801         includeFile(result, null, file);
    802     }
    803     return result;
    804 }
    805 </pre>
    806 
    807 <h4 id="queryDocument">Mengimplementasikan queryDocument</h4>
    808 
    809 <p>Implementasi
    810 {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
    811 oleh Anda harus menghasilkan {@link android.database.Cursor} yang menunjuk ke file yang disebutkan,
    812 dengan menggunakan kolom-kolom yang didefinisikan dalam {@link android.provider.DocumentsContract.Document}.
    813 </p>
    814 
    815 <p>Metode {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
    816 menghasilkan informasi yang sama yang diteruskan dalam
    817 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()},
    818 namun untuk file tertentu:</p>
    819 
    820 
    821 <pre>&#64;Override
    822 public Cursor queryDocument(String documentId, String[] projection) throws
    823         FileNotFoundException {
    824 
    825     // Create a cursor with the requested projection, or the default projection.
    826     final MatrixCursor result = new
    827             MatrixCursor(resolveDocumentProjection(projection));
    828     includeFile(result, documentId, null);
    829     return result;
    830 }
    831 </pre>
    832 
    833 <h4 id="openDocument">Mengimplementasikan openDocument</h4>
    834 
    835 <p>Anda harus mengimplementasikan {@link android.provider.DocumentsProvider#openDocument
    836 openDocument()} untuk menghasilkan {@link android.os.ParcelFileDescriptor} yang mewakili
    837 file yang disebutkan. Aplikasi lain bisa menggunakan {@link android.os.ParcelFileDescriptor}
    838 yang dihasilkan untuk mengalirkan data. Sistem memanggil metode ini setelah pengguna memilih file
    839 dan aplikasi klien meminta akses ke file itu dengan memanggil
    840 {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}.
    841 Misalnya:</p>
    842 
    843 <pre>&#64;Override
    844 public ParcelFileDescriptor openDocument(final String documentId,
    845                                          final String mode,
    846                                          CancellationSignal signal) throws
    847         FileNotFoundException {
    848     Log.v(TAG, &quot;openDocument, mode: &quot; + mode);
    849     // It's OK to do network operations in this method to download the document,
    850     // as long as you periodically check the CancellationSignal. If you have an
    851     // extremely large file to transfer from the network, a better solution may
    852     // be pipes or sockets (see ParcelFileDescriptor for helper methods).
    853 
    854     final File file = getFileForDocId(documentId);
    855 
    856     final boolean isWrite = (mode.indexOf('w') != -1);
    857     if(isWrite) {
    858         // Attach a close listener if the document is opened in write mode.
    859         try {
    860             Handler handler = new Handler(getContext().getMainLooper());
    861             return ParcelFileDescriptor.open(file, accessMode, handler,
    862                         new ParcelFileDescriptor.OnCloseListener() {
    863                 &#64;Override
    864                 public void onClose(IOException e) {
    865 
    866                     // Update the file with the cloud server. The client is done
    867                     // writing.
    868                     Log.i(TAG, &quot;A file with id &quot; +
    869                     documentId + &quot; has been closed!
    870                     Time to &quot; +
    871                     &quot;update the server.&quot;);
    872                 }
    873 
    874             });
    875         } catch (IOException e) {
    876             throw new FileNotFoundException(&quot;Failed to open document with id &quot;
    877             + documentId + &quot; and mode &quot; + mode);
    878         }
    879     } else {
    880         return ParcelFileDescriptor.open(file, accessMode);
    881     }
    882 }
    883 </pre>
    884 
    885 <h3 id="security">Keamanan</h3>
    886 
    887 <p>Anggaplah penyedia dokumen Anda sebuah layanan penyimpanan cloud yang dilindungi kata sandi
    888 dan Anda ingin memastikan bahwa pengguna sudah login sebelum Anda mulai berbagi file mereka.
    889 Apakah yang harus dilakukan aplikasi Anda jika pengguna tidak login?  Solusinya adalah menghasilkan
    890 akar nol dalam implementasi {@link android.provider.DocumentsProvider#queryRoots
    891 queryRoots()} Anda. Yakni, sebuah kursor akar kosong:</p>
    892 
    893 <pre>
    894 public Cursor queryRoots(String[] projection) throws FileNotFoundException {
    895 ...
    896     // If user is not logged in, return an empty root cursor.  This removes our
    897     // provider from the list entirely.
    898     if (!isUserLoggedIn()) {
    899         return result;
    900 }
    901 </pre>
    902 
    903 <p>Langkah lainnya adalah memanggil {@code getContentResolver().notifyChange()}.
    904 Ingat {@link android.provider.DocumentsContract}?  Kita menggunakannya untuk membuat
    905 URI ini. Cuplikan berikut memberi tahu sistem untuk membuat query akar penyedia dokumen Anda
    906 kapan saja status login pengguna berubah. Jika pengguna tidak
    907 login, panggilan ke {@link android.provider.DocumentsProvider#queryRoots queryRoots()} akan menghasilkan
    908 kursor kosong, seperti yang ditampilkan di atas. Cara ini akan memastikan bahwa dokumen penyedia hanya
    909 tersedia jika pengguna login ke penyedia itu.</p>
    910 
    911 <pre>private void onLoginButtonClick() {
    912     loginOrLogout();
    913     getContentResolver().notifyChange(DocumentsContract
    914             .buildRootsUri(AUTHORITY), null);
    915 }
    916 </pre>