1 page.title= (Storage Access Framework) 2 @jd:body 3 <div id="qv-wrapper"> 4 <div id="qv"> 5 6 <h2> 7 <a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle"> 8 <span class="more"> </span> 9 <span class="less" style="display:none"> </span></a></h2> 10 <ol id="toc44" class="hide-nested"> 11 <li> 12 <a href="#overview"></a> 13 </li> 14 <li> 15 <a href="#flow"> </a> 16 </li> 17 <li> 18 <a href="#client"> </a> 19 <ol> 20 <li><a href="#search"> </a></li> 21 <li><a href="#process"> </a></li> 22 <li><a href="#metadata"> </a></li> 23 <li><a href="#open"> </a></li> 24 <li><a href="#create"> </a></li> 25 <li><a href="#delete"> </a></li> 26 <li><a href="#edit"> </a></li> 27 <li><a href="#permissions"> </a></li> 28 </ol> 29 </li> 30 <li><a href="#custom"> </a> 31 <ol> 32 <li><a href="#manifest"></a></li> 33 <li><a href="#contract"></a></li> 34 <li><a href="#subclass"> DocumentsProvider</a></li> 35 <li><a href="#security"></a></li> 36 </ol> 37 </li> 38 39 </ol> 40 <h2> </h2> 41 <ol> 42 <li>{@link android.provider.DocumentsProvider}</li> 43 <li>{@link android.provider.DocumentsContract}</li> 44 </ol> 45 46 <h2></h2> 47 48 <ol> 49 <li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4"> 50 DevBytes: Android 4.4 Storage Access Framework: </a></li> 51 <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ"> 52 DevBytes: Android 4.4 Storage Access Framework: </a></li> 53 </ol> 54 55 56 <h2> </h2> 57 58 <ol> 59 <li><a href="{@docRoot}samples/StorageProvider/index.html"> 60 StorageProvider</a></li> 61 <li><a href="{@docRoot}samples/StorageClient/index.html"> 62 StorageClient</a></li> 63 </ol> 64 65 <h2>. :</h2> 66 <ol> 67 <li> 68 <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> 69 70 </a> 71 </li> 72 </ol> 73 74 </div> 75 </div> 76 77 78 <p> (Storage Access Framework, SAF) Android 4.4 (API 19). SAF 79 , 80 , . 81 .</p> 82 83 <p> , 84 {@link android.provider.DocumentsProvider}, . 85 , , SAF 86 .</p> 87 88 <p> SAF :</p> 89 90 <ul> 91 <li><strong> </strong>— , 92 (, Google) , . 93 {@link android.provider.DocumentsProvider}. 94 , 95 . 96 Android , 97 , .</li> 98 99 <li><strong> </strong>— , 100 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} / 101 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 102 , .</li> 103 104 <li><strong> </strong>— , 105 , , .</li> 106 </ul> 107 108 <p> SAF :</p> 109 <ul> 110 <li> , ;</li> 111 <li> , 112 , . , , 113 , ;</li> 114 <li> , , 115 USB-, , . </li> 116 </ul> 117 118 <h2 id ="overview"></h2> 119 120 <p> SAF , 121 {@link android.provider.DocumentsProvider}. <em> </em> 122 :</p> 123 <p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p> 124 <p class="img-caption"><strong> 1.</strong> . Root ( ) Document (), 125 .</p> 126 127 <p> .</p> 128 <ul> 129 130 <li> 131 , . 132 {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} 133 (), 134 . 135 , , , 136 USB- .</li> 137 138 <li> . <em>N</em> 139 , , <em>N</em> . </li> 140 141 <li> 142 , 143 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}. 144 , 145 URI, .</li> 146 147 148 <li> ( MIME-), 149 , ( 150 MIME- {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li> 151 152 <li> , 153 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}, 154 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, 155 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} 156 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. 157 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} 158 .</li> 159 </ul> 160 161 <h2 id="flow"> </h2> 162 <p> , 163 . , 164 , API- {@link android.provider.DocumentsProvider}. , 165 .</p> 166 167 <p> 2 , SAF 168 :</p> 169 <p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p> 170 171 <p class="img-caption"><strong> 2.</strong> Storage Access Framework</p> 172 173 <p> .</p> 174 <ul> 175 176 <li> SAF 177 . 178 ( , , , ).</li> 179 180 <li> , ( ) 181 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. 182 , , 183 MIME- image.</li> 184 185 <li> , 186 , .</li> 187 188 <li> , 189 . 2 190 Google, USB- .</li> 191 </ul> 192 193 <p> 3 , 194 Google:</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> 3.</strong> </p> 199 200 <p> Google, , 201 4. , 202 . 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>4.</strong> </p> 207 208 <h2 id="client"> </h2> 209 210 <p> Android 4.3 , 211 , , , {@link android.content.Intent#ACTION_PICK} 212 {@link android.content.Intent#ACTION_GET_CONTENT}. 213 - , , 214 , . </p> 215 216 <p> Android 4.4 , 217 {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, 218 , . 219 , . , 220 .</p> 221 222 <p> {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 223 {@link android.content.Intent#ACTION_GET_CONTENT}. 224 , :</p> 225 226 <ul> 227 <li> {@link android.content.Intent#ACTION_GET_CONTENT}, 228 . , 229 , .</li> 230 231 <li> {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, 232 , , 233 . , 234 , . </li> 235 236 </ul> 237 238 239 <p> , , 240 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 241 {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p> 242 243 244 <h3 id="search"> </h3> 245 246 <p> 247 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 248 , 249 :</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 */ 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 "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> .</p> 276 <ul> 277 <li> {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 278 , , , .</li> 279 280 <li> {@link android.content.Intent#CATEGORY_OPENABLE} 281 , , , .</li> 282 283 <li> {@code intent.setType("image/*")} , 284 MIME- image.</li> 285 </ul> 286 287 <h3 id="results"> </h3> 288 289 <p> , 290 {@link android.app.Activity#onActivityResult onActivityResult()}. 291 URI, , {@code resultData}. 292 URI, {@link android.content.Intent#getData getData()}. 293 URI , . : 294 </p> 295 296 <pre>@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"> </h3> 320 321 <p> URI , . 322 , URI, :</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"> </h3> 367 368 <p> URI , 369 , .</p> 370 371 <h4> </h4> 372 373 <p> {@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> , . 386 , {@link android.os.AsyncTask}. , 387 {@link android.widget.ImageView}. 388 </p> 389 390 <h4> InputStream</h4> 391 392 <p> , {@link java.io.InputStream} URI. 393 :</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"> </h3> 411 412 <p> , 413 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 414 . , MIME- , 415 . :</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 "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> URI 444 {@link android.app.Activity#onActivityResult onActivityResult()}, 445 .</p> 446 447 <h3 id="delete"> </h3> 448 449 <p> URI , 450 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} 451 452 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, 453 . :</p> 454 455 <pre> 456 DocumentsContract.deleteDocument(getContentResolver(), uri); 457 </pre> 458 459 <h3 id="edit"> </h3> 460 461 <p> SAF . 462 463 {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, 464 {@link android.content.Intent#CATEGORY_OPENABLE} , 465 , . , :</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 "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>, {@link android.app.Activity#onActivityResult onActivityResult()} 489 (. <a href="#results"> </a>) . 490 {@link java.io.FileOutputStream} 491 {@link android.content.ContentResolver}. . 492 , 493 /, :</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"> </h3> 514 515 <p> , 516 URI- . . 517 , , 518 , . 519 , 520 . , .</p> 521 522 <p> , , 523 . URI-, 524 . 525 , :</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. 532 getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre> 533 534 <p> . 535 URI-, . , , 536 . 537 {@code getContentResolver().takePersistableUriPermission()}, 538 .</p> 539 540 <h2 id="custom"> </h2> 541 542 <p> 543 , (, 544 ), 545 SAF, . , 546 .</p> 547 548 549 <h3 id="manifest"></h3> 550 551 <p> , 552 :</p> 553 <ul> 554 555 <li> API- 19 .</li> 556 557 <li> <code><provider></code>, 558 . </li> 559 560 <li> , .., . 561 : <code>com.example.android.storageprovider.MyCloudProvider</code>.</li> 562 563 <li> , .. ( 564 <code>com.example.android.storageprovider</code>) 565 (<code>documents</code>). ,{@code com.example.android.storageprovider.documents}.</li> 566 567 <li> <code>android:exported</code>, <code>"true"</code>. 568 , .</li> 569 570 <li> <code>android:grantUriPermissions</code>, 571 <code>"true"</code>. 572 . , 573 . <a href="#permissions"> </a>.</li> 574 575 <li> {@code MANAGE_DOCUMENTS}. 576 . . 577 .</li> 578 579 <li> {@code android:enabled}, , 580 . Android 4.3 . 581 : {@code android:enabled="@bool/atLeastKitKat"}. 582 , : 583 <ul> 584 <li> {@code bool.xml}, {@code res/values/}, 585 <pre><bool name="atLeastKitKat">false</bool></pre></li> 586 587 <li> {@code bool.xml}, {@code res/values-v19/}, 588 <pre><bool name="atLeastKitKat">true</bool></pre></li> 589 </ul></li> 590 591 <li> 592 {@code android.content.action.DOCUMENTS_PROVIDER}, 593 , .</li> 594 595 </ul> 596 <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"> Android 4.3 </h4> 620 621 <p> 622 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 623 Android 4.4 . 624 {@link android.content.Intent#ACTION_GET_CONTENT}, 625 , Android 4.3 , 626 {@link android.content.Intent#ACTION_GET_CONTENT} 627 Android 4.4 . 628 {@link android.content.Intent#ACTION_GET_CONTENT} 629 . , 630 , 631 . .</p> 632 633 <p> 634 {@link android.content.Intent#ACTION_GET_CONTENT} 635 Android 4.4 :</p> 636 637 <ol> 638 <li> {@code bool.xml}, {@code res/values/}, 639 : <pre><bool name="atMostJellyBeanMR2">true</bool></pre></li> 640 641 <li> {@code bool.xml}, {@code res/values-v19/}, 642 : <pre><bool name="atMostJellyBeanMR2">false</bool></pre></li> 643 644 <li> 645 <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"> 646 </a>, {@link android.content.Intent#ACTION_GET_CONTENT} 647 4.4 (API 19) . : 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"></h3> 668 669 <p> , 670 -, 671 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass"> 672 </a>. - {@code public final}, 673 URI, , MIME 674 . SAF 675 -, 676 :</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>, 684 , :</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"> DocumentsProvider</h3> 698 699 <p> 700 {@link android.provider.DocumentsProvider}. , 701 :</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> , , 714 , , , . {@link android.provider.DocumentsProvider} 715 .</p> 716 717 <h4 id="queryRoots"> queryRoots</h4> 718 719 <p> {@link android.provider.DocumentsProvider#queryRoots 720 queryRoots()} {@link android.database.Cursor}, 721 , , 722 {@link android.provider.DocumentsContract.Root}.</p> 723 724 <p> {@code projection} 725 , . , 726 , ( ), , 727 . . , 728 , . 729 .</p> 730 731 <pre> 732 @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 "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"> queryChildDocuments</h4> 778 779 <p> 780 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 781 {@link android.database.Cursor}, 782 , , 783 {@link android.provider.DocumentsContract.Document}.</p> 784 785 <p> , . 786 - . 787 , . 788 . 789 . 790 , , , :</p> 791 792 <pre>@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"> queryDocument</h4> 808 809 <p> 810 {@link android.provider.DocumentsProvider#queryDocument queryDocument()} 811 {@link android.database.Cursor}, , 812 , {@link android.provider.DocumentsContract.Document}. 813 </p> 814 815 <p> {@link android.provider.DocumentsProvider#queryDocument queryDocument()} 816 , 817 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, 818 :</p> 819 820 821 <pre>@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"> openDocument</h4> 834 835 <p> {@link android.provider.DocumentsProvider#openDocument 836 openDocument()}, {@link android.os.ParcelFileDescriptor}, 837 . {@link android.os.ParcelFileDescriptor} 838 . , , 839 , 840 {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. 841 :</p> 842 843 <pre>@Override 844 public 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"></h3> 886 887 <p>, , 888 , , . 889 , ? , 890 {@link android.provider.DocumentsProvider#queryRoots 891 queryRoots()} . , :</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> {@code getContentResolver().notifyChange()}. 904 {@link android.provider.DocumentsContract}? 905 URI. 906 , . 907 , {@link android.provider.DocumentsProvider#queryRoots queryRoots()} 908 , . , 909 , .</p> 910 911 <pre>private void onLoginButtonClick() { 912 loginOrLogout(); 913 getContentResolver().notifyChange(DocumentsContract 914 .buildRootsUri(AUTHORITY), null); 915 } 916 </pre>