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