1 page.title= 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 : </a></li> 51 <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ"> 52 DevBytes: Android 4.4 : </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"> </a> 69 70 71 </li> 72 </ol> 73 74 </div> 75 </div> 76 77 78 <p>Android 4.4 API 19 SAFSAF 79 80 UI 81 </p> 82 83 <p> {@link android.provider.DocumentsProvider} 84 SAF 85 86 </p> 87 88 <p>SAF </p> 89 90 <ul> 91 <li><strong> </strong>— Google 92 {@link android.provider.DocumentsProvider} document-provider Android 93 94 95 96 97 </li> 98 99 <li><strong> </strong>—{@link android.content.Intent#ACTION_OPEN_DOCUMENT} {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 100 101 102 </li> 103 104 <li><strong></strong>— UI 105 </li> 106 </ul> 107 108 <p>SAF </p> 109 <ul> 110 <li> 1 </li> 111 <li> 112 113 </li> 114 <li> USB 115 </li> 116 </ul> 117 118 <h2 id ="overview"></h2> 119 120 <p>SAF {@link android.provider.DocumentsProvider} 121 <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> 1 125 </p> 126 127 <p>: </p> 128 <ul> 129 130 <li> 1 {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} USB 131 132 133 134 135 136 </li> 137 138 <li> 1 1 <em>N</em> 1 <em>N</em> 139 </li> 140 141 <li> {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} URI ID 142 143 144 145 </li> 146 147 148 <li> MIME {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME 149 150 </li> 151 152 <li>{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS} {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL} {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} 153 154 155 156 157 158 </li> 159 </ul> 160 161 <h2 id="flow"> </h2> 162 <p> 163 {@link android.provider.DocumentsProvider} API 164 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> </p> 172 173 <p>: </p> 174 <ul> 175 176 <li>SAF 177 178 </li> 179 180 <li> {@link android.content.Intent#ACTION_OPEN_DOCUMENT} {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 181 — MIME 182 183 </li> 184 185 <li> 186 </li> 187 188 <li> 189 2 Google Drive USB 190 </li> 191 </ul> 192 193 <p> 3 Google Drive 194 </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 Drive 4 201 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 {@link android.content.Intent#ACTION_PICK} {@link android.content.Intent#ACTION_GET_CONTENT} 211 212 1 213 214 </p> 215 216 <p>Android 4.4 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} UI 217 218 219 1 UI 220 </p> 221 222 <p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} {@link android.content.Intent#ACTION_GET_CONTENT} 223 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>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 240 241 </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/*")} MIME 284 </li> 285 </ul> 286 287 <h3 id="results"></h3> 288 289 <p>{@link android.app.Activity#onActivityResult onActivityResult()} URI {@code resultData} 290 291 292 {@link android.content.Intent#getData getData()} URI URI URI 293 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 URI 322 </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> UI {@link android.os.AsyncTask} 386 {@link android.widget.ImageView} 387 388 </p> 389 390 <h4>InputStream </h4> 391 392 <p>URI {@link java.io.InputStream} 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>{@link android.content.Intent#ACTION_CREATE_DOCUMENT} 413 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>{@link android.app.Activity#onActivityResult onActivityResult()} URI 444 445 </p> 446 447 <h3 id="delete"></h3> 448 449 <p> URI {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE} {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} 450 451 452 453 </p> 454 455 <pre> 456 DocumentsContract.deleteDocument(getContentResolver(), uri); 457 </pre> 458 459 <h3 id="edit"></h3> 460 461 <p>SAF {@link android.content.Intent#ACTION_OPEN_DOCUMENT} {@link android.content.Intent#CATEGORY_OPENABLE} 462 463 464 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()}<a href="#results"></a> {@link android.content.ContentResolver} {@link java.io.FileOutputStream} 489 490 491 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> URI 516 5 517 518 519 520 </p> 521 522 <p> URI 523 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> 1 URI — 535 536 {@code getContentResolver().takePersistableUriPermission()} 537 538 </p> 539 540 <h2 id="custom"> </h2> 541 542 <p> 543 SAF 544 545 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>: <code>com.example.android.storageprovider.MyCloudProvider</code> 561 </li> 562 563 <li> <code>com.example.android.storageprovider</code> <code>documents</code> 564 565 {@code com.example.android.storageprovider.documents} </li> 566 567 <li><code>"true"</code> <code>android:exported</code> 568 </li> 569 570 <li><code>"true"</code> <code>android:grantUriPermissions</code> 571 572 <a href="#permissions"></a> 573 </li> 574 575 <li>{@code MANAGE_DOCUMENTS} 576 577 </li> 578 579 <li> {@code android:enabled} 580 Android 4.3 {@code android:enabled="@bool/atLeastKitKat"} 581 582 583 <ul> 584 <li>{@code res/values/} {@code bool.xml} 585 <pre><bool name="atLeastKitKat">false</bool></pre></li> 586 587 <li>{@code res/values-v19/} {@code bool.xml} 588 <pre><bool name="atLeastKitKat">true</bool></pre></li> 589 </ul></li> 590 591 <li>{@code android.content.action.DOCUMENTS_PROVIDER} 592 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>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} Android 4.4 Android 4.3 {@link android.content.Intent#ACTION_GET_CONTENT} Android 4.4 {@link android.content.Intent#ACTION_GET_CONTENT} 622 623 624 625 626 627 {@link android.content.Intent#ACTION_GET_CONTENT} 628 629 UI 2 2 630 631 </p> 632 633 <p>Android 4.4 {@link android.content.Intent#ACTION_GET_CONTENT} 634 635 </p> 636 637 <ol> 638 <li>{@code res/values/} {@code bool.xml} 639 <pre><bool name="atMostJellyBeanMR2">true</bool></pre></li> 640 641 <li>{@code res/values-v19/} {@code bool.xml} 642 <pre><bool name="atMostJellyBeanMR2">false</bool></pre></li> 643 644 <li>4.4 API 19 {@link android.content.Intent#ACTION_GET_CONTENT} <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"> </a> 645 646 647 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> <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass"> </a> 670 671 672 {@code public final} URIMIME 673 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> {@link android.provider.DocumentsProvider} 700 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.provider.DocumentsContract.Root} {@link android.database.Cursor} 721 722 </p> 723 724 <p> {@code projection} 725 1 —[] [] 1 1 726 727 1 1 728 2 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>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} {@link android.provider.DocumentsContract.Document} {@link android.database.Cursor} 780 781 782 783 </p> 784 785 <p> UI 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>{@link android.provider.DocumentsProvider#queryDocument queryDocument()} {@link android.provider.DocumentsContract.Document} {@link android.database.Cursor} 810 811 812 813 </p> 814 815 <p>{@link android.provider.DocumentsProvider#queryDocument queryDocument()} {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 816 817 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.os.ParcelFileDescriptor} {@link android.provider.DocumentsProvider#openDocument 836 openDocument()} 837 {@link android.os.ParcelFileDescriptor} 838 {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()} 839 840 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 {@link android.provider.DocumentsProvider#queryRoots 890 queryRoots()} 891 </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()} {@link android.provider.DocumentsContract} 904 URI 905 906 {@link android.provider.DocumentsProvider#queryRoots queryRoots()} 907 908 909 </p> 910 911 <pre>private void onLoginButtonClick() { 912 loginOrLogout(); 913 getContentResolver().notifyChange(DocumentsContract 914 .buildRootsUri(AUTHORITY), null); 915 } 916 </pre>