DocumentsContract.java revision 5b83f854d9cbd6dc9e5a31892dbe8515b4c29683
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 android.content.ContentProvider;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ProviderInfo;
26import android.content.res.AssetFileDescriptor;
27import android.database.Cursor;
28import android.graphics.Bitmap;
29import android.graphics.BitmapFactory;
30import android.graphics.Point;
31import android.net.Uri;
32import android.os.Bundle;
33import android.util.Log;
34
35import com.google.android.collect.Lists;
36
37import libcore.io.IoUtils;
38
39import java.io.IOException;
40import java.io.InputStream;
41import java.util.List;
42
43/**
44 * The contract between a storage backend and the platform. Contains definitions
45 * for the supported URIs and columns.
46 */
47public final class DocumentsContract {
48    private static final String TAG = "Documents";
49
50    // content://com.example/roots/
51    // content://com.example/roots/sdcard/
52    // content://com.example/roots/sdcard/docs/0/
53    // content://com.example/roots/sdcard/docs/0/contents/
54    // content://com.example/roots/sdcard/docs/0/search/?query=pony
55
56    /**
57     * MIME type of a document which is a directory that may contain additional
58     * documents.
59     *
60     * @see #buildContentsUri(String, String, String)
61     */
62    public static final String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";
63
64    /** {@hide} */
65    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
66
67    /** {@hide} */
68    public static final String ACTION_ROOTS_CHANGED = "android.provider.action.ROOTS_CHANGED";
69
70    /**
71     * {@link DocumentColumns#DOC_ID} value representing the root directory of a
72     * storage root.
73     */
74    public static final String ROOT_DOC_ID = "0";
75
76    /**
77     * Flag indicating that a document is a directory that supports creation of
78     * new files within it.
79     *
80     * @see DocumentColumns#FLAGS
81     * @see #createDocument(ContentResolver, Uri, String, String)
82     */
83    public static final int FLAG_SUPPORTS_CREATE = 1;
84
85    /**
86     * Flag indicating that a document is renamable.
87     *
88     * @see DocumentColumns#FLAGS
89     * @see #renameDocument(ContentResolver, Uri, String)
90     */
91    public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
92
93    /**
94     * Flag indicating that a document is deletable.
95     *
96     * @see DocumentColumns#FLAGS
97     */
98    public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
99
100    /**
101     * Flag indicating that a document can be represented as a thumbnail.
102     *
103     * @see DocumentColumns#FLAGS
104     * @see #getThumbnail(ContentResolver, Uri, Point)
105     */
106    public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
107
108    /**
109     * Flag indicating that a document is a directory that supports search.
110     *
111     * @see DocumentColumns#FLAGS
112     */
113    public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
114
115    /**
116     * Flag indicating that a document is writable.
117     *
118     * @see DocumentColumns#FLAGS
119     */
120    public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
121
122    /**
123     * Flag indicating that a document is a directory that prefers its contents
124     * be shown in a larger format grid. Usually suitable when a directory
125     * contains mostly pictures.
126     *
127     * @see DocumentColumns#FLAGS
128     */
129    public static final int FLAG_PREFERS_GRID = 1 << 6;
130
131    /**
132     * Optimal dimensions for a document thumbnail request, stored as a
133     * {@link Point} object. This is only a hint, and the returned thumbnail may
134     * have different dimensions.
135     *
136     * @see ContentProvider#openTypedAssetFile(Uri, String, Bundle)
137     */
138    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
139
140    /**
141     * Extra boolean flag included in a directory {@link Cursor#getExtras()}
142     * indicating that the backend can provide additional data if requested,
143     * such as additional search results.
144     */
145    public static final String EXTRA_HAS_MORE = "has_more";
146
147    /**
148     * Extra boolean flag included in a {@link Cursor#respond(Bundle)} call to a
149     * directory to request that additional data should be fetched. When
150     * requested data is ready, the provider should send a change notification
151     * to cause a requery.
152     *
153     * @see Cursor#respond(Bundle)
154     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
155     *      boolean)
156     */
157    public static final String EXTRA_REQUEST_MORE = "request_more";
158
159    private static final String PATH_ROOTS = "roots";
160    private static final String PATH_DOCS = "docs";
161    private static final String PATH_CONTENTS = "contents";
162    private static final String PATH_SEARCH = "search";
163
164    private static final String PARAM_QUERY = "query";
165    private static final String PARAM_LOCAL_ONLY = "localOnly";
166
167    /**
168     * Build URI representing the roots in a storage backend.
169     */
170    public static Uri buildRootsUri(String authority) {
171        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
172                .authority(authority).appendPath(PATH_ROOTS).build();
173    }
174
175    public static Uri buildRootUri(String authority, String rootId) {
176        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
177                .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build();
178    }
179
180    /**
181     * Build URI representing the given {@link DocumentColumns#DOC_ID} in a
182     * storage root.
183     */
184    public static Uri buildDocumentUri(String authority, String rootId, String docId) {
185        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
186                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
187                .build();
188    }
189
190    /**
191     * Build URI representing the contents of the given directory in a storage
192     * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}.
193     */
194    public static Uri buildContentsUri(String authority, String rootId, String docId) {
195        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
196                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
197                .appendPath(PATH_CONTENTS).build();
198    }
199
200    /**
201     * Build URI representing a search for matching documents under a directory
202     * in a storage backend.
203     */
204    public static Uri buildSearchUri(String authority, String rootId, String docId, String query) {
205        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
206                .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
207                .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
208    }
209
210    public static Uri buildDocumentUri(Uri relatedUri, String docId) {
211        return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId);
212    }
213
214    public static Uri buildContentsUri(Uri relatedUri) {
215        return buildContentsUri(
216                relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri));
217    }
218
219    public static Uri buildSearchUri(Uri relatedUri, String query) {
220        return buildSearchUri(
221                relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query);
222    }
223
224    public static String getRootId(Uri documentUri) {
225        final List<String> paths = documentUri.getPathSegments();
226        if (paths.size() < 2) {
227            throw new IllegalArgumentException("Not a root: " + documentUri);
228        }
229        if (!PATH_ROOTS.equals(paths.get(0))) {
230            throw new IllegalArgumentException("Not a root: " + documentUri);
231        }
232        return paths.get(1);
233    }
234
235    public static String getDocId(Uri documentUri) {
236        final List<String> paths = documentUri.getPathSegments();
237        if (paths.size() < 4) {
238            throw new IllegalArgumentException("Not a document: " + documentUri);
239        }
240        if (!PATH_ROOTS.equals(paths.get(0))) {
241            throw new IllegalArgumentException("Not a document: " + documentUri);
242        }
243        if (!PATH_DOCS.equals(paths.get(2))) {
244            throw new IllegalArgumentException("Not a document: " + documentUri);
245        }
246        return paths.get(3);
247    }
248
249    /**
250     * Return requested search query from the given Uri.
251     */
252    public static String getSearchQuery(Uri documentUri) {
253        return documentUri.getQueryParameter(PARAM_QUERY);
254    }
255
256    /**
257     * Mark the given Uri to indicate that only locally-available contents
258     * should be returned.
259     */
260    public static Uri setLocalOnly(Uri documentUri) {
261        return documentUri.buildUpon()
262                .appendQueryParameter(PARAM_LOCAL_ONLY, String.valueOf(true)).build();
263    }
264
265    /**
266     * Return if the given Uri is requesting that only locally-available content
267     * be returned. That is, no network connections should be initiated to
268     * provide the metadata or content.
269     */
270    public static boolean isLocalOnly(Uri documentUri) {
271        return documentUri.getBooleanQueryParameter(PARAM_LOCAL_ONLY, false);
272    }
273
274    /**
275     * These are standard columns for document URIs. Storage backend providers
276     * <em>must</em> support at least these columns when queried.
277     *
278     * @see Intent#ACTION_OPEN_DOCUMENT
279     * @see Intent#ACTION_CREATE_DOCUMENT
280     */
281    public interface DocumentColumns extends OpenableColumns {
282        /**
283         * The ID for a document under a storage backend root. Values
284         * <em>must</em> never change once returned. This field is read-only to
285         * document clients.
286         * <p>
287         * Type: STRING
288         */
289        public static final String DOC_ID = "doc_id";
290
291        /**
292         * MIME type of a document, matching the value returned by
293         * {@link ContentResolver#getType(android.net.Uri)}. This field must be
294         * provided when a new document is created, but after that the field is
295         * read-only.
296         * <p>
297         * Type: STRING
298         *
299         * @see DocumentsContract#MIME_TYPE_DIRECTORY
300         */
301        public static final String MIME_TYPE = "mime_type";
302
303        /**
304         * Timestamp when a document was last modified, in milliseconds since
305         * January 1, 1970 00:00:00.0 UTC. This field is read-only to document
306         * clients.
307         * <p>
308         * Type: INTEGER (long)
309         *
310         * @see System#currentTimeMillis()
311         */
312        public static final String LAST_MODIFIED = "last_modified";
313
314        /**
315         * Flags that apply to a specific document. This field is read-only to
316         * document clients.
317         * <p>
318         * Type: INTEGER (int)
319         */
320        public static final String FLAGS = "flags";
321
322        /**
323         * Summary for this document, or {@code null} to omit.
324         * <p>
325         * Type: STRING
326         */
327        public static final String SUMMARY = "summary";
328    }
329
330    /**
331     * Root that represents a cloud-based storage service.
332     *
333     * @see RootColumns#ROOT_TYPE
334     */
335    public static final int ROOT_TYPE_SERVICE = 1;
336
337    /**
338     * Root that represents a shortcut to content that may be available
339     * elsewhere through another storage root.
340     *
341     * @see RootColumns#ROOT_TYPE
342     */
343    public static final int ROOT_TYPE_SHORTCUT = 2;
344
345    /**
346     * Root that represents a physical storage device.
347     *
348     * @see RootColumns#ROOT_TYPE
349     */
350    public static final int ROOT_TYPE_DEVICE = 3;
351
352    /**
353     * Root that represents a physical storage device that should only be
354     * displayed to advanced users.
355     *
356     * @see RootColumns#ROOT_TYPE
357     */
358    public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
359
360    /**
361     * These are standard columns for the roots URI.
362     *
363     * @see DocumentsContract#buildRootsUri(String)
364     */
365    public interface RootColumns {
366        public static final String ROOT_ID = "root_id";
367
368        /**
369         * Storage root type, use for clustering.
370         * <p>
371         * Type: INTEGER (int)
372         *
373         * @see DocumentsContract#ROOT_TYPE_SERVICE
374         * @see DocumentsContract#ROOT_TYPE_DEVICE
375         */
376        public static final String ROOT_TYPE = "root_type";
377
378        /**
379         * Icon resource ID for this storage root, or {@code 0} to use the
380         * default {@link ProviderInfo#icon}.
381         * <p>
382         * Type: INTEGER (int)
383         */
384        public static final String ICON = "icon";
385
386        /**
387         * Title for this storage root, or {@code null} to use the default
388         * {@link ProviderInfo#labelRes}.
389         * <p>
390         * Type: STRING
391         */
392        public static final String TITLE = "title";
393
394        /**
395         * Summary for this storage root, or {@code null} to omit.
396         * <p>
397         * Type: STRING
398         */
399        public static final String SUMMARY = "summary";
400
401        /**
402         * Number of free bytes of available in this storage root, or -1 if
403         * unknown or unbounded.
404         * <p>
405         * Type: INTEGER (long)
406         */
407        public static final String AVAILABLE_BYTES = "available_bytes";
408    }
409
410    /**
411     * Return list of all documents that the calling package has "open." These
412     * are Uris matching {@link DocumentsContract} to which persistent
413     * read/write access has been granted, usually through
414     * {@link Intent#ACTION_OPEN_DOCUMENT} or
415     * {@link Intent#ACTION_CREATE_DOCUMENT}.
416     *
417     * @see Context#grantUriPermission(String, Uri, int)
418     * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
419     */
420    public static Uri[] getOpenDocuments(Context context) {
421        final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
422                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION;
423        final Uri[] uris = context.getContentResolver()
424                .getIncomingUriPermissionGrants(openedFlags, openedFlags);
425
426        // Filter to only include document providers
427        final PackageManager pm = context.getPackageManager();
428        final List<Uri> result = Lists.newArrayList();
429        for (Uri uri : uris) {
430            final ProviderInfo info = pm.resolveContentProvider(
431                    uri.getAuthority(), PackageManager.GET_META_DATA);
432            if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) {
433                result.add(uri);
434            }
435        }
436
437        return result.toArray(new Uri[result.size()]);
438    }
439
440    /**
441     * Return thumbnail representing the document at the given URI. Callers are
442     * responsible for their own caching. Given document must have
443     * {@link #FLAG_SUPPORTS_THUMBNAIL} set.
444     *
445     * @return decoded thumbnail, or {@code null} if problem was encountered.
446     */
447    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
448        final Bundle opts = new Bundle();
449        opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size);
450
451        InputStream is = null;
452        try {
453            is = new AssetFileDescriptor.AutoCloseInputStream(
454                    resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts));
455            return BitmapFactory.decodeStream(is);
456        } catch (IOException e) {
457            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
458            return null;
459        } finally {
460            IoUtils.closeQuietly(is);
461        }
462    }
463
464    /**
465     * Create a new document under a specific parent document with the given
466     * display name and MIME type.
467     *
468     * @param parentDocumentUri document with {@link #FLAG_SUPPORTS_CREATE}
469     * @param displayName name for new document
470     * @param mimeType MIME type for new document, which cannot be changed
471     * @return newly created document Uri, or {@code null} if failed
472     */
473    public static Uri createDocument(
474            ContentResolver resolver, Uri parentDocumentUri, String displayName, String mimeType) {
475        final ContentValues values = new ContentValues();
476        values.put(DocumentColumns.MIME_TYPE, mimeType);
477        values.put(DocumentColumns.DISPLAY_NAME, displayName);
478        return resolver.insert(parentDocumentUri, values);
479    }
480
481    /**
482     * Rename the document at the given URI. Given document must have
483     * {@link #FLAG_SUPPORTS_RENAME} set.
484     *
485     * @return if rename was successful.
486     */
487    public static boolean renameDocument(
488            ContentResolver resolver, Uri documentUri, String displayName) {
489        final ContentValues values = new ContentValues();
490        values.put(DocumentColumns.DISPLAY_NAME, displayName);
491        return (resolver.update(documentUri, values, null, null) == 1);
492    }
493
494    /**
495     * Notify the system that roots have changed for the given storage provider.
496     * This signal is used to invalidate internal caches.
497     */
498    public static void notifyRootsChanged(Context context, String authority) {
499        final Intent intent = new Intent(ACTION_ROOTS_CHANGED);
500        intent.setData(buildRootsUri(authority));
501        context.sendBroadcast(intent);
502    }
503}
504