• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.provider;
18 
19 import static android.net.TrafficStats.KB_IN_BYTES;
20 import static android.system.OsConstants.SEEK_SET;
21 
22 import android.annotation.Nullable;
23 import android.content.ContentProviderClient;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ResolveInfo;
28 import android.content.res.AssetFileDescriptor;
29 import android.database.Cursor;
30 import android.graphics.Bitmap;
31 import android.graphics.BitmapFactory;
32 import android.graphics.Matrix;
33 import android.graphics.Point;
34 import android.media.ExifInterface;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.CancellationSignal;
38 import android.os.OperationCanceledException;
39 import android.os.ParcelFileDescriptor;
40 import android.os.ParcelFileDescriptor.OnCloseListener;
41 import android.os.RemoteException;
42 import android.os.storage.StorageVolume;
43 import android.system.ErrnoException;
44 import android.system.Os;
45 import android.util.Log;
46 
47 import libcore.io.IoUtils;
48 
49 import java.io.BufferedInputStream;
50 import java.io.File;
51 import java.io.FileDescriptor;
52 import java.io.FileInputStream;
53 import java.io.FileNotFoundException;
54 import java.io.IOException;
55 import java.util.List;
56 
57 /**
58  * Defines the contract between a documents provider and the platform.
59  * <p>
60  * To create a document provider, extend {@link DocumentsProvider}, which
61  * provides a foundational implementation of this contract.
62  * <p>
63  * All client apps must hold a valid URI permission grant to access documents,
64  * typically issued when a user makes a selection through
65  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
66  * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
67  * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
68  *
69  * @see DocumentsProvider
70  */
71 public final class DocumentsContract {
72     private static final String TAG = "DocumentsContract";
73 
74     // content://com.example/root/
75     // content://com.example/root/sdcard/
76     // content://com.example/root/sdcard/recent/
77     // content://com.example/root/sdcard/search/?query=pony
78     // content://com.example/document/12/
79     // content://com.example/document/12/children/
80     // content://com.example/tree/12/document/24/
81     // content://com.example/tree/12/document/24/children/
82 
DocumentsContract()83     private DocumentsContract() {
84     }
85 
86     /**
87      * Intent action used to identify {@link DocumentsProvider} instances. This
88      * is used in the {@code <intent-filter>} of a {@code <provider>}.
89      */
90     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
91 
92     /** {@hide} */
93     public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
94 
95     /** {@hide} */
96     public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
97 
98     /** {@hide} */
99     public static final String EXTRA_SHOW_FILESIZE = "android.content.extra.SHOW_FILESIZE";
100 
101     /** {@hide} */
102     public static final String EXTRA_FANCY_FEATURES = "android.content.extra.FANCY";
103 
104     /** {@hide} */
105     public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
106 
107     /**
108      * Set this in a DocumentsUI intent to cause a package's own roots to be
109      * excluded from the roots list.
110      */
111     public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
112 
113     /**
114      * Included in {@link AssetFileDescriptor#getExtras()} when returned
115      * thumbnail should be rotated.
116      *
117      * @see MediaStore.Images.ImageColumns#ORIENTATION
118      */
119     public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
120 
121     /**
122      * Overrides the default prompt text in DocumentsUI when set in an intent.
123      */
124     public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
125 
126     /** {@hide} */
127     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
128 
129     /** {@hide} */
130     public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
131 
132     /** {@hide} */
133     public static final String
134             ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
135 
136     /**
137      * Buffer is large enough to rewind past any EXIF headers.
138      */
139     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
140 
141     /** {@hide} */
142     public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
143 
144     /**
145      * Constants related to a document, including {@link Cursor} column names
146      * and flags.
147      * <p>
148      * A document can be either an openable stream (with a specific MIME type),
149      * or a directory containing additional documents (with the
150      * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
151      * subtree containing zero or more documents, which can recursively contain
152      * even more documents and directories.
153      * <p>
154      * All columns are <em>read-only</em> to client applications.
155      */
156     public final static class Document {
Document()157         private Document() {
158         }
159 
160         /**
161          * Unique ID of a document. This ID is both provided by and interpreted
162          * by a {@link DocumentsProvider}, and should be treated as an opaque
163          * value by client applications. This column is required.
164          * <p>
165          * Each document must have a unique ID within a provider, but that
166          * single document may be included as a child of multiple directories.
167          * <p>
168          * A provider must always return durable IDs, since they will be used to
169          * issue long-term URI permission grants when an application interacts
170          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
171          * {@link Intent#ACTION_CREATE_DOCUMENT}.
172          * <p>
173          * Type: STRING
174          */
175         public static final String COLUMN_DOCUMENT_ID = "document_id";
176 
177         /**
178          * Concrete MIME type of a document. For example, "image/png" or
179          * "application/pdf" for openable files. A document can also be a
180          * directory containing additional documents, which is represented with
181          * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
182          * <p>
183          * Type: STRING
184          *
185          * @see #MIME_TYPE_DIR
186          */
187         public static final String COLUMN_MIME_TYPE = "mime_type";
188 
189         /**
190          * Display name of a document, used as the primary title displayed to a
191          * user. This column is required.
192          * <p>
193          * Type: STRING
194          */
195         public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
196 
197         /**
198          * Summary of a document, which may be shown to a user. This column is
199          * optional, and may be {@code null}.
200          * <p>
201          * Type: STRING
202          */
203         public static final String COLUMN_SUMMARY = "summary";
204 
205         /**
206          * Timestamp when a document was last modified, in milliseconds since
207          * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
208          * {@code null} if unknown. A {@link DocumentsProvider} can update this
209          * field using events from {@link OnCloseListener} or other reliable
210          * {@link ParcelFileDescriptor} transports.
211          * <p>
212          * Type: INTEGER (long)
213          *
214          * @see System#currentTimeMillis()
215          */
216         public static final String COLUMN_LAST_MODIFIED = "last_modified";
217 
218         /**
219          * Specific icon resource ID for a document. This column is optional,
220          * and may be {@code null} to use a platform-provided default icon based
221          * on {@link #COLUMN_MIME_TYPE}.
222          * <p>
223          * Type: INTEGER (int)
224          */
225         public static final String COLUMN_ICON = "icon";
226 
227         /**
228          * Flags that apply to a document. This column is required.
229          * <p>
230          * Type: INTEGER (int)
231          *
232          * @see #FLAG_SUPPORTS_WRITE
233          * @see #FLAG_SUPPORTS_DELETE
234          * @see #FLAG_SUPPORTS_THUMBNAIL
235          * @see #FLAG_DIR_PREFERS_GRID
236          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
237          * @see #FLAG_VIRTUAL_DOCUMENT
238          * @see #FLAG_SUPPORTS_COPY
239          * @see #FLAG_SUPPORTS_MOVE
240          * @see #FLAG_SUPPORTS_REMOVE
241          */
242         public static final String COLUMN_FLAGS = "flags";
243 
244         /**
245          * Size of a document, in bytes, or {@code null} if unknown. This column
246          * is required.
247          * <p>
248          * Type: INTEGER (long)
249          */
250         public static final String COLUMN_SIZE = OpenableColumns.SIZE;
251 
252         /**
253          * MIME type of a document which is a directory that may contain
254          * additional documents.
255          *
256          * @see #COLUMN_MIME_TYPE
257          */
258         public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
259 
260         /**
261          * Flag indicating that a document can be represented as a thumbnail.
262          *
263          * @see #COLUMN_FLAGS
264          * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
265          *      Point, CancellationSignal)
266          * @see DocumentsProvider#openDocumentThumbnail(String, Point,
267          *      android.os.CancellationSignal)
268          */
269         public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
270 
271         /**
272          * Flag indicating that a document supports writing.
273          * <p>
274          * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
275          * the calling application is granted both
276          * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
277          * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
278          * writability of a document may change over time, for example due to
279          * remote access changes. This flag indicates that a document client can
280          * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
281          *
282          * @see #COLUMN_FLAGS
283          */
284         public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
285 
286         /**
287          * Flag indicating that a document is deletable.
288          *
289          * @see #COLUMN_FLAGS
290          * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
291          * @see DocumentsProvider#deleteDocument(String)
292          */
293         public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
294 
295         /**
296          * Flag indicating that a document is a directory that supports creation
297          * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
298          * {@link #MIME_TYPE_DIR}.
299          *
300          * @see #COLUMN_FLAGS
301          * @see DocumentsProvider#createDocument(String, String, String)
302          */
303         public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
304 
305         /**
306          * Flag indicating that a directory prefers its contents be shown in a
307          * larger format grid. Usually suitable when a directory contains mostly
308          * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
309          * {@link #MIME_TYPE_DIR}.
310          *
311          * @see #COLUMN_FLAGS
312          */
313         public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
314 
315         /**
316          * Flag indicating that a directory prefers its contents be sorted by
317          * {@link #COLUMN_LAST_MODIFIED}. Only valid when
318          * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
319          *
320          * @see #COLUMN_FLAGS
321          */
322         public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
323 
324         /**
325          * Flag indicating that a document can be renamed.
326          *
327          * @see #COLUMN_FLAGS
328          * @see DocumentsContract#renameDocument(ContentResolver, Uri,
329          *      String)
330          * @see DocumentsProvider#renameDocument(String, String)
331          */
332         public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
333 
334         /**
335          * Flag indicating that a document can be copied to another location
336          * within the same document provider.
337          *
338          * @see #COLUMN_FLAGS
339          * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
340          * @see DocumentsProvider#copyDocument(String, String)
341          */
342         public static final int FLAG_SUPPORTS_COPY = 1 << 7;
343 
344         /**
345          * Flag indicating that a document can be moved to another location
346          * within the same document provider.
347          *
348          * @see #COLUMN_FLAGS
349          * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
350          * @see DocumentsProvider#moveDocument(String, String, String)
351          */
352         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
353 
354         /**
355          * Flag indicating that a document is virtual, and doesn't have byte
356          * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
357          *
358          * @see #COLUMN_FLAGS
359          * @see #COLUMN_MIME_TYPE
360          * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
361          *      android.os.CancellationSignal)
362          * @see DocumentsProvider#getDocumentStreamTypes(String, String)
363          */
364         public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
365 
366         /**
367          * Flag indicating that a document can be removed from a parent.
368          *
369          * @see #COLUMN_FLAGS
370          * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
371          * @see DocumentsProvider#removeDocument(String, String)
372          */
373         public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
374 
375         /**
376          * Flag indicating that a document is an archive, and it's contents can be
377          * obtained via {@link DocumentsProvider#queryChildDocuments}.
378          * <p>
379          * The <em>provider</em> support library offers utility classes to add common
380          * archive support.
381          *
382          * @see #COLUMN_FLAGS
383          * @see DocumentsProvider#queryChildDocuments(String, String[], String)
384          * @hide
385          */
386         public static final int FLAG_ARCHIVE = 1 << 15;
387 
388         /**
389          * Flag indicating that a document is not complete, likely its
390          * contents are being downloaded. Partial files cannot be opened,
391          * copied, moved in the UI. But they can be deleted and retried
392          * if they represent a failed download.
393          *
394          * @see #COLUMN_FLAGS
395          * @hide
396          */
397         public static final int FLAG_PARTIAL = 1 << 16;
398     }
399 
400     /**
401      * Constants related to a root of documents, including {@link Cursor} column
402      * names and flags. A root is the start of a tree of documents, such as a
403      * physical storage device, or an account. Each root starts at the directory
404      * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
405      * contain both documents and directories.
406      * <p>
407      * All columns are <em>read-only</em> to client applications.
408      */
409     public final static class Root {
Root()410         private Root() {
411         }
412 
413         /**
414          * Unique ID of a root. This ID is both provided by and interpreted by a
415          * {@link DocumentsProvider}, and should be treated as an opaque value
416          * by client applications. This column is required.
417          * <p>
418          * Type: STRING
419          */
420         public static final String COLUMN_ROOT_ID = "root_id";
421 
422         /**
423          * Flags that apply to a root. This column is required.
424          * <p>
425          * Type: INTEGER (int)
426          *
427          * @see #FLAG_LOCAL_ONLY
428          * @see #FLAG_SUPPORTS_CREATE
429          * @see #FLAG_SUPPORTS_RECENTS
430          * @see #FLAG_SUPPORTS_SEARCH
431          */
432         public static final String COLUMN_FLAGS = "flags";
433 
434         /**
435          * Icon resource ID for a root. This column is required.
436          * <p>
437          * Type: INTEGER (int)
438          */
439         public static final String COLUMN_ICON = "icon";
440 
441         /**
442          * Title for a root, which will be shown to a user. This column is
443          * required. For a single storage service surfacing multiple accounts as
444          * different roots, this title should be the name of the service.
445          * <p>
446          * Type: STRING
447          */
448         public static final String COLUMN_TITLE = "title";
449 
450         /**
451          * Summary for this root, which may be shown to a user. This column is
452          * optional, and may be {@code null}. For a single storage service
453          * surfacing multiple accounts as different roots, this summary should
454          * be the name of the account.
455          * <p>
456          * Type: STRING
457          */
458         public static final String COLUMN_SUMMARY = "summary";
459 
460         /**
461          * Document which is a directory that represents the top directory of
462          * this root. This column is required.
463          * <p>
464          * Type: STRING
465          *
466          * @see Document#COLUMN_DOCUMENT_ID
467          */
468         public static final String COLUMN_DOCUMENT_ID = "document_id";
469 
470         /**
471          * Number of bytes available in this root. This column is optional, and
472          * may be {@code null} if unknown or unbounded.
473          * <p>
474          * Type: INTEGER (long)
475          */
476         public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
477 
478         /**
479          * Capacity of a root in bytes. This column is optional, and may be
480          * {@code null} if unknown or unbounded.
481          * <p>
482          * Type: INTEGER (long)
483          */
484         public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes";
485 
486         /**
487          * MIME types supported by this root. This column is optional, and if
488          * {@code null} the root is assumed to support all MIME types. Multiple
489          * MIME types can be separated by a newline. For example, a root
490          * supporting audio might return "audio/*\napplication/x-flac".
491          * <p>
492          * Type: STRING
493          */
494         public static final String COLUMN_MIME_TYPES = "mime_types";
495 
496         /** {@hide} */
497         public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
498 
499         /**
500          * Flag indicating that at least one directory under this root supports
501          * creating content. Roots with this flag will be shown when an
502          * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
503          *
504          * @see #COLUMN_FLAGS
505          */
506         public static final int FLAG_SUPPORTS_CREATE = 1;
507 
508         /**
509          * Flag indicating that this root offers content that is strictly local
510          * on the device. That is, no network requests are made for the content.
511          *
512          * @see #COLUMN_FLAGS
513          * @see Intent#EXTRA_LOCAL_ONLY
514          */
515         public static final int FLAG_LOCAL_ONLY = 1 << 1;
516 
517         /**
518          * Flag indicating that this root can be queried to provide recently
519          * modified documents.
520          *
521          * @see #COLUMN_FLAGS
522          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
523          * @see DocumentsProvider#queryRecentDocuments(String, String[])
524          */
525         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
526 
527         /**
528          * Flag indicating that this root supports search.
529          *
530          * @see #COLUMN_FLAGS
531          * @see DocumentsContract#buildSearchDocumentsUri(String, String,
532          *      String)
533          * @see DocumentsProvider#querySearchDocuments(String, String,
534          *      String[])
535          */
536         public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
537 
538         /**
539          * Flag indicating that this root supports testing parent child
540          * relationships.
541          *
542          * @see #COLUMN_FLAGS
543          * @see DocumentsProvider#isChildDocument(String, String)
544          */
545         public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
546 
547         /**
548          * Flag indicating that this root is currently empty. This may be used
549          * to hide the root when opening documents, but the root will still be
550          * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
551          * also set. If the value of this flag changes, such as when a root
552          * becomes non-empty, you must send a content changed notification for
553          * {@link DocumentsContract#buildRootsUri(String)}.
554          *
555          * @see #COLUMN_FLAGS
556          * @see ContentResolver#notifyChange(Uri,
557          *      android.database.ContentObserver, boolean)
558          * @hide
559          */
560         public static final int FLAG_EMPTY = 1 << 16;
561 
562         /**
563          * Flag indicating that this root should only be visible to advanced
564          * users.
565          *
566          * @see #COLUMN_FLAGS
567          * @hide
568          */
569         public static final int FLAG_ADVANCED = 1 << 17;
570 
571         /**
572          * Flag indicating that this root has settings.
573          *
574          * @see #COLUMN_FLAGS
575          * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
576          * @hide
577          */
578         public static final int FLAG_HAS_SETTINGS = 1 << 18;
579 
580         /**
581          * Flag indicating that this root is on removable SD card storage.
582          *
583          * @see #COLUMN_FLAGS
584          * @hide
585          */
586         public static final int FLAG_REMOVABLE_SD = 1 << 19;
587 
588         /**
589          * Flag indicating that this root is on removable USB storage.
590          *
591          * @see #COLUMN_FLAGS
592          * @hide
593          */
594         public static final int FLAG_REMOVABLE_USB = 1 << 20;
595     }
596 
597     /**
598      * Optional boolean flag included in a directory {@link Cursor#getExtras()}
599      * indicating that a document provider is still loading data. For example, a
600      * provider has returned some results, but is still waiting on an
601      * outstanding network request. The provider must send a content changed
602      * notification when loading is finished.
603      *
604      * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
605      *      boolean)
606      */
607     public static final String EXTRA_LOADING = "loading";
608 
609     /**
610      * Optional string included in a directory {@link Cursor#getExtras()}
611      * providing an informational message that should be shown to a user. For
612      * example, a provider may wish to indicate that not all documents are
613      * available.
614      */
615     public static final String EXTRA_INFO = "info";
616 
617     /**
618      * Optional string included in a directory {@link Cursor#getExtras()}
619      * providing an error message that should be shown to a user. For example, a
620      * provider may wish to indicate that a network error occurred. The user may
621      * choose to retry, resulting in a new query.
622      */
623     public static final String EXTRA_ERROR = "error";
624 
625     /**
626      * Optional result (I'm thinking boolean) answer to a question.
627      * {@hide}
628      */
629     public static final String EXTRA_RESULT = "result";
630 
631     /** {@hide} */
632     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
633     /** {@hide} */
634     public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
635     /** {@hide} */
636     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
637     /** {@hide} */
638     public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
639     /** {@hide} */
640     public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
641     /** {@hide} */
642     public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
643     /** {@hide} */
644     public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
645 
646     /** {@hide} */
647     public static final String EXTRA_PARENT_URI = "parentUri";
648     /** {@hide} */
649     public static final String EXTRA_URI = "uri";
650 
651     private static final String PATH_ROOT = "root";
652     private static final String PATH_RECENT = "recent";
653     private static final String PATH_DOCUMENT = "document";
654     private static final String PATH_CHILDREN = "children";
655     private static final String PATH_SEARCH = "search";
656     private static final String PATH_TREE = "tree";
657 
658     private static final String PARAM_QUERY = "query";
659     private static final String PARAM_MANAGE = "manage";
660 
661     /**
662      * Build URI representing the roots of a document provider. When queried, a
663      * provider will return one or more rows with columns defined by
664      * {@link Root}.
665      *
666      * @see DocumentsProvider#queryRoots(String[])
667      */
buildRootsUri(String authority)668     public static Uri buildRootsUri(String authority) {
669         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
670                 .authority(authority).appendPath(PATH_ROOT).build();
671     }
672 
673     /**
674      * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
675      * document provider.
676      *
677      * @see #getRootId(Uri)
678      */
buildRootUri(String authority, String rootId)679     public static Uri buildRootUri(String authority, String rootId) {
680         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
681                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
682     }
683 
684     /**
685      * Builds URI for user home directory on external (local) storage.
686      * {@hide}
687      */
buildHomeUri()688     public static Uri buildHomeUri() {
689         // TODO: Avoid this type of interpackage copying. Added here to avoid
690         // direct coupling, but not ideal.
691         return DocumentsContract.buildRootUri("com.android.externalstorage.documents", "home");
692     }
693 
694     /**
695      * Build URI representing the recently modified documents of a specific root
696      * in a document provider. When queried, a provider will return zero or more
697      * rows with columns defined by {@link Document}.
698      *
699      * @see DocumentsProvider#queryRecentDocuments(String, String[])
700      * @see #getRootId(Uri)
701      */
buildRecentDocumentsUri(String authority, String rootId)702     public static Uri buildRecentDocumentsUri(String authority, String rootId) {
703         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
704                 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
705                 .appendPath(PATH_RECENT).build();
706     }
707 
708     /**
709      * Build URI representing access to descendant documents of the given
710      * {@link Document#COLUMN_DOCUMENT_ID}.
711      *
712      * @see #getTreeDocumentId(Uri)
713      */
buildTreeDocumentUri(String authority, String documentId)714     public static Uri buildTreeDocumentUri(String authority, String documentId) {
715         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
716                 .appendPath(PATH_TREE).appendPath(documentId).build();
717     }
718 
719     /**
720      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
721      * a document provider. When queried, a provider will return a single row
722      * with columns defined by {@link Document}.
723      *
724      * @see DocumentsProvider#queryDocument(String, String[])
725      * @see #getDocumentId(Uri)
726      */
buildDocumentUri(String authority, String documentId)727     public static Uri buildDocumentUri(String authority, String documentId) {
728         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
729                 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
730     }
731 
732     /**
733      * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
734      * a document provider. When queried, a provider will return a single row
735      * with columns defined by {@link Document}.
736      * <p>
737      * However, instead of directly accessing the target document, the returned
738      * URI will leverage access granted through a subtree URI, typically
739      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
740      * must be a descendant (child, grandchild, etc) of the subtree.
741      * <p>
742      * This is typically used to access documents under a user-selected
743      * directory tree, since it doesn't require the user to separately confirm
744      * each new document access.
745      *
746      * @param treeUri the subtree to leverage to gain access to the target
747      *            document. The target directory must be a descendant of this
748      *            subtree.
749      * @param documentId the target document, which the caller may not have
750      *            direct access to.
751      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
752      * @see DocumentsProvider#isChildDocument(String, String)
753      * @see #buildDocumentUri(String, String)
754      */
buildDocumentUriUsingTree(Uri treeUri, String documentId)755     public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
756         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
757                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
758                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
759                 .appendPath(documentId).build();
760     }
761 
762     /** {@hide} */
buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId)763     public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
764         if (isTreeUri(baseUri)) {
765             return buildDocumentUriUsingTree(baseUri, documentId);
766         } else {
767             return buildDocumentUri(baseUri.getAuthority(), documentId);
768         }
769     }
770 
771     /**
772      * Build URI representing the children of the target directory in a document
773      * provider. When queried, a provider will return zero or more rows with
774      * columns defined by {@link Document}.
775      *
776      * @param parentDocumentId the document to return children for, which must
777      *            be a directory with MIME type of
778      *            {@link Document#MIME_TYPE_DIR}.
779      * @see DocumentsProvider#queryChildDocuments(String, String[], String)
780      * @see #getDocumentId(Uri)
781      */
buildChildDocumentsUri(String authority, String parentDocumentId)782     public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
783         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
784                 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
785                 .build();
786     }
787 
788     /**
789      * Build URI representing the children of the target directory in a document
790      * provider. When queried, a provider will return zero or more rows with
791      * columns defined by {@link Document}.
792      * <p>
793      * However, instead of directly accessing the target directory, the returned
794      * URI will leverage access granted through a subtree URI, typically
795      * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
796      * directory must be a descendant (child, grandchild, etc) of the subtree.
797      * <p>
798      * This is typically used to access documents under a user-selected
799      * directory tree, since it doesn't require the user to separately confirm
800      * each new document access.
801      *
802      * @param treeUri the subtree to leverage to gain access to the target
803      *            document. The target directory must be a descendant of this
804      *            subtree.
805      * @param parentDocumentId the document to return children for, which the
806      *            caller may not have direct access to, and which must be a
807      *            directory with MIME type of {@link Document#MIME_TYPE_DIR}.
808      * @see Intent#ACTION_OPEN_DOCUMENT_TREE
809      * @see DocumentsProvider#isChildDocument(String, String)
810      * @see #buildChildDocumentsUri(String, String)
811      */
buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId)812     public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
813         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
814                 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
815                 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
816                 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
817     }
818 
819     /**
820      * Build URI representing a search for matching documents under a specific
821      * root in a document provider. When queried, a provider will return zero or
822      * more rows with columns defined by {@link Document}.
823      *
824      * @see DocumentsProvider#querySearchDocuments(String, String, String[])
825      * @see #getRootId(Uri)
826      * @see #getSearchDocumentsQuery(Uri)
827      */
buildSearchDocumentsUri( String authority, String rootId, String query)828     public static Uri buildSearchDocumentsUri(
829             String authority, String rootId, String query) {
830         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
831                 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
832                 .appendQueryParameter(PARAM_QUERY, query).build();
833     }
834 
835     /**
836      * Test if the given URI represents a {@link Document} backed by a
837      * {@link DocumentsProvider}.
838      *
839      * @see #buildDocumentUri(String, String)
840      * @see #buildDocumentUriUsingTree(Uri, String)
841      */
isDocumentUri(Context context, @Nullable Uri uri)842     public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
843         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
844             final List<String> paths = uri.getPathSegments();
845             if (paths.size() == 2) {
846                 return PATH_DOCUMENT.equals(paths.get(0));
847             } else if (paths.size() == 4) {
848                 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
849             }
850         }
851         return false;
852     }
853 
854     /** {@hide} */
isRootUri(Context context, @Nullable Uri uri)855     public static boolean isRootUri(Context context, @Nullable Uri uri) {
856         if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
857             final List<String> paths = uri.getPathSegments();
858             return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
859         }
860         return false;
861     }
862 
863     /** {@hide} */
isContentUri(@ullable Uri uri)864     public static boolean isContentUri(@Nullable Uri uri) {
865         return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
866     }
867 
868     /**
869      * Test if the given URI represents a {@link Document} tree.
870      *
871      * @see #buildTreeDocumentUri(String, String)
872      * @see #getTreeDocumentId(Uri)
873      */
isTreeUri(Uri uri)874     public static boolean isTreeUri(Uri uri) {
875         final List<String> paths = uri.getPathSegments();
876         return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
877     }
878 
isDocumentsProvider(Context context, String authority)879     private static boolean isDocumentsProvider(Context context, String authority) {
880         final Intent intent = new Intent(PROVIDER_INTERFACE);
881         final List<ResolveInfo> infos = context.getPackageManager()
882                 .queryIntentContentProviders(intent, 0);
883         for (ResolveInfo info : infos) {
884             if (authority.equals(info.providerInfo.authority)) {
885                 return true;
886             }
887         }
888         return false;
889     }
890 
891     /**
892      * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
893      */
getRootId(Uri rootUri)894     public static String getRootId(Uri rootUri) {
895         final List<String> paths = rootUri.getPathSegments();
896         if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
897             return paths.get(1);
898         }
899         throw new IllegalArgumentException("Invalid URI: " + rootUri);
900     }
901 
902     /**
903      * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
904      *
905      * @see #isDocumentUri(Context, Uri)
906      */
getDocumentId(Uri documentUri)907     public static String getDocumentId(Uri documentUri) {
908         final List<String> paths = documentUri.getPathSegments();
909         if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
910             return paths.get(1);
911         }
912         if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
913                 && PATH_DOCUMENT.equals(paths.get(2))) {
914             return paths.get(3);
915         }
916         throw new IllegalArgumentException("Invalid URI: " + documentUri);
917     }
918 
919     /**
920      * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
921      */
getTreeDocumentId(Uri documentUri)922     public static String getTreeDocumentId(Uri documentUri) {
923         final List<String> paths = documentUri.getPathSegments();
924         if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
925             return paths.get(1);
926         }
927         throw new IllegalArgumentException("Invalid URI: " + documentUri);
928     }
929 
930     /**
931      * Extract the search query from a URI built by
932      * {@link #buildSearchDocumentsUri(String, String, String)}.
933      */
getSearchDocumentsQuery(Uri searchDocumentsUri)934     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
935         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
936     }
937 
938     /** {@hide} */
setManageMode(Uri uri)939     public static Uri setManageMode(Uri uri) {
940         return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
941     }
942 
943     /** {@hide} */
isManageMode(Uri uri)944     public static boolean isManageMode(Uri uri) {
945         return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
946     }
947 
948     /**
949      * Return thumbnail representing the document at the given URI. Callers are
950      * responsible for their own in-memory caching.
951      *
952      * @param documentUri document to return thumbnail for, which must have
953      *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
954      * @param size optimal thumbnail size desired. A provider may return a
955      *            thumbnail of a different size, but never more than double the
956      *            requested size.
957      * @param signal signal used to indicate if caller is no longer interested
958      *            in the thumbnail.
959      * @return decoded thumbnail, or {@code null} if problem was encountered.
960      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
961      *      android.os.CancellationSignal)
962      */
getDocumentThumbnail( ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)963     public static Bitmap getDocumentThumbnail(
964             ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
965         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
966                 documentUri.getAuthority());
967         try {
968             return getDocumentThumbnail(client, documentUri, size, signal);
969         } catch (Exception e) {
970             if (!(e instanceof OperationCanceledException)) {
971                 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
972             }
973             return null;
974         } finally {
975             ContentProviderClient.releaseQuietly(client);
976         }
977     }
978 
979     /** {@hide} */
getDocumentThumbnail( ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)980     public static Bitmap getDocumentThumbnail(
981             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
982             throws RemoteException, IOException {
983         final Bundle openOpts = new Bundle();
984         openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
985 
986         AssetFileDescriptor afd = null;
987         Bitmap bitmap = null;
988         try {
989             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
990 
991             final FileDescriptor fd = afd.getFileDescriptor();
992             final long offset = afd.getStartOffset();
993 
994             // Try seeking on the returned FD, since it gives us the most
995             // optimal decode path; otherwise fall back to buffering.
996             BufferedInputStream is = null;
997             try {
998                 Os.lseek(fd, offset, SEEK_SET);
999             } catch (ErrnoException e) {
1000                 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
1001                 is.mark(THUMBNAIL_BUFFER_SIZE);
1002             }
1003 
1004             // We requested a rough thumbnail size, but the remote size may have
1005             // returned something giant, so defensively scale down as needed.
1006             final BitmapFactory.Options opts = new BitmapFactory.Options();
1007             opts.inJustDecodeBounds = true;
1008             if (is != null) {
1009                 BitmapFactory.decodeStream(is, null, opts);
1010             } else {
1011                 BitmapFactory.decodeFileDescriptor(fd, null, opts);
1012             }
1013 
1014             final int widthSample = opts.outWidth / size.x;
1015             final int heightSample = opts.outHeight / size.y;
1016 
1017             opts.inJustDecodeBounds = false;
1018             opts.inSampleSize = Math.min(widthSample, heightSample);
1019             if (is != null) {
1020                 is.reset();
1021                 bitmap = BitmapFactory.decodeStream(is, null, opts);
1022             } else {
1023                 try {
1024                     Os.lseek(fd, offset, SEEK_SET);
1025                 } catch (ErrnoException e) {
1026                     e.rethrowAsIOException();
1027                 }
1028                 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
1029             }
1030 
1031             // Transform the bitmap if requested. We use a side-channel to
1032             // communicate the orientation, since EXIF thumbnails don't contain
1033             // the rotation flags of the original image.
1034             final Bundle extras = afd.getExtras();
1035             final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
1036             if (orientation != 0) {
1037                 final int width = bitmap.getWidth();
1038                 final int height = bitmap.getHeight();
1039 
1040                 final Matrix m = new Matrix();
1041                 m.setRotate(orientation, width / 2, height / 2);
1042                 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
1043             }
1044         } finally {
1045             IoUtils.closeQuietly(afd);
1046         }
1047 
1048         return bitmap;
1049     }
1050 
1051     /**
1052      * Create a new document with given MIME type and display name.
1053      *
1054      * @param parentDocumentUri directory with
1055      *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
1056      * @param mimeType MIME type of new document
1057      * @param displayName name of new document
1058      * @return newly created document, or {@code null} if failed
1059      */
createDocument(ContentResolver resolver, Uri parentDocumentUri, String mimeType, String displayName)1060     public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
1061             String mimeType, String displayName) {
1062         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1063                 parentDocumentUri.getAuthority());
1064         try {
1065             return createDocument(client, parentDocumentUri, mimeType, displayName);
1066         } catch (Exception e) {
1067             Log.w(TAG, "Failed to create document", e);
1068             return null;
1069         } finally {
1070             ContentProviderClient.releaseQuietly(client);
1071         }
1072     }
1073 
1074     /** {@hide} */
createDocument(ContentProviderClient client, Uri parentDocumentUri, String mimeType, String displayName)1075     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
1076             String mimeType, String displayName) throws RemoteException {
1077         final Bundle in = new Bundle();
1078         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
1079         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
1080         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
1081 
1082         final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
1083         return out.getParcelable(DocumentsContract.EXTRA_URI);
1084     }
1085 
1086     /** {@hide} */
isChildDocument(ContentProviderClient client, Uri parentDocumentUri, Uri childDocumentUri)1087     public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
1088             Uri childDocumentUri) throws RemoteException {
1089 
1090         final Bundle in = new Bundle();
1091         in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
1092         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
1093 
1094         final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
1095         if (out == null) {
1096             throw new RemoteException("Failed to get a reponse from isChildDocument query.");
1097         }
1098         if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
1099             throw new RemoteException("Response did not include result field..");
1100         }
1101         return out.getBoolean(DocumentsContract.EXTRA_RESULT);
1102     }
1103 
1104     /**
1105      * Change the display name of an existing document.
1106      * <p>
1107      * If the underlying provider needs to create a new
1108      * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
1109      * name, that new document is returned and the original document is no
1110      * longer valid. Otherwise, the original document is returned.
1111      *
1112      * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
1113      * @param displayName updated name for document
1114      * @return the existing or new document after the rename, or {@code null} if
1115      *         failed.
1116      */
renameDocument(ContentResolver resolver, Uri documentUri, String displayName)1117     public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
1118             String displayName) {
1119         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1120                 documentUri.getAuthority());
1121         try {
1122             return renameDocument(client, documentUri, displayName);
1123         } catch (Exception e) {
1124             Log.w(TAG, "Failed to rename document", e);
1125             return null;
1126         } finally {
1127             ContentProviderClient.releaseQuietly(client);
1128         }
1129     }
1130 
1131     /** {@hide} */
renameDocument(ContentProviderClient client, Uri documentUri, String displayName)1132     public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
1133             String displayName) throws RemoteException {
1134         final Bundle in = new Bundle();
1135         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1136         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
1137 
1138         final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
1139         final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
1140         return (outUri != null) ? outUri : documentUri;
1141     }
1142 
1143     /**
1144      * Delete the given document.
1145      *
1146      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
1147      * @return if the document was deleted successfully.
1148      */
deleteDocument(ContentResolver resolver, Uri documentUri)1149     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
1150         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1151                 documentUri.getAuthority());
1152         try {
1153             deleteDocument(client, documentUri);
1154             return true;
1155         } catch (Exception e) {
1156             Log.w(TAG, "Failed to delete document", e);
1157             return false;
1158         } finally {
1159             ContentProviderClient.releaseQuietly(client);
1160         }
1161     }
1162 
1163     /** {@hide} */
deleteDocument(ContentProviderClient client, Uri documentUri)1164     public static void deleteDocument(ContentProviderClient client, Uri documentUri)
1165             throws RemoteException {
1166         final Bundle in = new Bundle();
1167         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1168 
1169         client.call(METHOD_DELETE_DOCUMENT, null, in);
1170     }
1171 
1172     /**
1173      * Copies the given document.
1174      *
1175      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
1176      * @param targetParentDocumentUri document which will become a parent of the source
1177      *         document's copy.
1178      * @return the copied document, or {@code null} if failed.
1179      */
copyDocument(ContentResolver resolver, Uri sourceDocumentUri, Uri targetParentDocumentUri)1180     public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
1181             Uri targetParentDocumentUri) {
1182         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1183                 sourceDocumentUri.getAuthority());
1184         try {
1185             return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
1186         } catch (Exception e) {
1187             Log.w(TAG, "Failed to copy document", e);
1188             return null;
1189         } finally {
1190             ContentProviderClient.releaseQuietly(client);
1191         }
1192     }
1193 
1194     /** {@hide} */
copyDocument(ContentProviderClient client, Uri sourceDocumentUri, Uri targetParentDocumentUri)1195     public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
1196             Uri targetParentDocumentUri) throws RemoteException {
1197         final Bundle in = new Bundle();
1198         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
1199         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1200 
1201         final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
1202         return out.getParcelable(DocumentsContract.EXTRA_URI);
1203     }
1204 
1205     /**
1206      * Moves the given document under a new parent.
1207      *
1208      * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
1209      * @param sourceParentDocumentUri parent document of the document to move.
1210      * @param targetParentDocumentUri document which will become a new parent of the source
1211      *         document.
1212      * @return the moved document, or {@code null} if failed.
1213      */
moveDocument(ContentResolver resolver, Uri sourceDocumentUri, Uri sourceParentDocumentUri, Uri targetParentDocumentUri)1214     public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
1215             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) {
1216         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1217                 sourceDocumentUri.getAuthority());
1218         try {
1219             return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
1220                     targetParentDocumentUri);
1221         } catch (Exception e) {
1222             Log.w(TAG, "Failed to move document", e);
1223             return null;
1224         } finally {
1225             ContentProviderClient.releaseQuietly(client);
1226         }
1227     }
1228 
1229     /** {@hide} */
moveDocument(ContentProviderClient client, Uri sourceDocumentUri, Uri sourceParentDocumentUri, Uri targetParentDocumentUri)1230     public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
1231             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
1232         final Bundle in = new Bundle();
1233         in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
1234         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
1235         in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1236 
1237         final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
1238         return out.getParcelable(DocumentsContract.EXTRA_URI);
1239     }
1240 
1241     /**
1242      * Removes the given document from a parent directory.
1243      *
1244      * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
1245      * This method is especially useful if the document can be in multiple parents.
1246      *
1247      * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
1248      * @param parentDocumentUri parent document of the document to remove.
1249      * @return true if the document was removed successfully.
1250      */
removeDocument(ContentResolver resolver, Uri documentUri, Uri parentDocumentUri)1251     public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
1252             Uri parentDocumentUri) {
1253         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1254                 documentUri.getAuthority());
1255         try {
1256             removeDocument(client, documentUri, parentDocumentUri);
1257             return true;
1258         } catch (Exception e) {
1259             Log.w(TAG, "Failed to remove document", e);
1260             return false;
1261         } finally {
1262             ContentProviderClient.releaseQuietly(client);
1263         }
1264     }
1265 
1266     /** {@hide} */
removeDocument(ContentProviderClient client, Uri documentUri, Uri parentDocumentUri)1267     public static void removeDocument(ContentProviderClient client, Uri documentUri,
1268             Uri parentDocumentUri) throws RemoteException {
1269         final Bundle in = new Bundle();
1270         in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1271         in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
1272 
1273         client.call(METHOD_REMOVE_DOCUMENT, null, in);
1274     }
1275 
1276     /**
1277      * Open the given image for thumbnail purposes, using any embedded EXIF
1278      * thumbnail if available, and providing orientation hints from the parent
1279      * image.
1280      *
1281      * @hide
1282      */
openImageThumbnail(File file)1283     public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
1284         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
1285                 file, ParcelFileDescriptor.MODE_READ_ONLY);
1286         Bundle extras = null;
1287 
1288         try {
1289             final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
1290 
1291             switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
1292                 case ExifInterface.ORIENTATION_ROTATE_90:
1293                     extras = new Bundle(1);
1294                     extras.putInt(EXTRA_ORIENTATION, 90);
1295                     break;
1296                 case ExifInterface.ORIENTATION_ROTATE_180:
1297                     extras = new Bundle(1);
1298                     extras.putInt(EXTRA_ORIENTATION, 180);
1299                     break;
1300                 case ExifInterface.ORIENTATION_ROTATE_270:
1301                     extras = new Bundle(1);
1302                     extras.putInt(EXTRA_ORIENTATION, 270);
1303                     break;
1304             }
1305 
1306             final long[] thumb = exif.getThumbnailRange();
1307             if (thumb != null) {
1308                 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
1309             }
1310         } catch (IOException e) {
1311         }
1312 
1313         return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
1314     }
1315 }
1316