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