DocumentsContract.java revision f6db154975ef575479ba4ab59d80bcf592288252
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/root/sdcard/search/?query=pony
68    // content://com.example/document/12/
69    // content://com.example/document/12/children/
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         */
178        public static final String COLUMN_FLAGS = "flags";
179
180        /**
181         * Size of a document, in bytes, or {@code null} if unknown.
182         * <p>
183         * Type: INTEGER (long)
184         */
185        public static final String COLUMN_SIZE = OpenableColumns.SIZE;
186
187        /**
188         * MIME type of a document which is a directory that may contain
189         * additional documents.
190         *
191         * @see #COLUMN_MIME_TYPE
192         */
193        public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
194
195        /**
196         * Flag indicating that a document can be represented as a thumbnail.
197         *
198         * @see #COLUMN_FLAGS
199         * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
200         *      Point, CancellationSignal)
201         * @see DocumentsProvider#openDocumentThumbnail(String, Point,
202         *      android.os.CancellationSignal)
203         */
204        public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
205
206        /**
207         * Flag indicating that a document supports writing.
208         * <p>
209         * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
210         * the calling application is granted both
211         * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
212         * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
213         * writability of a document may change over time, for example due to
214         * remote access changes. This flag indicates that a document client can
215         * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
216         *
217         * @see #COLUMN_FLAGS
218         */
219        public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
220
221        /**
222         * Flag indicating that a document is deletable.
223         *
224         * @see #COLUMN_FLAGS
225         * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
226         * @see DocumentsProvider#deleteDocument(String)
227         */
228        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
229
230        /**
231         * Flag indicating that a document is a directory that supports creation
232         * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
233         * {@link #MIME_TYPE_DIR}.
234         *
235         * @see #COLUMN_FLAGS
236         * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
237         *      String)
238         * @see DocumentsProvider#createDocument(String, String, String)
239         */
240        public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
241
242        /**
243         * Flag indicating that a directory prefers its contents be shown in a
244         * larger format grid. Usually suitable when a directory contains mostly
245         * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
246         * {@link #MIME_TYPE_DIR}.
247         *
248         * @see #COLUMN_FLAGS
249         */
250        public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
251
252        /**
253         * Flag indicating that a directory prefers its contents be sorted by
254         * {@link #COLUMN_LAST_MODIFIED}. Only valid when
255         * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
256         *
257         * @see #COLUMN_FLAGS
258         */
259        public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
260
261        /**
262         * Flag indicating that document titles should be hidden when viewing
263         * this directory in a larger format grid. For example, a directory
264         * containing only images may want the image thumbnails to speak for
265         * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is
266         * {@link #MIME_TYPE_DIR}.
267         *
268         * @see #COLUMN_FLAGS
269         * @see #FLAG_DIR_PREFERS_GRID
270         */
271        public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 6;
272    }
273
274    /**
275     * Constants related to a root of documents, including {@link Cursor}
276     * columns names and flags.
277     * <p>
278     * All columns are <em>read-only</em> to client applications.
279     */
280    public final static class Root {
281        private Root() {
282        }
283
284        /**
285         * Unique ID of a root. This ID is both provided by and interpreted by a
286         * {@link DocumentsProvider}, and should be treated as an opaque value
287         * by client applications.
288         * <p>
289         * Type: STRING
290         */
291        public static final String COLUMN_ROOT_ID = "root_id";
292
293        /**
294         * Type of a root, used for clustering when presenting multiple roots to
295         * a user.
296         * <p>
297         * Type: INTEGER (int)
298         *
299         * @see #ROOT_TYPE_SERVICE
300         * @see #ROOT_TYPE_SHORTCUT
301         * @see #ROOT_TYPE_DEVICE
302         */
303        public static final String COLUMN_ROOT_TYPE = "root_type";
304
305        /**
306         * Flags that apply to a root.
307         * <p>
308         * Type: INTEGER (int)
309         *
310         * @see #FLAG_ADVANCED
311         * @see #FLAG_EMPTY
312         * @see #FLAG_LOCAL_ONLY
313         * @see #FLAG_SUPPORTS_CREATE
314         * @see #FLAG_SUPPORTS_RECENTS
315         * @see #FLAG_SUPPORTS_SEARCH
316         */
317        public static final String COLUMN_FLAGS = "flags";
318
319        /**
320         * Icon resource ID for a root.
321         * <p>
322         * Type: INTEGER (int)
323         */
324        public static final String COLUMN_ICON = "icon";
325
326        /**
327         * Title for a root, which will be shown to a user.
328         * <p>
329         * Type: STRING
330         */
331        public static final String COLUMN_TITLE = "title";
332
333        /**
334         * Summary for this root, which may be shown to a user. The summary may
335         * be {@code null}.
336         * <p>
337         * Type: STRING
338         */
339        public static final String COLUMN_SUMMARY = "summary";
340
341        /**
342         * Document which is a directory that represents the top directory of
343         * this root.
344         * <p>
345         * Type: STRING
346         *
347         * @see Document#COLUMN_DOCUMENT_ID
348         */
349        public static final String COLUMN_DOCUMENT_ID = "document_id";
350
351        /**
352         * Number of bytes available in this root, or {@code null} if unknown or
353         * unbounded.
354         * <p>
355         * Type: INTEGER (long)
356         */
357        public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
358
359        /**
360         * MIME types supported by this root, or {@code null} if the root
361         * supports all MIME types. Multiple MIME types can be separated by a
362         * newline. For example, a root supporting audio might use
363         * "audio/*\napplication/x-flac".
364         * <p>
365         * Type: String
366         */
367        public static final String COLUMN_MIME_TYPES = "mime_types";
368
369        /** {@hide} */
370        public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
371
372        /**
373         * Type of root that represents a storage service, such as a cloud-based
374         * service.
375         *
376         * @see #COLUMN_ROOT_TYPE
377         */
378        public static final int ROOT_TYPE_SERVICE = 1;
379
380        /**
381         * Type of root that represents a shortcut to content that may be
382         * available elsewhere through another storage root.
383         *
384         * @see #COLUMN_ROOT_TYPE
385         */
386        public static final int ROOT_TYPE_SHORTCUT = 2;
387
388        /**
389         * Type of root that represents a physical storage device.
390         *
391         * @see #COLUMN_ROOT_TYPE
392         */
393        public static final int ROOT_TYPE_DEVICE = 3;
394
395        /**
396         * Flag indicating that at least one directory under this root supports
397         * creating content. Roots with this flag will be shown when an
398         * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
399         *
400         * @see #COLUMN_FLAGS
401         */
402        public static final int FLAG_SUPPORTS_CREATE = 1;
403
404        /**
405         * Flag indicating that this root offers content that is strictly local
406         * on the device. That is, no network requests are made for the content.
407         *
408         * @see #COLUMN_FLAGS
409         * @see Intent#EXTRA_LOCAL_ONLY
410         */
411        public static final int FLAG_LOCAL_ONLY = 1 << 1;
412
413        /**
414         * Flag indicating that this root should only be visible to advanced
415         * users.
416         *
417         * @see #COLUMN_FLAGS
418         */
419        public static final int FLAG_ADVANCED = 1 << 2;
420
421        /**
422         * Flag indicating that this root can report recently modified
423         * documents.
424         *
425         * @see #COLUMN_FLAGS
426         * @see DocumentsContract#buildRecentDocumentsUri(String, String)
427         */
428        public static final int FLAG_SUPPORTS_RECENTS = 1 << 3;
429
430        /**
431         * Flag indicating that this root supports search.
432         *
433         * @see #COLUMN_FLAGS
434         * @see DocumentsProvider#querySearchDocuments(String, String,
435         *      String[])
436         */
437        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
438
439        /**
440         * Flag indicating that this root is currently empty. This may be used
441         * to hide the root when opening documents, but the root will still be
442         * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
443         * also set.
444         *
445         * @see #COLUMN_FLAGS
446         * @see DocumentsProvider#querySearchDocuments(String, String,
447         *      String[])
448         */
449        public static final int FLAG_EMPTY = 1 << 5;
450    }
451
452    /**
453     * Optional boolean flag included in a directory {@link Cursor#getExtras()}
454     * indicating that a document provider is still loading data. For example, a
455     * provider has returned some results, but is still waiting on an
456     * outstanding network request. The provider must send a content changed
457     * notification when loading is finished.
458     *
459     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
460     *      boolean)
461     */
462    public static final String EXTRA_LOADING = "loading";
463
464    /**
465     * Optional string included in a directory {@link Cursor#getExtras()}
466     * providing an informational message that should be shown to a user. For
467     * example, a provider may wish to indicate that not all documents are
468     * available.
469     */
470    public static final String EXTRA_INFO = "info";
471
472    /**
473     * Optional string included in a directory {@link Cursor#getExtras()}
474     * providing an error message that should be shown to a user. For example, a
475     * provider may wish to indicate that a network error occurred. The user may
476     * choose to retry, resulting in a new query.
477     */
478    public static final String EXTRA_ERROR = "error";
479
480    /** {@hide} */
481    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
482    /** {@hide} */
483    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
484
485    /** {@hide} */
486    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
487
488    private static final String PATH_ROOT = "root";
489    private static final String PATH_RECENT = "recent";
490    private static final String PATH_DOCUMENT = "document";
491    private static final String PATH_CHILDREN = "children";
492    private static final String PATH_SEARCH = "search";
493
494    private static final String PARAM_QUERY = "query";
495    private static final String PARAM_MANAGE = "manage";
496
497    /**
498     * Build Uri representing the roots of a document provider. When queried, a
499     * provider will return one or more rows with columns defined by
500     * {@link Root}.
501     *
502     * @see DocumentsProvider#queryRoots(String[])
503     */
504    public static Uri buildRootsUri(String authority) {
505        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
506                .authority(authority).appendPath(PATH_ROOT).build();
507    }
508
509    /**
510     * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a
511     * document provider.
512     *
513     * @see #getRootId(Uri)
514     */
515    public static Uri buildRootUri(String authority, String rootId) {
516        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
517                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
518    }
519
520    /**
521     * Build Uri representing the recently modified documents of a specific root
522     * in a document provider. When queried, a provider will return zero or more
523     * rows with columns defined by {@link Document}.
524     *
525     * @see DocumentsProvider#queryRecentDocuments(String, String[])
526     * @see #getRootId(Uri)
527     */
528    public static Uri buildRecentDocumentsUri(String authority, String rootId) {
529        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
530                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
531                .appendPath(PATH_RECENT).build();
532    }
533
534    /**
535     * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
536     * document provider. When queried, a provider will return a single row with
537     * columns defined by {@link Document}.
538     *
539     * @see DocumentsProvider#queryDocument(String, String[])
540     * @see #getDocumentId(Uri)
541     */
542    public static Uri buildDocumentUri(String authority, String documentId) {
543        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
544                .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
545    }
546
547    /**
548     * Build Uri representing the children of the given directory in a document
549     * provider. When queried, a provider will return zero or more rows with
550     * columns defined by {@link Document}.
551     *
552     * @param parentDocumentId the document to return children for, which must
553     *            be a directory with MIME type of
554     *            {@link Document#MIME_TYPE_DIR}.
555     * @see DocumentsProvider#queryChildDocuments(String, String[], String)
556     * @see #getDocumentId(Uri)
557     */
558    public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
559        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
560                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
561                .build();
562    }
563
564    /**
565     * Build Uri representing a search for matching documents under a specific
566     * root in a document provider. When queried, a provider will return zero or
567     * more rows with columns defined by {@link Document}.
568     *
569     * @see DocumentsProvider#querySearchDocuments(String, String, String[])
570     * @see #getRootId(Uri)
571     * @see #getSearchDocumentsQuery(Uri)
572     */
573    public static Uri buildSearchDocumentsUri(
574            String authority, String rootId, String query) {
575        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
576                .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
577                .appendQueryParameter(PARAM_QUERY, query).build();
578    }
579
580    /**
581     * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
582     */
583    public static String getRootId(Uri rootUri) {
584        final List<String> paths = rootUri.getPathSegments();
585        if (paths.size() < 2) {
586            throw new IllegalArgumentException("Not a root: " + rootUri);
587        }
588        if (!PATH_ROOT.equals(paths.get(0))) {
589            throw new IllegalArgumentException("Not a root: " + rootUri);
590        }
591        return paths.get(1);
592    }
593
594    /**
595     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
596     */
597    public static String getDocumentId(Uri documentUri) {
598        final List<String> paths = documentUri.getPathSegments();
599        if (paths.size() < 2) {
600            throw new IllegalArgumentException("Not a document: " + documentUri);
601        }
602        if (!PATH_DOCUMENT.equals(paths.get(0))) {
603            throw new IllegalArgumentException("Not a document: " + documentUri);
604        }
605        return paths.get(1);
606    }
607
608    /**
609     * Extract the search query from a Uri built by
610     * {@link #buildSearchDocumentsUri(String, String, String)}.
611     */
612    public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
613        return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
614    }
615
616    /** {@hide} */
617    public static Uri setManageMode(Uri uri) {
618        return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
619    }
620
621    /** {@hide} */
622    public static boolean isManageMode(Uri uri) {
623        return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
624    }
625
626    /**
627     * Return list of all documents that the calling package has "open." These
628     * are Uris matching {@link DocumentsContract} to which persistent
629     * read/write access has been granted, usually through
630     * {@link Intent#ACTION_OPEN_DOCUMENT} or
631     * {@link Intent#ACTION_CREATE_DOCUMENT}.
632     *
633     * @see Context#grantUriPermission(String, Uri, int)
634     * @see Context#revokeUriPermission(Uri, int)
635     * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
636     */
637    public static Uri[] getOpenDocuments(Context context) {
638        final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
639                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION;
640        final Uri[] uris = context.getContentResolver()
641                .getIncomingUriPermissionGrants(openedFlags, openedFlags);
642
643        // Filter to only include document providers
644        final PackageManager pm = context.getPackageManager();
645        final List<Uri> result = Lists.newArrayList();
646        for (Uri uri : uris) {
647            final ProviderInfo info = pm.resolveContentProvider(
648                    uri.getAuthority(), PackageManager.GET_META_DATA);
649            if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) {
650                result.add(uri);
651            }
652        }
653
654        return result.toArray(new Uri[result.size()]);
655    }
656
657    /**
658     * Return thumbnail representing the document at the given Uri. Callers are
659     * responsible for their own in-memory caching.
660     *
661     * @param documentUri document to return thumbnail for, which must have
662     *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
663     * @param size optimal thumbnail size desired. A provider may return a
664     *            thumbnail of a different size, but never more than double the
665     *            requested size.
666     * @param signal signal used to indicate that caller is no longer interested
667     *            in the thumbnail.
668     * @return decoded thumbnail, or {@code null} if problem was encountered.
669     * @see DocumentsProvider#openDocumentThumbnail(String, Point,
670     *      android.os.CancellationSignal)
671     */
672    public static Bitmap getDocumentThumbnail(
673            ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
674        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
675                documentUri.getAuthority());
676        try {
677            return getDocumentThumbnail(client, documentUri, size, signal);
678        } catch (RemoteException e) {
679            return null;
680        } finally {
681            ContentProviderClient.closeQuietly(client);
682        }
683    }
684
685    /** {@hide} */
686    public static Bitmap getDocumentThumbnail(
687            ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
688            throws RemoteException {
689        final Bundle openOpts = new Bundle();
690        openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
691
692        AssetFileDescriptor afd = null;
693        try {
694            afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
695
696            final FileDescriptor fd = afd.getFileDescriptor();
697            final long offset = afd.getStartOffset();
698
699            // Try seeking on the returned FD, since it gives us the most
700            // optimal decode path; otherwise fall back to buffering.
701            BufferedInputStream is = null;
702            try {
703                Libcore.os.lseek(fd, offset, SEEK_SET);
704            } catch (ErrnoException e) {
705                is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
706                is.mark(THUMBNAIL_BUFFER_SIZE);
707            }
708
709            // We requested a rough thumbnail size, but the remote size may have
710            // returned something giant, so defensively scale down as needed.
711            final BitmapFactory.Options opts = new BitmapFactory.Options();
712            opts.inJustDecodeBounds = true;
713            if (is != null) {
714                BitmapFactory.decodeStream(is, null, opts);
715            } else {
716                BitmapFactory.decodeFileDescriptor(fd, null, opts);
717            }
718
719            final int widthSample = opts.outWidth / size.x;
720            final int heightSample = opts.outHeight / size.y;
721
722            opts.inJustDecodeBounds = false;
723            opts.inSampleSize = Math.min(widthSample, heightSample);
724            Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
725            if (is != null) {
726                is.reset();
727                return BitmapFactory.decodeStream(is, null, opts);
728            } else {
729                try {
730                    Libcore.os.lseek(fd, offset, SEEK_SET);
731                } catch (ErrnoException e) {
732                    e.rethrowAsIOException();
733                }
734                return BitmapFactory.decodeFileDescriptor(fd, null, opts);
735            }
736        } catch (IOException e) {
737            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
738            return null;
739        } finally {
740            IoUtils.closeQuietly(afd);
741        }
742    }
743
744    /**
745     * Create a new document with given MIME type and display name.
746     *
747     * @param parentDocumentUri directory with
748     *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
749     * @param mimeType MIME type of new document
750     * @param displayName name of new document
751     * @return newly created document, or {@code null} if failed
752     * @hide
753     */
754    public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
755            String mimeType, String displayName) {
756        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
757                parentDocumentUri.getAuthority());
758        try {
759            return createDocument(client, parentDocumentUri, mimeType, displayName);
760        } finally {
761            ContentProviderClient.closeQuietly(client);
762        }
763    }
764
765    /** {@hide} */
766    public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
767            String mimeType, String displayName) {
768        final Bundle in = new Bundle();
769        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
770        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
771        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
772
773        try {
774            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
775            return buildDocumentUri(
776                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
777        } catch (Exception e) {
778            Log.w(TAG, "Failed to create document", e);
779            return null;
780        }
781    }
782
783    /**
784     * Delete the given document.
785     *
786     * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
787     */
788    public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
789        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
790                documentUri.getAuthority());
791        try {
792            return deleteDocument(client, documentUri);
793        } finally {
794            ContentProviderClient.closeQuietly(client);
795        }
796    }
797
798    /** {@hide} */
799    public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) {
800        final Bundle in = new Bundle();
801        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
802
803        try {
804            final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in);
805            return true;
806        } catch (Exception e) {
807            Log.w(TAG, "Failed to delete document", e);
808            return false;
809        }
810    }
811}
812