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>—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>—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>—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í—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 "file chooser" 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 "opened", 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 "audio/ogg". 268 // To search for all documents available via installed storage providers, 269 // it would be "*/*". 270 intent.setType("image/*"); 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>@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 // "if there's anything to look at, look at it" conditionals. 335 if (cursor != null && cursor.moveToFirst()) { 336 337 // Note it's called "Display Name". 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, "Display Name: " + 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 "unpredictable". 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 = "Unknown"; 357 } 358 Log.i(TAG, "Size: " + 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 "opened", 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 "opened", 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("text/plain"); 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 & (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ệ—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><provider></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>"true"</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>"true"</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><bool name="atLeastKitKat">false</bool></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><bool name="atLeastKitKat">true</bool></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><manifest... > 599 ... 600 <uses-sdk 601 android:minSdkVersion="19" 602 android:targetSdkVersion="19" /> 603 .... 604 <provider 605 android:name="com.example.android.storageprovider.MyCloudProvider" 606 android:authorities="com.example.android.storageprovider.documents" 607 android:grantUriPermissions="true" 608 android:exported="true" 609 android:permission="android.permission.MANAGE_DOCUMENTS" 610 android:enabled="@bool/atLeastKitKat"> 611 <intent-filter> 612 <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 613 </intent-filter> 614 </provider> 615 </application> 616 617</manifest></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><bool name="atMostJellyBeanMR2">true</bool></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><bool name="atMostJellyBeanMR2">false</bool></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<!-- This activity alias is added so that GET_CONTENT intent-filter 651 can be disabled for builds on API level 19 and higher. --> 652<activity-alias android:name="com.android.example.app.MyPicker" 653 android:targetActivity="com.android.example.app.MyActivity" 654 ... 655 android:enabled="@bool/atMostJellyBeanMR2"> 656 <intent-filter> 657 <action android:name="android.intent.action.GET_CONTENT" /> 658 <category android:name="android.intent.category.OPENABLE" /> 659 <category android:name="android.intent.category.DEFAULT" /> 660 <data android:mimeType="image/*" /> 661 <data android:mimeType="video/*" /> 662 </intent-filter> 663</activity-alias> 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ó—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@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 "MyCloud". 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 "Recents" 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—bất kỳ tệp nào:</p> 791 792<pre>@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>@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>@Override 844public ParcelFileDescriptor openDocument(final String documentId, 845 final String mode, 846 CancellationSignal signal) throws 847 FileNotFoundException { 848 Log.v(TAG, "openDocument, mode: " + 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 @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, "A file with id " + 869 documentId + " has been closed! 870 Time to " + 871 "update the server."); 872 } 873 874 }); 875 } catch (IOException e) { 876 throw new FileNotFoundException("Failed to open document with id " 877 + documentId + " and mode " + 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>