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