DocumentsContract.java revision e66c1778f80f4b18e29e018eca3a338f125f23b9
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 libcore.io.OsConstants.SEEK_SET;
21
22import android.content.ContentProviderClient;
23import android.content.ContentResolver;
24import android.content.Intent;
25import android.content.res.AssetFileDescriptor;
26import android.database.Cursor;
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.graphics.Point;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.CancellationSignal;
33import android.os.ParcelFileDescriptor;
34import android.os.ParcelFileDescriptor.OnCloseListener;
35import android.os.RemoteException;
36import android.util.Log;
37
38import libcore.io.ErrnoException;
39import libcore.io.IoUtils;
40import libcore.io.Libcore;
41
42import java.io.BufferedInputStream;
43import java.io.FileDescriptor;
44import java.io.FileInputStream;
45import java.io.IOException;
46import java.util.List;
47
48/**
49 * Defines the contract between a documents provider and the platform.
50 * <p>
51 * To create a document provider, extend {@link DocumentsProvider}, which
52 * provides a foundational implementation of this contract.
53 *
54 * @see DocumentsProvider
55 */
56public final class DocumentsContract {
57    private static final String TAG = "Documents";
58
59    // content://com.example/root/
60    // content://com.example/root/sdcard/
61    // content://com.example/root/sdcard/recent/
62    // content://com.example/root/sdcard/search/?query=pony
63    // content://com.example/document/12/
64    // content://com.example/document/12/children/
65
66    private DocumentsContract() {
67    }
68
69    /** {@hide} */
70    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
71
72    /** {@hide} */
73    public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
74    /** {@hide} */
75    public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
76
77    /**
78     * Buffer is large enough to rewind past any EXIF headers.
79     */
80    private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
81
82    /**
83     * Constants related to a document, including {@link Cursor} columns names
84     * and flags.
85     * <p>
86     * A document can be either an openable file (with a specific MIME type), or
87     * a directory containing additional documents (with the
88     * {@link #MIME_TYPE_DIR} MIME type).
89     * <p>
90     * All columns are <em>read-only</em> to client applications.
91     */
92    public final static class Document {
93        private Document() {
94        }
95
96        /**
97         * Unique ID of a document. This ID is both provided by and interpreted
98         * by a {@link DocumentsProvider}, and should be treated as an opaque
99         * value by client applications.
100         * <p>
101         * Each document must have a unique ID within a provider, but that
102         * single document may be included as a child of multiple directories.
103         * <p>
104         * A provider must always return durable IDs, since they will be used to
105         * issue long-term Uri permission grants when an application interacts
106         * with {@link Intent#ACTION_OPEN_DOCUMENT} and
107         * {@link Intent#ACTION_CREATE_DOCUMENT}.
108         * <p>
109         * Type: STRING
110         */
111        public static final String COLUMN_DOCUMENT_ID = "document_id";
112
113        /**
114         * Concrete MIME type of a document. For example, "image/png" or
115         * "application/pdf" for openable files. A document can also be a
116         * directory containing additional documents, which is represented with
117         * the {@link #MIME_TYPE_DIR} MIME type.
118         * <p>
119         * Type: STRING
120         *
121         * @see #MIME_TYPE_DIR
122         */
123        public static final String COLUMN_MIME_TYPE = "mime_type";
124
125        /**
126         * Display name of a document, used as the primary title displayed to a
127         * user.
128         * <p>
129         * Type: STRING
130         */
131        public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
132
133        /**
134         * Summary of a document, which may be shown to a user. The summary may
135         * be {@code null}.
136         * <p>
137         * Type: STRING
138         */
139        public static final String COLUMN_SUMMARY = "summary";
140
141        /**
142         * Timestamp when a document was last modified, in milliseconds since
143         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
144         * {@link DocumentsProvider} can update this field using events from
145         * {@link OnCloseListener} or other reliable
146         * {@link ParcelFileDescriptor} transports.
147         * <p>
148         * Type: INTEGER (long)
149         *
150         * @see System#currentTimeMillis()
151         */
152        public static final String COLUMN_LAST_MODIFIED = "last_modified";
153
154        /**
155         * Specific icon resource ID for a document, or {@code null} to use
156         * platform default icon based on {@link #COLUMN_MIME_TYPE}.
157         * <p>
158         * Type: INTEGER (int)
159         */
160        public static final String COLUMN_ICON = "icon";
161
162        /**
163         * Flags that apply to a document.
164         * <p>
165         * Type: INTEGER (int)
166         *
167         * @see #FLAG_SUPPORTS_WRITE
168         * @see #FLAG_SUPPORTS_DELETE
169         * @see #FLAG_SUPPORTS_THUMBNAIL
170         * @see #FLAG_DIR_PREFERS_GRID
171         * @see #FLAG_DIR_SUPPORTS_CREATE
172         */
173        public static final String COLUMN_FLAGS = "flags";
174
175        /**
176         * Size of a document, in bytes, or {@code null} if unknown.
177         * <p>
178         * Type: INTEGER (long)
179         */
180        public static final String COLUMN_SIZE = OpenableColumns.SIZE;
181
182        /**
183         * MIME type of a document which is a directory that may contain
184         * additional documents.
185         *
186         * @see #COLUMN_MIME_TYPE
187         */
188        public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
189
190        /**
191         * Flag indicating that a document can be represented as a thumbnail.
192         *
193         * @see #COLUMN_FLAGS
194         * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
195         *      Point, CancellationSignal)
196         * @see DocumentsProvider#openDocumentThumbnail(String, Point,
197         *      android.os.CancellationSignal)
198         */
199        public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
200
201        /**
202         * Flag indicating that a document supports writing.
203         * <p>
204         * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
205         * the calling application is granted both
206         * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
207         * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
208         * writability of a document may change over time, for example due to
209         * remote access changes. This flag indicates that a document client can
210         * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
211         *
212         * @see #COLUMN_FLAGS
213         */
214        public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
215
216        /**
217         * Flag indicating that a document is deletable.
218         *
219         * @see #COLUMN_FLAGS
220         * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
221         * @see DocumentsProvider#deleteDocument(String)
222         */
223        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
224
225        /**
226         * Flag indicating that a document is a directory that supports creation
227         * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
228         * {@link #MIME_TYPE_DIR}.
229         *
230         * @see #COLUMN_FLAGS
231         * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
232         *      String)
233         * @see DocumentsProvider#createDocument(String, String, String)
234         */
235        public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
236
237        /**
238         * Flag indicating that a directory prefers its contents be shown in a
239         * larger format grid. Usually suitable when a directory contains mostly
240         * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
241         * {@link #MIME_TYPE_DIR}.
242         *
243         * @see #COLUMN_FLAGS
244         */
245        public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
246
247        /**
248         * Flag indicating that a directory prefers its contents be sorted by
249         * {@link #COLUMN_LAST_MODIFIED}. Only valid when
250         * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
251         *
252         * @see #COLUMN_FLAGS
253         */
254        public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
255
256        /**
257         * Flag indicating that document titles should be hidden when viewing
258         * this directory in a larger format grid. For example, a directory
259         * containing only images may want the image thumbnails to speak for
260         * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is
261         * {@link #MIME_TYPE_DIR}.
262         *
263         * @see #COLUMN_FLAGS
264         * @see #FLAG_DIR_PREFERS_GRID
265         */
266        public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 6;
267    }
268
269    /**
270     * Constants related to a root of documents, including {@link Cursor}
271     * columns names and flags.
272     * <p>
273     * All columns are <em>read-only</em> to client applications.
274     */
275    public final static class Root {
276        private Root() {
277        }
278
279        /**
280         * Unique ID of a root. This ID is both provided by and interpreted by a
281         * {@link DocumentsProvider}, and should be treated as an opaque value
282         * by client applications.
283         * <p>
284         * Type: STRING
285         */
286        public static final String COLUMN_ROOT_ID = "root_id";
287
288        /**
289         * Type of a root, used for clustering when presenting multiple roots to
290         * a user.
291         * <p>
292         * Type: INTEGER (int)
293         *
294         * @see #ROOT_TYPE_SERVICE
295         * @see #ROOT_TYPE_SHORTCUT
296         * @see #ROOT_TYPE_DEVICE
297         */
298        public static final String COLUMN_ROOT_TYPE = "root_type";
299
300        /**
301         * Flags that apply to a root.
302         * <p>
303         * Type: INTEGER (int)
304         *
305         * @see #FLAG_ADVANCED
306         * @see #FLAG_EMPTY
307         * @see #FLAG_LOCAL_ONLY
308         * @see #FLAG_SUPPORTS_CREATE
309         * @see #FLAG_SUPPORTS_RECENTS
310         * @see #FLAG_SUPPORTS_SEARCH
311         */
312        public static final String COLUMN_FLAGS = "flags";
313
314        /**
315         * Icon resource ID for a root.
316         * <p>
317         * Type: INTEGER (int)
318         */
319        public static final String COLUMN_ICON = "icon";
320
321        /**
322         * Title for a root, which will be shown to a user.
323         * <p>
324         * Type: STRING
325         */
326        public static final String COLUMN_TITLE = "title";
327
328        /**
329         * Summary for this root, which may be shown to a user. The summary may
330         * be {@code null}.
331         * <p>
332         * Type: STRING
333         */
334        public static final String COLUMN_SUMMARY = "summary";
335
336        /**
337         * Document which is a directory that represents the top directory of
338         * this root.
339         * <p>
340         * Type: STRING
341         *
342         * @see Document#COLUMN_DOCUMENT_ID
343         */
344        public static final String COLUMN_DOCUMENT_ID = "document_id";
345
346        /**
347         * Number of bytes available in this root, or {@code null} if unknown or
348         * unbounded.
349         * <p>
350         * Type: INTEGER (long)
351         */
352        public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
353
354        /**
355         * MIME types supported by this root, or {@code null} if the root
356         * supports all MIME types. Multiple MIME types can be separated by a
357         * newline. For example, a root supporting audio might use
358         * "audio/*\napplication/x-flac".
359         * <p>
360         * Type: String
361         */
362        public static final String COLUMN_MIME_TYPES = "mime_types";
363
364        /** {@hide} */
365        public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
366
367        /**
368         * Type of root that represents a storage service, such as a cloud-based
369         * service.
370         *
371         * @see #COLUMN_ROOT_TYPE
372         */
373        public static final int ROOT_TYPE_SERVICE = 1;
374
375        /**
376         * Type of root that represents a shortcut to content that may be
377         * available elsewhere through another storage root.
378         *
379         * @see #COLUMN_ROOT_TYPE
380         */
381        public static final int ROOT_TYPE_SHORTCUT = 2;
382
383        /**
384         * Type of root that represents a physical storage device.
385         *
386         * @see #COLUMN_ROOT_TYPE
387         */
388        public static final int ROOT_TYPE_DEVICE = 3;
389
390        /**
391         * Flag indicating that at least one directory under this root supports
392         * creating content. Roots with this flag will be shown when an
393         * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
394         *
395         * @see #COLUMN_FLAGS
396         */
397        public static final int FLAG_SUPPORTS_CREATE = 1;
398
399        /**
400         * Flag indicating that this root offers content that is strictly local
401         * on the device. That is, no network requests are made for the content.
402         *
403         * @see #COLUMN_FLAGS
404         * @see Intent#EXTRA_LOCAL_ONLY
405         */
406        public static final int FLAG_LOCAL_ONLY = 1 << 1;
407
408        /**
409         * Flag indicating that this root should only be visible to advanced
410         * users.
411         *
412         * @see #COLUMN_FLAGS
413         */
414        public static final int FLAG_ADVANCED = 1 << 2;
415
416        /**
417         * Flag indicating that this root can report recently modified
418         * documents.
419         *
420         * @see #COLUMN_FLAGS
421         * @see DocumentsContract#buildRecentDocumentsUri(String, String)
422         */
423        public static final int FLAG_SUPPORTS_RECENTS = 1 << 3;
424
425        /**
426         * Flag indicating that this root supports search.
427         *
428         * @see #COLUMN_FLAGS
429         * @see DocumentsProvider#querySearchDocuments(String, String,
430         *      String[])
431         */
432        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
433
434        /**
435         * Flag indicating that this root is currently empty. This may be used
436         * to hide the root when opening documents, but the root will still be
437         * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
438         * also set.
439         *
440         * @see #COLUMN_FLAGS
441         * @see DocumentsProvider#querySearchDocuments(String, String,
442         *      String[])
443         */
444        public static final int FLAG_EMPTY = 1 << 5;
445    }
446
447    /**
448     * Optional boolean flag included in a directory {@link Cursor#getExtras()}
449     * indicating that a document provider is still loading data. For example, a
450     * provider has returned some results, but is still waiting on an
451     * outstanding network request. The provider must send a content changed
452     * notification when loading is finished.
453     *
454     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
455     *      boolean)
456     */
457    public static final String EXTRA_LOADING = "loading";
458
459    /**
460     * Optional string included in a directory {@link Cursor#getExtras()}
461     * providing an informational message that should be shown to a user. For
462     * example, a provider may wish to indicate that not all documents are
463     * available.
464     */
465    public static final String EXTRA_INFO = "info";
466
467    /**
468     * Optional string included in a directory {@link Cursor#getExtras()}
469     * providing an error message that should be shown to a user. For example, a
470     * provider may wish to indicate that a network error occurred. The user may
471     * choose to retry, resulting in a new query.
472     */
473    public static final String EXTRA_ERROR = "error";
474
475    /** {@hide} */
476    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
477    /** {@hide} */
478    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
479
480    /** {@hide} */
481    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
482
483    private static final String PATH_ROOT = "root";
484    private static final String PATH_RECENT = "recent";
485    private static final String PATH_DOCUMENT = "document";
486    private static final String PATH_CHILDREN = "children";
487    private static final String PATH_SEARCH = "search";
488
489    private static final String PARAM_QUERY = "query";
490    private static final String PARAM_MANAGE = "manage";
491
492    /**
493     * Build Uri representing the roots of a document provider. When queried, a
494     * provider will return one or more rows with columns defined by
495     * {@link Root}.
496     *
497     * @see DocumentsProvider#queryRoots(String[])
498     */
499    public static Uri buildRootsUri(String authority) {
500        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
501                .authority(authority).appendPath(PATH_ROOT).build();
502    }
503
504    /**
505     * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a
506     * document provider.
507     *
508     * @see #getRootId(Uri)
509     */
510    public static Uri buildRootUri(String authority, String rootId) {
511        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
512                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
513    }
514
515    /**
516     * Build Uri representing the recently modified documents of a specific root
517     * in a document provider. When queried, a provider will return zero or more
518     * rows with columns defined by {@link Document}.
519     *
520     * @see DocumentsProvider#queryRecentDocuments(String, String[])
521     * @see #getRootId(Uri)
522     */
523    public static Uri buildRecentDocumentsUri(String authority, String rootId) {
524        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
525                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
526                .appendPath(PATH_RECENT).build();
527    }
528
529    /**
530     * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
531     * document provider. When queried, a provider will return a single row with
532     * columns defined by {@link Document}.
533     *
534     * @see DocumentsProvider#queryDocument(String, String[])
535     * @see #getDocumentId(Uri)
536     */
537    public static Uri buildDocumentUri(String authority, String documentId) {
538        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
539                .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
540    }
541
542    /**
543     * Build Uri representing the children of the given directory in a document
544     * provider. When queried, a provider will return zero or more rows with
545     * columns defined by {@link Document}.
546     *
547     * @param parentDocumentId the document to return children for, which must
548     *            be a directory with MIME type of
549     *            {@link Document#MIME_TYPE_DIR}.
550     * @see DocumentsProvider#queryChildDocuments(String, String[], String)
551     * @see #getDocumentId(Uri)
552     */
553    public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
554        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
555                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
556                .build();
557    }
558
559    /**
560     * Build Uri representing a search for matching documents under a specific
561     * root in a document provider. When queried, a provider will return zero or
562     * more rows with columns defined by {@link Document}.
563     *
564     * @see DocumentsProvider#querySearchDocuments(String, String, String[])
565     * @see #getRootId(Uri)
566     * @see #getSearchDocumentsQuery(Uri)
567     */
568    public static Uri buildSearchDocumentsUri(
569            String authority, String rootId, String query) {
570        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
571                .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
572                .appendQueryParameter(PARAM_QUERY, query).build();
573    }
574
575    /**
576     * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
577     */
578    public static String getRootId(Uri rootUri) {
579        final List<String> paths = rootUri.getPathSegments();
580        if (paths.size() < 2) {
581            throw new IllegalArgumentException("Not a root: " + rootUri);
582        }
583        if (!PATH_ROOT.equals(paths.get(0))) {
584            throw new IllegalArgumentException("Not a root: " + rootUri);
585        }
586        return paths.get(1);
587    }
588
589    /**
590     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
591     */
592    public static String getDocumentId(Uri documentUri) {
593        final List<String> paths = documentUri.getPathSegments();
594        if (paths.size() < 2) {
595            throw new IllegalArgumentException("Not a document: " + documentUri);
596        }
597        if (!PATH_DOCUMENT.equals(paths.get(0))) {
598            throw new IllegalArgumentException("Not a document: " + documentUri);
599        }
600        return paths.get(1);
601    }
602
603    /**
604     * Extract the search query from a Uri built by
605     * {@link #buildSearchDocumentsUri(String, String, String)}.
606     */
607    public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
608        return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
609    }
610
611    /** {@hide} */
612    public static Uri setManageMode(Uri uri) {
613        return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
614    }
615
616    /** {@hide} */
617    public static boolean isManageMode(Uri uri) {
618        return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
619    }
620
621    /**
622     * Return thumbnail representing the document at the given Uri. Callers are
623     * responsible for their own in-memory caching.
624     *
625     * @param documentUri document to return thumbnail for, which must have
626     *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
627     * @param size optimal thumbnail size desired. A provider may return a
628     *            thumbnail of a different size, but never more than double the
629     *            requested size.
630     * @param signal signal used to indicate that caller is no longer interested
631     *            in the thumbnail.
632     * @return decoded thumbnail, or {@code null} if problem was encountered.
633     * @see DocumentsProvider#openDocumentThumbnail(String, Point,
634     *      android.os.CancellationSignal)
635     */
636    public static Bitmap getDocumentThumbnail(
637            ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
638        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
639                documentUri.getAuthority());
640        try {
641            return getDocumentThumbnail(client, documentUri, size, signal);
642        } catch (RemoteException e) {
643            return null;
644        } finally {
645            ContentProviderClient.closeQuietly(client);
646        }
647    }
648
649    /** {@hide} */
650    public static Bitmap getDocumentThumbnail(
651            ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
652            throws RemoteException {
653        final Bundle openOpts = new Bundle();
654        openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
655
656        AssetFileDescriptor afd = null;
657        try {
658            afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
659
660            final FileDescriptor fd = afd.getFileDescriptor();
661            final long offset = afd.getStartOffset();
662
663            // Try seeking on the returned FD, since it gives us the most
664            // optimal decode path; otherwise fall back to buffering.
665            BufferedInputStream is = null;
666            try {
667                Libcore.os.lseek(fd, offset, SEEK_SET);
668            } catch (ErrnoException e) {
669                is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
670                is.mark(THUMBNAIL_BUFFER_SIZE);
671            }
672
673            // We requested a rough thumbnail size, but the remote size may have
674            // returned something giant, so defensively scale down as needed.
675            final BitmapFactory.Options opts = new BitmapFactory.Options();
676            opts.inJustDecodeBounds = true;
677            if (is != null) {
678                BitmapFactory.decodeStream(is, null, opts);
679            } else {
680                BitmapFactory.decodeFileDescriptor(fd, null, opts);
681            }
682
683            final int widthSample = opts.outWidth / size.x;
684            final int heightSample = opts.outHeight / size.y;
685
686            opts.inJustDecodeBounds = false;
687            opts.inSampleSize = Math.min(widthSample, heightSample);
688            Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
689            if (is != null) {
690                is.reset();
691                return BitmapFactory.decodeStream(is, null, opts);
692            } else {
693                try {
694                    Libcore.os.lseek(fd, offset, SEEK_SET);
695                } catch (ErrnoException e) {
696                    e.rethrowAsIOException();
697                }
698                return BitmapFactory.decodeFileDescriptor(fd, null, opts);
699            }
700        } catch (IOException e) {
701            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
702            return null;
703        } finally {
704            IoUtils.closeQuietly(afd);
705        }
706    }
707
708    /**
709     * Create a new document with given MIME type and display name.
710     *
711     * @param parentDocumentUri directory with
712     *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
713     * @param mimeType MIME type of new document
714     * @param displayName name of new document
715     * @return newly created document, or {@code null} if failed
716     * @hide
717     */
718    public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
719            String mimeType, String displayName) {
720        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
721                parentDocumentUri.getAuthority());
722        try {
723            return createDocument(client, parentDocumentUri, mimeType, displayName);
724        } finally {
725            ContentProviderClient.closeQuietly(client);
726        }
727    }
728
729    /** {@hide} */
730    public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
731            String mimeType, String displayName) {
732        final Bundle in = new Bundle();
733        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
734        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
735        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
736
737        try {
738            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
739            return buildDocumentUri(
740                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
741        } catch (Exception e) {
742            Log.w(TAG, "Failed to create document", e);
743            return null;
744        }
745    }
746
747    /**
748     * Delete the given document.
749     *
750     * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
751     */
752    public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
753        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
754                documentUri.getAuthority());
755        try {
756            return deleteDocument(client, documentUri);
757        } finally {
758            ContentProviderClient.closeQuietly(client);
759        }
760    }
761
762    /** {@hide} */
763    public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) {
764        final Bundle in = new Bundle();
765        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
766
767        try {
768            final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in);
769            return true;
770        } catch (Exception e) {
771            Log.w(TAG, "Failed to delete document", e);
772            return false;
773        }
774    }
775}
776