1page.title=Khuôn khổ Truy cập Kho lưu trữ
2@jd:body
3<div id="qv-wrapper">
4<div id="qv">
5
6<h2>Trong tài liệu này
7 <a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle">
8        <span class="more">hiện nhiều hơn</span>
9        <span class="less" style="display:none">hiện ít hơn</span></a></h2>
10<ol id="toc44" class="hide-nested">
11    <li>
12        <a href="#overview">Tổng quan</a>
13    </li>
14    <li>
15        <a href="#flow">Dòng Điều khiển</a>
16    </li>
17    <li>
18        <a href="#client">Ghi một Ứng dụng Máy khách</a>
19        <ol>
20        <li><a href="#search">Tìm kiếm tài liệu</a></li>
21        <li><a href="#process">Kết quả tiến trình</a></li>
22        <li><a href="#metadata">Kiểm tra siêu dữ liệu tài liệu</a></li>
23        <li><a href="#open">Mở một tài liệu</a></li>
24        <li><a href="#create">Tạo một tài liệu mới</a></li>
25        <li><a href="#delete">Xóa một tài liệu</a></li>
26        <li><a href="#edit">Chỉnh sửa một tài liệu</a></li>
27        <li><a href="#permissions">Cố định các quyền</a></li>
28        </ol>
29    </li>
30    <li><a href="#custom">Ghi một Trình cung cấp Tài liệu Tùy chỉnh</a>
31        <ol>
32        <li><a href="#manifest">Bản kê khai</a></li>
33        <li><a href="#contract">Hợp đồng</a></li>
34        <li><a href="#subclass">Phân lớp con DocumentsProvider</a></li>
35        <li><a href="#security">Bảo mật</a></li>
36        </ol>
37    </li>
38
39</ol>
40<h2>Lớp khóa</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">
50DevBytes: Khuôn khổ Truy cập Kho lưu trữ Android 4.4: Trình cung cấp</a></li>
51     <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ">
52DevBytes: Khuôn khổ Truy cập Kho lưu trữ Android 4.4: Máy khách</a></li>
53</ol>
54
55
56<h2>Mã Ví dụ</h2>
57
58<ol>
59    <li><a href="{@docRoot}samples/StorageProvider/index.html">
60Trình cung cấp Lưu trữ</a></li>
61     <li><a href="{@docRoot}samples/StorageClient/index.html">
62StorageClient</a></li>
63</ol>
64
65<h2>Xem thêm</h2>
66<ol>
67    <li>
68        <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
69        Nội dung Cơ bản về Trình cung cấp Nội dung
70        </a>
71    </li>
72</ol>
73
74</div>
75</div>
76
77
78<p>Android 4.4 (API mức 19) giới thiệu Khuôn khổ Truy cập Kho lưu trữ (SAF). SAF
79 giúp người dùng đơn giản hóa việc duyệt và mở tài liệu, hình ảnh và các tệp khác
80giữa tất cả trình cung cấp lưu trữ tài liệu mà họ thích. UI tiêu chuẩn, dễ sử dụng
81cho phép người dùng duyệt tệp và truy cập hoạt động gần đây một cách nhất quán giữa các ứng dụng và trình cung cấp.</p>
82
83<p>Dịch vụ lưu trữ đám mây hoặc cục bộ có thể tham gia vào hệ sinh thái này bằng cách triển khai một
84{@link android.provider.DocumentsProvider} để gói gọn các dịch vụ của mình. Những ứng dụng
85máy khách cần truy cập vào tài liệu của một trình cung cấp có thể tích hợp với SAF chỉ bằng một vài
86dòng mã.</p>
87
88<p>SAF bao gồm:</p>
89
90<ul>
91<li><strong>Trình cung cấp tài liệu</strong>&mdash;Một trình cung cấp nội dung cho phép một
92dịch vụ lưu trữ (chẳng hạn như Google Drive) phát hiện các tệp mà nó quản lý. Trình cung cấp tài liệu được
93triển khai thành một lớp con của lớp {@link android.provider.DocumentsProvider}.
94Sơ đồ tài liệu-trình cung cấp sẽ được dựa trên một phân cấp tệp truyền thống,
95cho dù cách thức trình cung cấp tài liệu của bạn trực tiếp lưu trữ dữ liệu là hoàn toàn do bạn.
96Nền tảng Android bao gồm một vài trình cung cấp tài liệu tích hợp, chẳng hạn như
97Downloads, Images, và Videos.</li>
98
99<li><strong>Ứng dụng máy khách</strong>&mdash;Một ứng dụng tùy chỉnh có chức năng gọi ra ý định
100{@link android.content.Intent#ACTION_OPEN_DOCUMENT} và/hoặc
101{@link android.content.Intent#ACTION_CREATE_DOCUMENT} và nhận các tệp
102được trả về bởi trình cung cấp tài liệu.</li>
103
104<li><strong>Bộ chọn</strong>&mdash;Một UI hệ thống cho phép người dùng truy cập tài liệu từ tất cả
105trình cung cấp tài liệu mà thỏa mãn các tiêu chí tìm kiếm của ứng dụng máy khách.</li>
106</ul>
107
108<p>Một số tính năng được SAF cung cấp bao gồm:</p>
109<ul>
110<li>Cho phép người dùng duyệt nội dung từ tất cả trình cung cấp tài liệu, không chỉ một ứng dụng duy nhất.</li>
111<li>Giúp ứng dụng của bạn có thể có quyền truy cập lâu dài, cố định vào
112 các tài liệu được sở hữu bởi một trình cung cấp tài liệu. Thông qua truy cập này, người dùng có thể thêm, chỉnh sửa,
113 lưu và xóa tệp trên trình cung cấp.</li>
114<li>Hỗ trợ nhiều tài khoản người dùng và các phần gốc tạm thời chẳng hạn như trình cung cấp
115bộ nhớ USB, nó chỉ xuất hiện nếu ổ đĩa được cắm vào. </li>
116</ul>
117
118<h2 id ="overview">Tổng quan</h2>
119
120<p>SAF tập trung xoay quanh một trình cung cấp nội dung là một lớp con
121của lớp {@link android.provider.DocumentsProvider}. Trong một <em>trình cung cấp tài liệu</em>, dữ liệu được
122cấu trúc thành một phân cấp tệp truyền thống:</p>
123<p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p>
124<p class="img-caption"><strong>Hình 1.</strong> Mô hình dữ liệu của trình cung cấp tài liệu. Một Phần gốc chỉ đến một Tài liệu duy nhất,
125sau đó nó bắt đầu xòe ra toàn bộ cây.</p>
126
127<p>Lưu ý điều sau đây:</p>
128<ul>
129
130<li>Mỗi một trình cung cấp tài liệu sẽ báo cáo một hoặc nhiều
131"phần gốc" là điểm bắt đầu khám phá cây tài liệu.
132Mỗi phần gốc có một {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} duy nhất,
133và nó trỏ đến một tài liệu (thư mục)
134biểu diễn nội dung bên dưới phần gốc đó.
135Phần gốc có thể linh hoạt theo thiết kế để hỗ trợ các trường hợp sử dụng như nhiều tài khoản,
136thiết bị lưu trữ USB tạm thời, hoặc đăng nhập/đăng xuất người dùng.</li>
137
138<li>Dưới mỗi phần gốc là một tài liệu đơn lẻ. Tài liệu đó sẽ trỏ tới 1 đến <em>N</em> tài liệu,
139mỗi tài liệu lại có thể trỏ tới 1 đến <em>N</em> tài liệu khác. </li>
140
141<li>Mỗi bộ nhớ phụ trợ phủ bề mặt
142các tệp và thư mục riêng lẻ bằng cách tham chiếu chúng bằng một
143{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} duy nhất.
144ID của tài liệu phải là duy nhất và không thay đổi sau khi được phát hành, do chúng được sử dụng để cấp URI
145không thay đổi giữa các lần khởi động lại thiết bị.</li>
146
147
148<li>Tài liệu có thể là một tệp mở được (có một kiểu MIME cụ thể), hoặc một
149thư mục chứa các tài liệu bổ sung (có kiểu MIME
150{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li>
151
152<li>Mỗi tài liệu có thể có các khả năng khác nhau như được mô tả bởi
153{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}.
154Ví dụ, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE},
155{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, và
156{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}.
157{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} cũng có thể
158có trong nhiều thư mục.</li>
159</ul>
160
161<h2 id="flow">Dòng Điều khiển</h2>
162<p>Như nêu trên, mô hình dữ liệu của trình cung cấp tài liệu được dựa trên một phân cấp
163tệp truyền thống. Tuy nhiên, bạn có thể thực tế lưu trữ dữ liệu của mình bằng bất kỳ cách nào mà mình thích, miễn
164là nó có thể được truy cập thông qua API {@link android.provider.DocumentsProvider}. Ví dụ, bạn
165có thể sử dụng kho lưu trữ đám mây dựa trên tag cho dữ liệu của mình.</p>
166
167<p>Hình 2 minh họa một ví dụ về cách mà một ứng dụng ảnh có thể sử dụng SAF
168để truy cập dữ liệu được lưu trữ:</p>
169<p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p>
170
171<p class="img-caption"><strong>Hình 2.</strong> Dòng Khuôn khổ Truy cập Kho lưu trữ</p>
172
173<p>Lưu ý điều sau đây:</p>
174<ul>
175
176<li>Trong SAF, trình cung cấp và máy khách không tương tác
177trực tiếp với nhau. Một máy khách yêu cầu quyền để tương tác
178với tệp (cụ thể là quyền đọc, chỉnh sửa, tạo hoặc xóa tệp).</li>
179
180<li>Tương tác bắt đầu khi một ứng dụng (trong ví dụ này này một ứng dụng ảnh) thể hiện ý định
181{@link android.content.Intent#ACTION_OPEN_DOCUMENT} hoặc {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Ý định có thể bao gồm các bộ lọc
182để cụ thể hơn các tiêu chí&mdash;ví dụ, "cấp cho tôi tất cả tệp mở được
183có kiểu MIME là 'image'."</li>
184
185<li>Sau khi ý định thể hiện, bộ chọn của hệ thống sẽ đi đến từng trình cung cấp được đăng ký
186và hiển thị cho người dùng xem các phần gốc nội dung khớp với tiêu chí.</li>
187
188<li>Bộ chọn cấp cho người dùng một giao diện tiêu chuẩn để truy cập tài liệu, mặc
189dù các trình cung cấp tài liệu liên quan có thể rất khác nhau. Ví dụ, hình 2
190minh họa một trình cung cấp Google Drive, một trình cung cấp USB, và một trình cung cấp đám mây.</li>
191</ul>
192
193<p>Hình 3 minh họa một bộ chọn mà trong đó một người dùng đang tìm kiếm hình ảnh đã chọn một
194tài khoản 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>Hình 3.</strong> Bộ chọn</p>
199
200<p>Khi người dùng chọn Google Drive, hình ảnh được hiển thị như minh họa trong
201hình 4. Từ điểm đó trở đi, người dùng có thể tương tác với chúng theo bất kỳ cách nào
202được hỗ trợ bởi trình cung cấp và ứng dụng máy khách.
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>Hình 4.</strong> Hình ảnh</p>
207
208<h2 id="client">Ghi một Ứng dụng Máy khách</h2>
209
210<p>Trên phiên bản Android 4.3 và thấp hơn, nếu bạn muốn ứng dụng của mình truy xuất một tệp từ một ứng dụng
211khác, nó phải gọi ra một ý định chẳng hạn như {@link android.content.Intent#ACTION_PICK}
212hay {@link android.content.Intent#ACTION_GET_CONTENT}. Khi đó, người dùng phải chọn
213một ứng dụng duy nhất mà từ đó họ chọn một tệp và ứng dụng được chọn phải cung cấp một
214giao diện người dùng để người dùng duyệt và chọn từ các tệp có sẵn. </p>
215
216<p>Trên phiên bản Android 4.4 trở lên, bạn có thêm một tùy chọn là sử dụng ý định
217{@link android.content.Intent#ACTION_OPEN_DOCUMENT},
218nó hiển thị một UI bộ chọn được điều khiển bởi hệ thống, cho phép người dùng
219duyệt tất cả tệp mà các ứng dụng khác đã cung cấp. Từ UI duy nhất này, người dùng
220có thể chọn một tệp từ bất kỳ ứng dụng nào được hỗ trợ.</p>
221
222<p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} không
223nhằm mục đích thay thế cho {@link android.content.Intent#ACTION_GET_CONTENT}.
224Bạn nên sử dụng cái nào sẽ phụ thuộc vào nhu cầu của ứng dụng của bạn:</p>
225
226<ul>
227<li>Sử dụng {@link android.content.Intent#ACTION_GET_CONTENT} nếu bạn muốn ứng dụng của mình
228chỉ đơn thuần đọc/nhập dữ liệu. Bằng cách này, ứng dụng nhập một bản sao dữ liệu,
229chẳng hạn như một tệp hình ảnh.</li>
230
231<li>Sử dụng {@link android.content.Intent#ACTION_OPEN_DOCUMENT} nếu bạn muốn ứng dụng
232của mình có quyền truy cập lâu dài, cố định vào các tài liệu được sở hữu bởi một
233trình cung cấp tài liệu. Ví dụ như trường hợp một ứng dụng chỉnh sửa ảnh cho phép người dùng chỉnh sửa
234các hình ảnh được lưu trữ trong một trình cung cấp tài liệu. </li>
235
236</ul>
237
238
239<p>Phần này mô tả cách ghi các ứng dụng máy khách dựa trên
240{@link android.content.Intent#ACTION_OPEN_DOCUMENT} và
241các ý định {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p>
242
243
244<h3 id="search">Tìm kiếm tài liệu</h3>
245
246<p>
247Đoạn mã HTML sau sử dụng {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
248để tìm kiếm các trình cung cấp tài liệu mà
249chứa tệp hình ảnh:</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 */
256public 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>Lưu ý điều sau đây:</p>
276<ul>
277<li>Khi ứng dụng thể hiện ý định {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
278, nó sẽ khởi chạy một bộ chọn để hiển thị tất cả trình cung cấp tài liệu khớp với tiêu chí.</li>
279
280<li>Thêm thể loại {@link android.content.Intent#CATEGORY_OPENABLE} vào
281ý định sẽ lọc kết quả để chỉ hiển thị những tài liệu có thể mở được, chẳng hạn như tệp hình ảnh.</li>
282
283<li>Câu lệnh {@code intent.setType("image/*")} sẽ lọc thêm để
284chỉ hiển thị những tài liệu có kiểu dữ liệu MIME hình ảnh.</li>
285</ul>
286
287<h3 id="results">Kết quả Tiến trình</h3>
288
289<p>Sau khi người dùng chọn một tài liệu trong bộ chọn,
290{@link android.app.Activity#onActivityResult onActivityResult()} sẽ được gọi.
291URI tro tới tài liệu được chọn sẽ nằm trong tham số {@code resultData}
292. Trích xuất UI bằng cách sử dụng {@link android.content.Intent#getData getData()}.
293Sau khi có nó, bạn có thể sử dụng nó để truy xuất tài liệu mà người dùng muốn. Ví
294dụ:</p>
295
296<pre>&#64;Override
297public 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">Kiểm tra siêu dữ liệu tài liệu</h3>
320
321<p>Sau khi có URI cho một tài liệu, bạn có quyền truy cập siêu dữ liệu của nó. Đoạn mã HTML
322này bắt siêu dữ liệu cho một tài liệu được quy định bởi URI, và ghi lại nó:</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">Mở một tài liệu</h3>
367
368<p>Sau khi có URI cho một tài liệu, bạn có thể mở nó hoặc làm bất kỳ điều gì
369mà bạn muốn.</p>
370
371<h4>Bitmap</h4>
372
373<p>Sau đây là một ví dụ về cách bạn có thể mở một {@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>Lưu ý rằng bạn không nên thực hiện thao tác này trên luồng UI. Thực hiện điều này dưới
386nền bằng cách sử dụng {@link android.os.AsyncTask}. Sau khi mở bitmap, bạn có thể
387hiển thị nó trong một {@link android.widget.ImageView}.
388</p>
389
390<h4>Nhận một InputStream</h4>
391
392<p>Sau đây là một ví dụ về cách mà bạn có thể nhận một {@link java.io.InputStream} từ URI. Trong đoạn mã HTML
393này, các dòng tệp đang được đọc thành một xâu:</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">Tạo một tài liệu mới</h3>
411
412<p>Ứng dụng của bạn có thể tạo một tài liệu mới trong một trình cung cấp tài liệu bằng cách sử dụng ý định
413{@link android.content.Intent#ACTION_CREATE_DOCUMENT}
414. Để tạo một tệp, bạn cấp cho ý định của mình một kiểu MIME và tên tệp, và
415khởi chạy nó bằng một mã yêu cầu duy nhất. Phần còn lại sẽ được làm hộ bạn:</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.
427private static final int WRITE_REQUEST_CODE = 43;
428...
429private 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>Sau khi tạo một tài liệu mới, bạn có thể nhận URI của tài liệu trong
444{@link android.app.Activity#onActivityResult onActivityResult()}, sao cho bạn
445có thể tiếp tục ghi nó.</p>
446
447<h3 id="delete">Xóa một tài liệu</h3>
448
449<p>Nếu bạn có URI cho một tài liệu và
450{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS}
451của tài liệu chứa
452{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},
453bạn có thể xóa tài liệu đó. Ví dụ:</p>
454
455<pre>
456DocumentsContract.deleteDocument(getContentResolver(), uri);
457</pre>
458
459<h3 id="edit">Chỉnh sửa một tài liệu</h3>
460
461<p>Bạn có thể sử dụng SAF để chỉnh sửa một tài liệu văn bản ngay tại chỗ.
462Đoạn mã HTML này thể hiện
463ý định {@link android.content.Intent#ACTION_OPEN_DOCUMENT} và sử dụng
464thể loại {@link android.content.Intent#CATEGORY_OPENABLE} để chỉ hiển thị
465những tài liệu có thể mở được. Nó lọc thêm để chỉ hiển thị những tệp văn bản:</p>
466
467<pre>
468private 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>Tiếp theo, từ {@link android.app.Activity#onActivityResult onActivityResult()}
489(xem <a href="#results">Kết quả tiến trình</a>) bạn có thể gọi mã để thực hiện chỉnh sửa.
490Đoạn mã HTML sau nhận được một {@link java.io.FileOutputStream}
491từ {@link android.content.ContentResolver}. Theo mặc định, nó sử dụng chế độ “ghi”.
492Cách tốt nhất là yêu cầu lượng quyền truy cập bạn cần ở mức ít nhất, vì thế đừng yêu cầu
493quyền đọc/ghi nếu bạn chỉ cần quyền ghi:</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">Cố định các quyền</h3>
514
515<p>Khi ứng dụng của bạn mở một tệp để đọc hoặc ghi, hệ thống sẽ cấp cho
516ứng dụng của bạn một quyền URI được cấp cho tệp đó. Quyền này sẽ kéo dài tới khi thiết bị của bạn khởi động lại.
517Nhưng giả sử ứng dụng của bạn là một ứng dụng chỉnh sửa hình ảnh, và bạn muốn người dùng có thể
518truy cập 5 hình ảnh cuối cùng mà họ đã chỉnh sửa, trực tiếp từ ứng dụng của bạn. Nếu thiết bị của người dùng
519đã khởi động lại, bạn sẽ phải gửi người dùng trở lại bộ chọn hệ thống để tìm
520các tệp đó, đây rõ ràng không phải là cách lý tưởng.</p>
521
522<p>Để tránh điều này xảy ra, bạn có thể cố định các quyền mà hệ thống
523cấp cho ứng dụng của bạn. Ứng dụng của bạn sẽ "nhận" cấp quyền URI có thể cố định
524mà hệ thống cung cấp một cách hiệu quả. Điều này cho phép người dùng có quyền liên tục truy cập các tệp đó
525thông qua ứng dụng của bạn, ngay cả khi thiết bị đã bị khởi động lại:</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.
532getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre>
533
534<p>Còn một bước cuối cùng. Bạn có thể đã lưu các
535URI gần đây nhất mà ứng dụng của bạn đã truy cập, nhưng chúng còn thể không còn hợp lệ&mdash;một ứng dụng khác
536có thể đã xóa hoặc sửa đổi tài liệu. Vì thế, bạn luôn nên gọi
537{@code getContentResolver().takePersistableUriPermission()} để kiểm tra
538dữ liệu mới nhất.</p>
539
540<h2 id="custom">Ghi một Trình cung cấp Tài liệu Tùy chỉnh</h2>
541
542<p>
543Nếu bạn đang phát triển một ứng dụng cung cấp dịch vụ lưu trữ cho tệp (chẳng hạn như
544một dịch vụ lưu trữ đám mây), bạn có thể cung cấp các tệp của mình thông qua
545SAF bằng cách ghi một trình cung cấp tài liệu tùy chỉnh.  Phần này mô tả cách làm điều
546này.</p>
547
548
549<h3 id="manifest">Bản kê khai</h3>
550
551<p>Để triển khai một trình cung cấp tài liệu tùy chỉnh, hãy thêm nội dung sau vào bản kê khai
552của ứng dụng của bạn:</p>
553<ul>
554
555<li>Một mục tiêu API mức 19 hoặc cao hơn.</li>
556
557<li>Một phần tử <code>&lt;provider&gt;</code> khai báo trình cung cấp lưu trữ
558tùy chỉnh của bạn. </li>
559
560<li>Tên của trình cung cấp của bạn, là tên lớp của nó, bao gồm tên gói.
561Ví dụ: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li>
562
563<li>Tên thẩm quyền của bạn, tức là tên gói của bạn (trong ví dụ này là
564<code>com.example.android.storageprovider</code>) cộng với kiểu của trình cung cấp nội dung
565(<code>documents</code>). Ví dụ, {@code com.example.android.storageprovider.documents}.</li>
566
567<li>Thuộc tính <code>android:exported</code> được đặt thành <code>&quot;true&quot;</code>.
568Bạn phải xuất trình cung cấp của mình để các ứng dụng khác có thể thấy nó.</li>
569
570<li>Thuộc tính <code>android:grantUriPermissions</code> được đặt thành
571<code>&quot;true&quot;</code>. Thiết đặt này cho phép hệ thống cấp cho các ứng dụng khác quyền truy cập
572vào nội dung trong trình cung cấp của bạn. Để thảo luận về cách cố định quyền được cấp cho
573một tài liệu cụ thể, hãy xem phần<a href="#permissions">Cố định các quyền</a>.</li>
574
575<li>Quyền {@code MANAGE_DOCUMENTS}. Theo mặc định, một trình cung cấp sẽ có sẵn
576đối với mọi người. Việc thêm quyền này sẽ hạn chế trình cung cấp của bạn vào hệ thống.
577Hạn chế này có ý nghĩa quan trọng đối với vấn đề bảo mật.</li>
578
579<li>Thuộc tính {@code android:enabled} được đặt thành một giá trị boolean được định nghĩa trong một tệp
580tài nguyên. Mục đích của thuộc tính này là để vô hiệu hóa trình cung cấp trên các thiết bị chạy phiên bản Android 4.3 hoặc thấp hơn.
581Ví dụ, {@code android:enabled="@bool/atLeastKitKat"}. Bên
582cạnh việc nêu thuộc tính này trong bản kê khai, bạn cần làm như sau:
583<ul>
584<li>Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values/}, hãy thêm
585dòng sau: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;false&lt;/bool&gt;</pre></li>
586
587<li>Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values-v19/}, hãy thêm
588dòng sau: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;true&lt;/bool&gt;</pre></li>
589</ul></li>
590
591<li>Một bộ lọc ý định chứa hành động
592{@code android.content.action.DOCUMENTS_PROVIDER}, sao cho trình cung cấp của bạn
593xuất hiện trong bộ chọn khi hệ thống tìm kiếm trình cung cấp.</li>
594
595</ul>
596<p>Sau đây là các đoạn trích từ một bản kê khai mẫu chứa một trình cung cấp:</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">Hỗ trợ các thiết bị chạy phiên bản Android 4.3 và thấp hơn</h4>
620
621<p>Ý định
622{@link android.content.Intent#ACTION_OPEN_DOCUMENT} chỉ có sẵn
623trên các thiết bị chạy phiên bản Android 4.4 trở lên.
624Nếu bạn muốn ứng dụng của mình hỗ trợ {@link android.content.Intent#ACTION_GET_CONTENT}
625để tạo điều kiện cho các thiết bị đang chạy phiên bản Android 4.3 và thấp hơn, bạn nên
626vô hiệu hóa bộ lọc ý định {@link android.content.Intent#ACTION_GET_CONTENT} trong
627bản kê khai của bạn cho các thiết bị chạy phiên bản Android 4.4 trở lên. Một
628trình cung cấp tài liệu và {@link android.content.Intent#ACTION_GET_CONTENT} nên được xem xét
629 loại trừ lẫn nhau. Nếu bạn hỗ trợ cả hai đồng thời, ứng dụng của bạn sẽ
630xuất hiện hai lần trong UI của bộ chọn hệ thống, đưa ra hai cách khác nhau để truy cập
631dữ liệu đã lưu của bạn. Điều này có thể khiến người dùng bị nhầm lẫn.</p>
632
633<p>Sau đây là cách được khuyến cáo để vô hiệu hóa bộ lọc ý định
634{@link android.content.Intent#ACTION_GET_CONTENT} đối với các thiết bị
635chạy phiên bản Android 4.4 hoặc cao hơn:</p>
636
637<ol>
638<li>Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values/}, hãy thêm
639dòng sau: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;true&lt;/bool&gt;</pre></li>
640
641<li>Trong tệp tài nguyên {@code bool.xml} của bạn bên dưới {@code res/values-v19/}, hãy thêm
642dòng sau: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;false&lt;/bool&gt;</pre></li>
643
644<li>Thêm một
645<a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">bí danh
646hoạt động</a> để vô hiệu hóa bộ lọc ý định {@link android.content.Intent#ACTION_GET_CONTENT}
647đối với các phiên bản 4.4 (API mức 19) trở lên. Ví dụ:
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">Hợp đồng</h3>
668
669<p>Thường khi bạn ghi một trình cung cấp nội dung tùy chỉnh, một trong những tác vụ đó là
670triển khai các lớp hợp đồng như được mô tả trong hướng dẫn cho nhà phát triển
671<a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass">
672Trình cung cấp Nội dung</a>. Lớp hợp đồng là một lớp {@code public final} mà
673chứa các định nghĩa hằng số cho URI, tên cột, kiểu MIME và
674siêu dữ liệu khác liên quan tới trình cung cấp. SAF
675cung cấp những lớp hợp đồng này cho bạn, vì thế bạn không cần tự
676ghi:</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>Ví dụ, sau đây là các cột bạn có thể trả về trong một con chạy khi
684trình cung cấp tài liệu của bạn được truy vấn về tài liệu hoặc phần gốc:</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,};
691private 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">Phân lớp con DocumentsProvider</h3>
698
699<p>Bước tiếp theo trong khi ghi một trình cung cấp tài liệu tùy chỉnh đó là phân lớp con
700cho lớp tóm tắt {@link android.provider.DocumentsProvider}. Tối thiểu, bạn cần triển khai
701các phương pháp sau:</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>Đây là những phương pháp duy nhất mà bạn được yêu cầu phải triển khai, nhưng còn
714nhiều phương pháp nữa mà bạn có thể muốn triển khai. Xem {@link android.provider.DocumentsProvider}
715để biết chi tiết.</p>
716
717<h4 id="queryRoots">Triển khai queryRoots</h4>
718
719<p>Việc bạn triển khai {@link android.provider.DocumentsProvider#queryRoots
720queryRoots()} phải trả về một {@link android.database.Cursor} trỏ về tất cả
721thư mục gốc trong trình cung cấp tài liệu của bạn, bằng cách sử dụng các cột được định nghĩa trong
722{@link android.provider.DocumentsContract.Root}.</p>
723
724<p>Trong đoạn mã HTML sau, tham số {@code projection} biểu diễn các trường cụ thể
725mà hàm gọi muốn nhận về. Đoạn mã HTML tạo một con chạy mới
726và thêm một hàng vào nó&mdash;một thư mục gốc, mức cao nhất, như
727Downloads hoặc Images.  Hầu hết các trình cung cấp chỉ có một phần gốc. Bạn có thể có nhiều hơn một,
728ví dụ, trong trường hợp nhiều tài khoản người dùng. Trong trường hợp đó, chỉ cần thêm một
729hàng thứ hai vào con chạy.</p>
730
731<pre>
732&#64;Override
733public 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">Triển khai queryChildDocuments</h4>
778
779<p>Việc bạn triển khai
780{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
781phải trả về một {@link android.database.Cursor} mà chỉ đến tất cả tệp trong
782thư mục được chỉ định, bằng cách sử dụng các cột được định nghĩa trong
783{@link android.provider.DocumentsContract.Document}.</p>
784
785<p>Phương pháp này được gọi khi bạn chọn một thư mục gốc ứng dụng trong UI bộ chọn.
786Nó nhận được tài liệu con của một thư mục nằm dưới phần gốc.  Nó có thể được gọi ở bất kỳ mức nào trong phân cấp tệp
787, không chỉ phần gốc. Đoạn mã HTML
788này tạo một con chạy mới bằng các cột được yêu cầu, sau đó thêm thông tin về
789mọi tệp con trực tiếp trong thư mục mẹ vào con chạy.
790Tệp con có thể là một hình ảnh, một thư mục khác&mdash;bất kỳ tệp nào:</p>
791
792<pre>&#64;Override
793public 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">Triển khai queryDocument</h4>
808
809<p>Việc bạn triển khai
810{@link android.provider.DocumentsProvider#queryDocument queryDocument()}
811phải trả về một {@link android.database.Cursor} mà chỉ đến tệp được chỉ định,
812bằng cách sử dụng các cột được định nghĩa trong {@link android.provider.DocumentsContract.Document}.
813</p>
814
815<p>Phương pháp {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
816trả về cùng thông tin đã được chuyển trong
817{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()},
818nhưng là đối với một tệp cụ thể:</p>
819
820
821<pre>&#64;Override
822public 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">Triển khai openDocument</h4>
834
835<p>Bạn phải triển khai {@link android.provider.DocumentsProvider#openDocument
836openDocument()} để trả về một {@link android.os.ParcelFileDescriptor} biểu diễn
837tệp được chỉ định. Các ứng dụng khác có thể sử dụng {@link android.os.ParcelFileDescriptor}
838được trả về để truyền phát dữ liệu. Hệ thống gọi phương pháp này sau khi người dùng chọn một tệp
839và ứng dụng máy khách yêu cầu truy cập nó bằng cách gọi
840{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}.
841Ví dụ:</p>
842
843<pre>&#64;Override
844public 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">Bảo mật</h3>
886
887<p>Giả sử trình cung cấp tài liệu của bạn là một dịch vụ lưu trữ đám mây được bảo vệ bằng mật khẩu
888và bạn muốn đảm bảo rằng người dùng được đăng nhập trước khi bạn bắt đầu chia sẻ tệp của họ.
889Ứng dụng của bạn nên làm gì nếu người dùng không đăng nhập?  Giải pháp là trả về
890phần gốc 0 trong triển khai {@link android.provider.DocumentsProvider#queryRoots
891queryRoots()} của bạn. Cụ thể là một con chạy gốc trống:</p>
892
893<pre>
894public 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>Bước còn lại là gọi {@code getContentResolver().notifyChange()}.
904Bạn còn nhớ {@link android.provider.DocumentsContract} chứ?  Chúng ta đang sử dụng nó để tạo
905URI này. Đoạn mã HTML sau báo cho hệ thống truy vấn các phần gốc trong
906trình cung cấp tài liệu của bạn bất cứ khi nào trạng thái đăng nhập của người dùng thay đổi. Nếu người dùng không được
907đăng nhập, lệnh gọi tới {@link android.provider.DocumentsProvider#queryRoots queryRoots()} sẽ trả về một
908con chạy trống như minh họa bên trên. Điều này đảm bảo rằng tài liệu của một trình cung cấp chỉ
909có sẵn nếu người dùng đăng nhập vào trình cung cấp đó.</p>
910
911<pre>private void onLoginButtonClick() {
912    loginOrLogout();
913    getContentResolver().notifyChange(DocumentsContract
914            .buildRootsUri(AUTHORITY), null);
915}
916</pre>