DocumentsContract.java revision 6398343e83b3fd11dd6536cf6f390a52c1e19d2e
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.ContentProvider;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.content.pm.ProviderInfo;
29import android.content.res.AssetFileDescriptor;
30import android.database.Cursor;
31import android.graphics.Bitmap;
32import android.graphics.BitmapFactory;
33import android.graphics.Point;
34import android.net.Uri;
35import android.os.Bundle;
36import android.os.ParcelFileDescriptor;
37import android.os.ParcelFileDescriptor.OnCloseListener;
38import android.util.Log;
39
40import com.google.android.collect.Lists;
41
42import libcore.io.ErrnoException;
43import libcore.io.IoBridge;
44import libcore.io.IoUtils;
45import libcore.io.Libcore;
46
47import java.io.FileDescriptor;
48import java.io.IOException;
49import java.util.List;
50
51/**
52 * Defines the contract between a documents provider and the platform.
53 * <p>
54 * A document provider is a {@link ContentProvider} that presents a set of
55 * documents in a hierarchical structure. The system provides UI that visualizes
56 * all available document providers, offering users the ability to open existing
57 * documents or create new documents.
58 * <p>
59 * Each provider expresses one or more "roots" which each serve as the top-level
60 * of a tree. For example, a root could represent an account, or a physical
61 * storage device. Under each root, documents are referenced by a unique
62 * {@link DocumentColumns#DOC_ID}, and each root starts at the
63 * {@link Documents#DOC_ID_ROOT} document.
64 * <p>
65 * Documents can be either an openable file (with a specific MIME type), or a
66 * directory containing additional documents (with the
67 * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
68 * capabilities, as described by {@link DocumentColumns#FLAGS}. The same
69 * {@link DocumentColumns#DOC_ID} can be included in multiple directories.
70 * <p>
71 * Document providers must be protected with the
72 * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
73 * only be requested by the system. The system-provided UI then issues narrow
74 * Uri permission grants for individual documents when the user explicitly picks
75 * documents.
76 *
77 * @see Intent#ACTION_OPEN_DOCUMENT
78 * @see Intent#ACTION_CREATE_DOCUMENT
79 */
80public final class DocumentsContract {
81    private static final String TAG = "Documents";
82
83    // content://com.example/roots/
84    // content://com.example/roots/sdcard/
85    // content://com.example/roots/sdcard/docs/0/
86    // content://com.example/roots/sdcard/docs/0/contents/
87    // content://com.example/roots/sdcard/docs/0/search/?query=pony
88
89    /** {@hide} */
90    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
91
92    /** {@hide} */
93    public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED";
94
95    /**
96     * Constants for individual documents.
97     */
98    public static class Documents {
99        private Documents() {
100        }
101
102        /**
103         * MIME type of a document which is a directory that may contain additional
104         * documents.
105         *
106         * @see #buildContentsUri(String, String, String)
107         */
108        public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc";
109
110        /**
111         * {@link DocumentColumns#DOC_ID} value representing the root directory of a
112         * documents root.
113         */
114        public static final String DOC_ID_ROOT = "0";
115
116        /**
117         * Flag indicating that a document is a directory that supports creation of
118         * new files within it.
119         *
120         * @see DocumentColumns#FLAGS
121         * @see #createDocument(ContentResolver, Uri, String, String)
122         */
123        public static final int FLAG_SUPPORTS_CREATE = 1;
124
125        /**
126         * Flag indicating that a document is renamable.
127         *
128         * @see DocumentColumns#FLAGS
129         * @see #renameDocument(ContentResolver, Uri, String)
130         */
131        public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
132
133        /**
134         * Flag indicating that a document is deletable.
135         *
136         * @see DocumentColumns#FLAGS
137         */
138        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
139
140        /**
141         * Flag indicating that a document can be represented as a thumbnail.
142         *
143         * @see DocumentColumns#FLAGS
144         * @see #getThumbnail(ContentResolver, Uri, Point)
145         */
146        public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
147
148        /**
149         * Flag indicating that a document is a directory that supports search.
150         *
151         * @see DocumentColumns#FLAGS
152         */
153        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
154
155        /**
156         * Flag indicating that a document is writable.
157         *
158         * @see DocumentColumns#FLAGS
159         */
160        public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
161
162        /**
163         * Flag indicating that a document is a directory that prefers its contents
164         * be shown in a larger format grid. Usually suitable when a directory
165         * contains mostly pictures.
166         *
167         * @see DocumentColumns#FLAGS
168         */
169        public static final int FLAG_PREFERS_GRID = 1 << 6;
170    }
171
172    /**
173     * Optimal dimensions for a document thumbnail request, stored as a
174     * {@link Point} object. This is only a hint, and the returned thumbnail may
175     * have different dimensions.
176     *
177     * @see ContentProvider#openTypedAssetFile(Uri, String, Bundle)
178     */
179    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
180
181    /**
182     * Extra boolean flag included in a directory {@link Cursor#getExtras()}
183     * indicating that the document provider can provide additional data if
184     * requested, such as additional search results.
185     */
186    public static final String EXTRA_HAS_MORE = "has_more";
187
188    /**
189     * Extra boolean flag included in a {@link Cursor#respond(Bundle)} call to a
190     * directory to request that additional data should be fetched. When
191     * requested data is ready, the provider should send a change notification
192     * to cause a requery.
193     *
194     * @see Cursor#respond(Bundle)
195     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
196     *      boolean)
197     */
198    public static final String EXTRA_REQUEST_MORE = "request_more";
199
200    private static final String PATH_ROOTS = "roots";
201    private static final String PATH_DOCS = "docs";
202    private static final String PATH_CONTENTS = "contents";
203    private static final String PATH_SEARCH = "search";
204
205    private static final String PARAM_QUERY = "query";
206    private static final String PARAM_LOCAL_ONLY = "localOnly";
207
208    /**
209     * Build Uri representing the roots offered by a document provider.
210     */
211    public static Uri buildRootsUri(String authority) {
212        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
213                .authority(authority).appendPath(PATH_ROOTS).build();
214    }
215
216    /**
217     * Build Uri representing a specific root offered by a document provider.
218     */
219    public static Uri buildRootUri(String authority, String rootId) {
220        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
221                .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build();
222    }
223
224    /**
225     * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
226     * document provider.
227     */
228    public static Uri buildDocumentUri(String authority, String rootId, String docId) {
229        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
230                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
231                .build();
232    }
233
234    /**
235     * Build Uri representing the contents of the given directory in a document
236     * provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
237     */
238    public static Uri buildContentsUri(String authority, String rootId, String docId) {
239        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
240                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
241                .appendPath(PATH_CONTENTS).build();
242    }
243
244    /**
245     * Build Uri representing a search for matching documents under a specific
246     * directory in a document provider. The given document must have
247     * {@link Documents#FLAG_SUPPORTS_SEARCH}.
248     */
249    public static Uri buildSearchUri(String authority, String rootId, String docId, String query) {
250        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
251                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
252                .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
253    }
254
255    /**
256     * Convenience method for {@link #buildDocumentUri(String, String, String)},
257     * extracting authority and root from the given Uri.
258     */
259    public static Uri buildDocumentUri(Uri relatedUri, String docId) {
260        return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId);
261    }
262
263    /**
264     * Convenience method for {@link #buildContentsUri(String, String, String)},
265     * extracting authority and root from the given Uri.
266     */
267    public static Uri buildContentsUri(Uri relatedUri) {
268        return buildContentsUri(
269                relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri));
270    }
271
272    /**
273     * Convenience method for
274     * {@link #buildSearchUri(String, String, String, String)}, extracting
275     * authority and root from the given Uri.
276     */
277    public static Uri buildSearchUri(Uri relatedUri, String query) {
278        return buildSearchUri(
279                relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query);
280    }
281
282    /**
283     * Extract the {@link RootColumns#ROOT_ID} from the given Uri.
284     */
285    public static String getRootId(Uri documentUri) {
286        final List<String> paths = documentUri.getPathSegments();
287        if (paths.size() < 2) {
288            throw new IllegalArgumentException("Not a root: " + documentUri);
289        }
290        if (!PATH_ROOTS.equals(paths.get(0))) {
291            throw new IllegalArgumentException("Not a root: " + documentUri);
292        }
293        return paths.get(1);
294    }
295
296    /**
297     * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
298     */
299    public static String getDocId(Uri documentUri) {
300        final List<String> paths = documentUri.getPathSegments();
301        if (paths.size() < 4) {
302            throw new IllegalArgumentException("Not a document: " + documentUri);
303        }
304        if (!PATH_ROOTS.equals(paths.get(0))) {
305            throw new IllegalArgumentException("Not a document: " + documentUri);
306        }
307        if (!PATH_DOCS.equals(paths.get(2))) {
308            throw new IllegalArgumentException("Not a document: " + documentUri);
309        }
310        return paths.get(3);
311    }
312
313    /**
314     * Return requested search query from the given Uri, as constructed by
315     * {@link #buildSearchUri(String, String, String, String)}.
316     */
317    public static String getSearchQuery(Uri documentUri) {
318        return documentUri.getQueryParameter(PARAM_QUERY);
319    }
320
321    /**
322     * Mark the given Uri to indicate that only locally-available data should be
323     * returned. That is, no network connections should be initiated to provide
324     * the metadata or content.
325     */
326    public static Uri setLocalOnly(Uri documentUri) {
327        return documentUri.buildUpon()
328                .appendQueryParameter(PARAM_LOCAL_ONLY, String.valueOf(true)).build();
329    }
330
331    /**
332     * Return if the given Uri is requesting that only locally-available data be
333     * returned. That is, no network connections should be initiated to provide
334     * the metadata or content.
335     */
336    public static boolean isLocalOnly(Uri documentUri) {
337        return documentUri.getBooleanQueryParameter(PARAM_LOCAL_ONLY, false);
338    }
339
340    /**
341     * Standard columns for document queries. Document providers <em>must</em>
342     * support at least these columns when queried.
343     *
344     * @see DocumentsContract#buildDocumentUri(String, String, String)
345     * @see DocumentsContract#buildContentsUri(String, String, String)
346     * @see DocumentsContract#buildSearchUri(String, String, String, String)
347     */
348    public interface DocumentColumns extends OpenableColumns {
349        /**
350         * The ID for a document under a storage backend root. Values
351         * <em>must</em> never change once returned. This field is read-only to
352         * document clients.
353         * <p>
354         * Type: STRING
355         */
356        public static final String DOC_ID = "doc_id";
357
358        /**
359         * MIME type of a document, matching the value returned by
360         * {@link ContentResolver#getType(android.net.Uri)}. This field must be
361         * provided when a new document is created. This field is read-only to
362         * document clients.
363         * <p>
364         * Type: STRING
365         *
366         * @see Documents#MIME_TYPE_DIR
367         */
368        public static final String MIME_TYPE = "mime_type";
369
370        /**
371         * Timestamp when a document was last modified, in milliseconds since
372         * January 1, 1970 00:00:00.0 UTC. This field is read-only to document
373         * clients. Document providers can update this field using events from
374         * {@link OnCloseListener} or other reliable
375         * {@link ParcelFileDescriptor} transport.
376         * <p>
377         * Type: INTEGER (long)
378         *
379         * @see System#currentTimeMillis()
380         */
381        public static final String LAST_MODIFIED = "last_modified";
382
383        /**
384         * Flags that apply to a specific document. This field is read-only to
385         * document clients.
386         * <p>
387         * Type: INTEGER (int)
388         */
389        public static final String FLAGS = "flags";
390
391        /**
392         * Summary for this document, or {@code null} to omit. This field is
393         * read-only to document clients.
394         * <p>
395         * Type: STRING
396         */
397        public static final String SUMMARY = "summary";
398    }
399
400    /**
401     * Constants for individual document roots.
402     */
403    public static class Roots {
404        private Roots() {
405        }
406
407        public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/root";
408        public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root";
409
410        /**
411         * Root that represents a storage service, such as a cloud-based
412         * service.
413         *
414         * @see RootColumns#ROOT_TYPE
415         */
416        public static final int ROOT_TYPE_SERVICE = 1;
417
418        /**
419         * Root that represents a shortcut to content that may be available
420         * elsewhere through another storage root.
421         *
422         * @see RootColumns#ROOT_TYPE
423         */
424        public static final int ROOT_TYPE_SHORTCUT = 2;
425
426        /**
427         * Root that represents a physical storage device.
428         *
429         * @see RootColumns#ROOT_TYPE
430         */
431        public static final int ROOT_TYPE_DEVICE = 3;
432
433        /**
434         * Root that represents a physical storage device that should only be
435         * displayed to advanced users.
436         *
437         * @see RootColumns#ROOT_TYPE
438         */
439        public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
440    }
441
442    /**
443     * Standard columns for document root queries.
444     *
445     * @see DocumentsContract#buildRootsUri(String)
446     * @see DocumentsContract#buildRootUri(String, String)
447     */
448    public interface RootColumns {
449        public static final String ROOT_ID = "root_id";
450
451        /**
452         * Storage root type, use for clustering. This field is read-only to
453         * document clients.
454         * <p>
455         * Type: INTEGER (int)
456         *
457         * @see Roots#ROOT_TYPE_SERVICE
458         * @see Roots#ROOT_TYPE_DEVICE
459         */
460        public static final String ROOT_TYPE = "root_type";
461
462        /**
463         * Icon resource ID for this storage root, or {@code null} to use the
464         * default {@link ProviderInfo#icon}. This field is read-only to
465         * document clients.
466         * <p>
467         * Type: INTEGER (int)
468         */
469        public static final String ICON = "icon";
470
471        /**
472         * Title for this storage root, or {@code null} to use the default
473         * {@link ProviderInfo#labelRes}. This field is read-only to document
474         * clients.
475         * <p>
476         * Type: STRING
477         */
478        public static final String TITLE = "title";
479
480        /**
481         * Summary for this storage root, or {@code null} to omit. This field is
482         * read-only to document clients.
483         * <p>
484         * Type: STRING
485         */
486        public static final String SUMMARY = "summary";
487
488        /**
489         * Number of free bytes of available in this storage root, or
490         * {@code null} if unknown or unbounded. This field is read-only to
491         * document clients.
492         * <p>
493         * Type: INTEGER (long)
494         */
495        public static final String AVAILABLE_BYTES = "available_bytes";
496    }
497
498    /**
499     * Return list of all documents that the calling package has "open." These
500     * are Uris matching {@link DocumentsContract} to which persistent
501     * read/write access has been granted, usually through
502     * {@link Intent#ACTION_OPEN_DOCUMENT} or
503     * {@link Intent#ACTION_CREATE_DOCUMENT}.
504     *
505     * @see Context#grantUriPermission(String, Uri, int)
506     * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
507     */
508    public static Uri[] getOpenDocuments(Context context) {
509        final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
510                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION;
511        final Uri[] uris = context.getContentResolver()
512                .getIncomingUriPermissionGrants(openedFlags, openedFlags);
513
514        // Filter to only include document providers
515        final PackageManager pm = context.getPackageManager();
516        final List<Uri> result = Lists.newArrayList();
517        for (Uri uri : uris) {
518            final ProviderInfo info = pm.resolveContentProvider(
519                    uri.getAuthority(), PackageManager.GET_META_DATA);
520            if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) {
521                result.add(uri);
522            }
523        }
524
525        return result.toArray(new Uri[result.size()]);
526    }
527
528    /**
529     * Return thumbnail representing the document at the given URI. Callers are
530     * responsible for their own in-memory caching. Given document must have
531     * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
532     *
533     * @return decoded thumbnail, or {@code null} if problem was encountered.
534     */
535    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
536        final Bundle openOpts = new Bundle();
537        openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
538
539        AssetFileDescriptor afd = null;
540        try {
541            afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
542
543            final FileDescriptor fd = afd.getFileDescriptor();
544            final long offset = afd.getStartOffset();
545            final long length = afd.getDeclaredLength();
546
547            // Some thumbnails might be a region inside a larger file, such as
548            // an EXIF thumbnail. Since BitmapFactory aggressively seeks around
549            // the entire file, we read the region manually.
550            byte[] region = null;
551            if (offset > 0 && length <= 64 * KB_IN_BYTES) {
552                region = new byte[(int) length];
553                Libcore.os.lseek(fd, offset, SEEK_SET);
554                if (IoBridge.read(fd, region, 0, region.length) != region.length) {
555                    region = null;
556                }
557            }
558
559            // We requested a rough thumbnail size, but the remote size may have
560            // returned something giant, so defensively scale down as needed.
561            final BitmapFactory.Options opts = new BitmapFactory.Options();
562            opts.inJustDecodeBounds = true;
563            if (region != null) {
564                BitmapFactory.decodeByteArray(region, 0, region.length, opts);
565            } else {
566                BitmapFactory.decodeFileDescriptor(fd, null, opts);
567            }
568
569            final int widthSample = opts.outWidth / size.x;
570            final int heightSample = opts.outHeight / size.y;
571
572            opts.inJustDecodeBounds = false;
573            opts.inSampleSize = Math.min(widthSample, heightSample);
574            Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
575            if (region != null) {
576                return BitmapFactory.decodeByteArray(region, 0, region.length, opts);
577            } else {
578                return BitmapFactory.decodeFileDescriptor(fd, null, opts);
579            }
580        } catch (ErrnoException e) {
581            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
582            return null;
583        } catch (IOException e) {
584            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
585            return null;
586        } finally {
587            IoUtils.closeQuietly(afd);
588        }
589    }
590
591    /**
592     * Create a new document under a specific parent document with the given
593     * display name and MIME type.
594     *
595     * @param parentDocumentUri document with
596     *            {@link Documents#FLAG_SUPPORTS_CREATE}
597     * @param displayName name for new document
598     * @param mimeType MIME type for new document, which cannot be changed
599     * @return newly created document Uri, or {@code null} if failed
600     */
601    public static Uri createDocument(
602            ContentResolver resolver, Uri parentDocumentUri, String displayName, String mimeType) {
603        final ContentValues values = new ContentValues();
604        values.put(DocumentColumns.MIME_TYPE, mimeType);
605        values.put(DocumentColumns.DISPLAY_NAME, displayName);
606        return resolver.insert(parentDocumentUri, values);
607    }
608
609    /**
610     * Rename the document at the given URI. Given document must have
611     * {@link Documents#FLAG_SUPPORTS_RENAME} set.
612     *
613     * @return if rename was successful.
614     */
615    public static boolean renameDocument(
616            ContentResolver resolver, Uri documentUri, String displayName) {
617        final ContentValues values = new ContentValues();
618        values.put(DocumentColumns.DISPLAY_NAME, displayName);
619        return (resolver.update(documentUri, values, null, null) == 1);
620    }
621
622    /**
623     * Notify the system that roots have changed for the given storage provider.
624     * This signal is used to invalidate internal caches.
625     */
626    public static void notifyRootsChanged(Context context, String authority) {
627        final Intent intent = new Intent(ACTION_DOCUMENT_CHANGED);
628        intent.setData(buildRootsUri(authority));
629        context.sendBroadcast(intent);
630    }
631}
632