blob: ad4ec7248a81612e49865f3a975b308ffc9d5b55 [file] [log] [blame]
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001/*
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
Jeff Sharkey63983432013-08-21 11:33:50 -070019import static android.net.TrafficStats.KB_IN_BYTES;
Elliott Hughes34385d32014-04-28 11:11:32 -070020import static android.system.OsConstants.SEEK_SET;
Julian Mancinib6505152017-06-27 13:29:09 -070021
Garfield Tan06940e12016-10-07 16:03:17 -070022import static com.android.internal.util.Preconditions.checkArgument;
23import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
24import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
25
Steve McKay323ee3e2015-09-25 16:02:56 -070026import android.annotation.Nullable;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070027import android.content.ContentProviderClient;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070028import android.content.ContentResolver;
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -070029import android.content.Context;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070030import android.content.Intent;
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +090031import android.content.IntentSender;
Jeff Sharkeyd2e1e812013-10-09 13:31:13 -070032import android.content.pm.ResolveInfo;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070033import android.content.res.AssetFileDescriptor;
Jeff Sharkey20d96d82013-07-30 17:08:39 -070034import android.database.Cursor;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070035import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -070037import android.graphics.Matrix;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070038import android.graphics.Point;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -070039import android.media.ExifInterface;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070040import android.net.Uri;
Ben Lin8ea82002017-03-08 17:30:16 -080041import android.os.Build;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070042import android.os.Bundle;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070043import android.os.CancellationSignal;
Jeff Sharkey33819312013-10-29 11:56:37 -070044import android.os.OperationCanceledException;
Garfield Tanaba97f32016-10-06 17:34:19 +000045import android.os.Parcel;
Jeff Sharkeybd3b9022013-08-20 15:20:04 -070046import android.os.ParcelFileDescriptor;
Jeff Sharkey67f9d502017-08-05 13:49:13 -060047import android.os.ParcelFileDescriptor.OnCloseListener;
Garfield Tanaba97f32016-10-06 17:34:19 +000048import android.os.Parcelable;
Ben Lin8ea82002017-03-08 17:30:16 -080049import android.os.ParcelableException;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070050import android.os.RemoteException;
Felipe Leme04a5d402016-02-08 16:44:06 -080051import android.os.storage.StorageVolume;
Elliott Hughes34385d32014-04-28 11:11:32 -070052import android.system.ErrnoException;
53import android.system.Os;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070054import android.util.Log;
55
56import libcore.io.IoUtils;
57
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070058import java.io.BufferedInputStream;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -070059import java.io.File;
Jeff Sharkey9d0843d2013-05-07 12:41:33 -070060import java.io.FileDescriptor;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070061import java.io.FileInputStream;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -070062import java.io.FileNotFoundException;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070063import java.io.IOException;
Jeff Sharkeydc2963a2013-08-02 15:55:26 -070064import java.util.List;
Garfield Tan06940e12016-10-07 16:03:17 -070065import java.util.Objects;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070066
67/**
Jeff Sharkeybd3b9022013-08-20 15:20:04 -070068 * Defines the contract between a documents provider and the platform.
69 * <p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070070 * To create a document provider, extend {@link DocumentsProvider}, which
71 * provides a foundational implementation of this contract.
Jeff Sharkey21de56a2014-04-05 19:05:24 -070072 * <p>
73 * All client apps must hold a valid URI permission grant to access documents,
74 * typically issued when a user makes a selection through
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070075 * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
Felipe Leme04a5d402016-02-08 16:44:06 -080076 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
77 * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
Jeff Sharkeybd3b9022013-08-20 15:20:04 -070078 *
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070079 * @see DocumentsProvider
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070080 */
81public final class DocumentsContract {
Steve McKayd3afdee2015-11-19 17:27:12 -080082 private static final String TAG = "DocumentsContract";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070083
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070084 // content://com.example/root/
85 // content://com.example/root/sdcard/
86 // content://com.example/root/sdcard/recent/
Jeff Sharkey3e1189b2013-09-12 21:59:06 -070087 // content://com.example/root/sdcard/search/?query=pony
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070088 // content://com.example/document/12/
89 // content://com.example/document/12/children/
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070090 // content://com.example/tree/12/document/24/
91 // content://com.example/tree/12/document/24/children/
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070092
93 private DocumentsContract() {
94 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070095
Jeff Sharkey85f5f812013-10-07 10:16:12 -070096 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070097 * Intent action used to identify {@link DocumentsProvider} instances. This
98 * is used in the {@code <intent-filter>} of a {@code <provider>}.
Jeff Sharkey85f5f812013-10-07 10:16:12 -070099 */
100 public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
101
Jeff Sharkey5b83f852013-08-14 18:29:19 -0700102 /** {@hide} */
Jeff Sharkey15be8362013-10-09 13:52:17 -0700103 public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
104
Jeff Sharkey96c62052013-10-25 16:30:54 -0700105 /** {@hide} */
Aga Wronska1719b352016-03-21 11:28:03 -0700106 public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
107
108 /** {@hide} */
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700109 public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
110
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700111 /**
Garfield Tanb44ae612016-11-07 16:46:37 -0800112 * Sets the desired initial location visible to user when file chooser is shown.
113 *
114 * <p>Applicable to {@link Intent} with actions:
115 * <ul>
116 * <li>{@link Intent#ACTION_OPEN_DOCUMENT}</li>
117 * <li>{@link Intent#ACTION_CREATE_DOCUMENT}</li>
118 * <li>{@link Intent#ACTION_OPEN_DOCUMENT_TREE}</li>
119 * </ul>
120 *
121 * <p>Location should specify a document URI or a tree URI with document ID. If
122 * this URI identifies a non-directory, document navigator will attempt to use the parent
123 * of the document as the initial location.
Garfield Tan40d7b352017-03-02 15:30:30 -0800124 *
125 * <p>The initial location is system specific if this extra is missing or document navigator
126 * failed to locate the desired initial location.
Garfield Tanb44ae612016-11-07 16:46:37 -0800127 */
128 public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
129
130 /**
Ben Kwa77797402015-05-29 15:40:31 -0700131 * Set this in a DocumentsUI intent to cause a package's own roots to be
132 * excluded from the roots list.
133 */
134 public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
135
136 /**
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700137 * Included in {@link AssetFileDescriptor#getExtras()} when returned
138 * thumbnail should be rotated.
139 *
140 * @see MediaStore.Images.ImageColumns#ORIENTATION
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700141 */
Tomasz Mikolajewski5f53f652016-03-31 09:34:51 +0900142 public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700143
Tomasz Mikolajewski0e591f92015-06-12 16:22:17 -0700144 /**
145 * Overrides the default prompt text in DocumentsUI when set in an intent.
146 */
147 public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
148
Ben Linbd036d82016-12-15 15:41:08 -0800149 /**
150 * Action of intent issued by DocumentsUI when user wishes to open/configure/manage a particular
151 * document in the provider application.
Ben Lin8ea82002017-03-08 17:30:16 -0800152 *
Ben Linbd036d82016-12-15 15:41:08 -0800153 * <p>When issued, the intent will include the URI of the document as the intent data.
Ben Lin8ea82002017-03-08 17:30:16 -0800154 *
Ben Linbd036d82016-12-15 15:41:08 -0800155 * <p>A provider wishing to provide support for this action should do two things.
156 * <li>Add an {@code <intent-filter>} matching this action.
157 * <li>When supplying information in {@link DocumentsProvider#queryChildDocuments}, include
158 * {@link Document#FLAG_SUPPORTS_SETTINGS} in the flags for each document that supports
159 * settings.
160 *
161 * @see DocumentsContact#Document#FLAG_SUPPORTS_SETTINGS
162 */
163 public static final String
164 ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS";
165
Jeff Sharkey15be8362013-10-09 13:52:17 -0700166 /** {@hide} */
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700167 public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700168
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700169 /** {@hide} */
Jeff Sharkey1407d4c2015-04-12 21:52:24 -0700170 public static final String
171 ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700172
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700173 /**
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700174 * Buffer is large enough to rewind past any EXIF headers.
175 */
176 private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
177
Jeff Sharkeycc2ae6b42015-09-29 13:04:46 -0700178 /** {@hide} */
Garfield Tan92b96ba2016-11-01 14:33:48 -0700179 public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
180 "com.android.externalstorage.documents";
181
182 /** {@hide} */
Jeff Sharkeycc2ae6b42015-09-29 13:04:46 -0700183 public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
184
Julian Mancinib6505152017-06-27 13:29:09 -0700185 /** {@hide} */
186 public static final String METADATA_TYPES = "android:documentMetadataType";
187
188 /** {@hide} */
189 public static final String METADATA_EXIF = "android:documentExif";
190
191 /** {@hide} */
192 public static final String EXTRA_METADATA_TAGS = "android:documentMetadataTags";
193
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700194 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700195 * Constants related to a document, including {@link Cursor} column names
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700196 * and flags.
197 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700198 * A document can be either an openable stream (with a specific MIME type),
199 * or a directory containing additional documents (with the
200 * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
201 * subtree containing zero or more documents, which can recursively contain
202 * even more documents and directories.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700203 * <p>
204 * All columns are <em>read-only</em> to client applications.
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700205 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700206 public final static class Document {
207 private Document() {
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700208 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700209
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700210 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700211 * Unique ID of a document. This ID is both provided by and interpreted
212 * by a {@link DocumentsProvider}, and should be treated as an opaque
Jeff Sharkey6efba222013-09-27 16:44:11 -0700213 * value by client applications. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700214 * <p>
215 * Each document must have a unique ID within a provider, but that
216 * single document may be included as a child of multiple directories.
217 * <p>
218 * A provider must always return durable IDs, since they will be used to
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700219 * issue long-term URI permission grants when an application interacts
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700220 * with {@link Intent#ACTION_OPEN_DOCUMENT} and
221 * {@link Intent#ACTION_CREATE_DOCUMENT}.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700222 * <p>
223 * Type: STRING
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700224 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700225 public static final String COLUMN_DOCUMENT_ID = "document_id";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700226
227 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700228 * Concrete MIME type of a document. For example, "image/png" or
229 * "application/pdf" for openable files. A document can also be a
230 * directory containing additional documents, which is represented with
Jeff Sharkey6efba222013-09-27 16:44:11 -0700231 * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700232 * <p>
233 * Type: STRING
234 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700235 * @see #MIME_TYPE_DIR
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700236 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700237 public static final String COLUMN_MIME_TYPE = "mime_type";
238
239 /**
240 * Display name of a document, used as the primary title displayed to a
Jeff Sharkey6efba222013-09-27 16:44:11 -0700241 * user. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700242 * <p>
243 * Type: STRING
244 */
245 public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
246
247 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700248 * Summary of a document, which may be shown to a user. This column is
249 * optional, and may be {@code null}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700250 * <p>
251 * Type: STRING
252 */
253 public static final String COLUMN_SUMMARY = "summary";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700254
255 /**
256 * Timestamp when a document was last modified, in milliseconds since
Jeff Sharkey6efba222013-09-27 16:44:11 -0700257 * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
258 * {@code null} if unknown. A {@link DocumentsProvider} can update this
259 * field using events from {@link OnCloseListener} or other reliable
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700260 * {@link ParcelFileDescriptor} transports.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700261 * <p>
262 * Type: INTEGER (long)
263 *
264 * @see System#currentTimeMillis()
265 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700266 public static final String COLUMN_LAST_MODIFIED = "last_modified";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700267
268 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700269 * Specific icon resource ID for a document. This column is optional,
270 * and may be {@code null} to use a platform-provided default icon based
271 * on {@link #COLUMN_MIME_TYPE}.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700272 * <p>
273 * Type: INTEGER (int)
274 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700275 public static final String COLUMN_ICON = "icon";
Jeff Sharkey66516692013-08-06 11:26:10 -0700276
277 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700278 * Flags that apply to a document. This column is required.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700279 * <p>
280 * Type: INTEGER (int)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700281 *
282 * @see #FLAG_SUPPORTS_WRITE
283 * @see #FLAG_SUPPORTS_DELETE
284 * @see #FLAG_SUPPORTS_THUMBNAIL
285 * @see #FLAG_DIR_PREFERS_GRID
Jeff Sharkey6efba222013-09-27 16:44:11 -0700286 * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900287 * @see #FLAG_VIRTUAL_DOCUMENT
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900288 * @see #FLAG_SUPPORTS_COPY
289 * @see #FLAG_SUPPORTS_MOVE
290 * @see #FLAG_SUPPORTS_REMOVE
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700291 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700292 public static final String COLUMN_FLAGS = "flags";
293
294 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700295 * Size of a document, in bytes, or {@code null} if unknown. This column
296 * is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700297 * <p>
298 * Type: INTEGER (long)
299 */
300 public static final String COLUMN_SIZE = OpenableColumns.SIZE;
301
302 /**
303 * MIME type of a document which is a directory that may contain
304 * additional documents.
305 *
306 * @see #COLUMN_MIME_TYPE
307 */
308 public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
309
310 /**
311 * Flag indicating that a document can be represented as a thumbnail.
312 *
313 * @see #COLUMN_FLAGS
314 * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
315 * Point, CancellationSignal)
316 * @see DocumentsProvider#openDocumentThumbnail(String, Point,
317 * android.os.CancellationSignal)
318 */
319 public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
320
321 /**
322 * Flag indicating that a document supports writing.
323 * <p>
324 * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
325 * the calling application is granted both
326 * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
327 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
328 * writability of a document may change over time, for example due to
329 * remote access changes. This flag indicates that a document client can
330 * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
Steve McKay83df8c02015-09-16 15:07:31 -0700331 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700332 * @see #COLUMN_FLAGS
333 */
334 public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
335
336 /**
337 * Flag indicating that a document is deletable.
338 *
339 * @see #COLUMN_FLAGS
340 * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
341 * @see DocumentsProvider#deleteDocument(String)
342 */
343 public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
344
345 /**
346 * Flag indicating that a document is a directory that supports creation
347 * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
348 * {@link #MIME_TYPE_DIR}.
349 *
350 * @see #COLUMN_FLAGS
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700351 * @see DocumentsProvider#createDocument(String, String, String)
352 */
353 public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
354
355 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700356 * Flag indicating that a directory prefers its contents be shown in a
357 * larger format grid. Usually suitable when a directory contains mostly
358 * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
359 * {@link #MIME_TYPE_DIR}.
360 *
361 * @see #COLUMN_FLAGS
362 */
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700363 public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700364
365 /**
366 * Flag indicating that a directory prefers its contents be sorted by
367 * {@link #COLUMN_LAST_MODIFIED}. Only valid when
368 * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
369 *
370 * @see #COLUMN_FLAGS
371 */
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700372 public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
Jeff Sharkeyf6db1542013-09-13 13:42:19 -0700373
374 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700375 * Flag indicating that a document can be renamed.
376 *
377 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900378 * @see DocumentsContract#renameDocument(ContentResolver, Uri,
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700379 * String)
380 * @see DocumentsProvider#renameDocument(String, String)
381 */
382 public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
383
384 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700385 * Flag indicating that a document can be copied to another location
386 * within the same document provider.
387 *
388 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900389 * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900390 * @see DocumentsProvider#copyDocument(String, String)
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700391 */
392 public static final int FLAG_SUPPORTS_COPY = 1 << 7;
393
394 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900395 * Flag indicating that a document can be moved to another location
396 * within the same document provider.
397 *
398 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900399 * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900400 * @see DocumentsProvider#moveDocument(String, String, String)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900401 */
402 public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
403
404 /**
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900405 * Flag indicating that a document is virtual, and doesn't have byte
406 * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
407 *
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +0900408 * <p><em>Virtual documents must have at least one alternative streamable
409 * format via {@link DocumentsProvider#openTypedDocument}</em>
410 *
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900411 * @see #COLUMN_FLAGS
412 * @see #COLUMN_MIME_TYPE
413 * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
414 * android.os.CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +0900415 * @see DocumentsProvider#getDocumentStreamTypes(String, String)
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900416 */
Tomasz Mikolajewski75395652016-01-07 07:19:22 +0000417 public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900418
419 /**
Tomasz Mikolajewski9b055e12016-02-01 13:01:34 +0900420 * Flag indicating that a document can be removed from a parent.
421 *
422 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900423 * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
Tomasz Mikolajewski9b055e12016-02-01 13:01:34 +0900424 * @see DocumentsProvider#removeDocument(String, String)
425 */
426 public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
427
428 /**
Ben Linbd036d82016-12-15 15:41:08 -0800429 * Flag indicating that a document has settings that can be configured by user.
430 *
431 * @see #COLUMN_FLAGS
432 * @see #ACTION_DOCUMENT_SETTINGS
433 */
434 public static final int FLAG_SUPPORTS_SETTINGS = 1 << 11;
435
436 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900437 * Flag indicating that a Web link can be obtained for the document.
438 *
439 * @see #COLUMN_FLAGS
440 * @see DocumentsContract#createWebLinkIntent(PackageManager, Uri, Bundle)
441 */
442 public static final int FLAG_WEB_LINKABLE = 1 << 12;
443
444 /**
Steve McKay168e4642016-03-14 13:02:56 -0700445 * Flag indicating that a document is not complete, likely its
446 * contents are being downloaded. Partial files cannot be opened,
447 * copied, moved in the UI. But they can be deleted and retried
448 * if they represent a failed download.
449 *
450 * @see #COLUMN_FLAGS
451 * @hide
452 */
453 public static final int FLAG_PARTIAL = 1 << 16;
Julian Mancinib6505152017-06-27 13:29:09 -0700454
455 /**
456 * Flag indicating that a document has available metadata that can be read
457 * using DocumentsContract#getDocumentMetadata
458 * @hide
459 */
460 public static final int FLAG_SUPPORTS_METADATA = 1 << 17;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700461 }
462
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700463 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700464 * Constants related to a root of documents, including {@link Cursor} column
465 * names and flags. A root is the start of a tree of documents, such as a
466 * physical storage device, or an account. Each root starts at the directory
467 * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
468 * contain both documents and directories.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700469 * <p>
470 * All columns are <em>read-only</em> to client applications.
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700471 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700472 public final static class Root {
473 private Root() {
474 }
475
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700476 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700477 * Unique ID of a root. This ID is both provided by and interpreted by a
478 * {@link DocumentsProvider}, and should be treated as an opaque value
Jeff Sharkey6efba222013-09-27 16:44:11 -0700479 * by client applications. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700480 * <p>
481 * Type: STRING
482 */
483 public static final String COLUMN_ROOT_ID = "root_id";
484
485 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700486 * Flags that apply to a root. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700487 * <p>
488 * Type: INTEGER (int)
489 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700490 * @see #FLAG_LOCAL_ONLY
491 * @see #FLAG_SUPPORTS_CREATE
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700492 * @see #FLAG_SUPPORTS_RECENTS
493 * @see #FLAG_SUPPORTS_SEARCH
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700494 */
495 public static final String COLUMN_FLAGS = "flags";
496
497 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700498 * Icon resource ID for a root. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700499 * <p>
500 * Type: INTEGER (int)
501 */
502 public static final String COLUMN_ICON = "icon";
503
504 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700505 * Title for a root, which will be shown to a user. This column is
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700506 * required. For a single storage service surfacing multiple accounts as
507 * different roots, this title should be the name of the service.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700508 * <p>
509 * Type: STRING
510 */
511 public static final String COLUMN_TITLE = "title";
512
513 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700514 * Summary for this root, which may be shown to a user. This column is
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700515 * optional, and may be {@code null}. For a single storage service
516 * surfacing multiple accounts as different roots, this summary should
517 * be the name of the account.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700518 * <p>
519 * Type: STRING
520 */
521 public static final String COLUMN_SUMMARY = "summary";
522
523 /**
524 * Document which is a directory that represents the top directory of
Jeff Sharkey6efba222013-09-27 16:44:11 -0700525 * this root. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700526 * <p>
527 * Type: STRING
528 *
529 * @see Document#COLUMN_DOCUMENT_ID
530 */
531 public static final String COLUMN_DOCUMENT_ID = "document_id";
532
533 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700534 * Number of bytes available in this root. This column is optional, and
535 * may be {@code null} if unknown or unbounded.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700536 * <p>
537 * Type: INTEGER (long)
538 */
539 public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
540
541 /**
Tomasz Mikolajewski3f78e172015-06-25 16:17:26 +0900542 * Capacity of a root in bytes. This column is optional, and may be
543 * {@code null} if unknown or unbounded.
Tomasz Mikolajewski3f78e172015-06-25 16:17:26 +0900544 * <p>
545 * Type: INTEGER (long)
546 */
547 public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes";
548
549 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700550 * MIME types supported by this root. This column is optional, and if
551 * {@code null} the root is assumed to support all MIME types. Multiple
552 * MIME types can be separated by a newline. For example, a root
553 * supporting audio might return "audio/*\napplication/x-flac".
Jeff Sharkey923396b2013-09-05 13:55:35 -0700554 * <p>
Jeff Sharkey6efba222013-09-27 16:44:11 -0700555 * Type: STRING
Jeff Sharkey923396b2013-09-05 13:55:35 -0700556 */
557 public static final String COLUMN_MIME_TYPES = "mime_types";
558
Garfield Tana7e852e2017-02-21 12:34:08 -0800559 /**
560 * MIME type for a root.
561 */
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700562 public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
563
Jeff Sharkey923396b2013-09-05 13:55:35 -0700564 /**
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700565 * Flag indicating that at least one directory under this root supports
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700566 * creating content. Roots with this flag will be shown when an
567 * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700568 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700569 * @see #COLUMN_FLAGS
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700570 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700571 public static final int FLAG_SUPPORTS_CREATE = 1;
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700572
573 /**
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700574 * Flag indicating that this root offers content that is strictly local
575 * on the device. That is, no network requests are made for the content.
576 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700577 * @see #COLUMN_FLAGS
578 * @see Intent#EXTRA_LOCAL_ONLY
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700579 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700580 public static final int FLAG_LOCAL_ONLY = 1 << 1;
581
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700582 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700583 * Flag indicating that this root can be queried to provide recently
584 * modified documents.
Jeff Sharkey251097b2013-09-02 15:07:28 -0700585 *
586 * @see #COLUMN_FLAGS
587 * @see DocumentsContract#buildRecentDocumentsUri(String, String)
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700588 * @see DocumentsProvider#queryRecentDocuments(String, String[])
Jeff Sharkey251097b2013-09-02 15:07:28 -0700589 */
Jeff Sharkey6efba222013-09-27 16:44:11 -0700590 public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700591
592 /**
593 * Flag indicating that this root supports search.
594 *
595 * @see #COLUMN_FLAGS
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700596 * @see DocumentsContract#buildSearchDocumentsUri(String, String,
597 * String)
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700598 * @see DocumentsProvider#querySearchDocuments(String, String,
599 * String[])
600 */
Jeff Sharkey6efba222013-09-27 16:44:11 -0700601 public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700602
603 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700604 * Flag indicating that this root supports testing parent child
605 * relationships.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700606 *
607 * @see #COLUMN_FLAGS
608 * @see DocumentsProvider#isChildDocument(String, String)
609 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700610 public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700611
612 /**
Garfield Tan87877032017-03-22 12:01:14 -0700613 * Flag indicating that this root can be ejected.
614 *
615 * @see #COLUMN_FLAGS
616 * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
617 * @see DocumentsProvider#ejectRoot(String)
618 */
619 public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
620
621 /**
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700622 * Flag indicating that this root is currently empty. This may be used
623 * to hide the root when opening documents, but the root will still be
624 * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
Jeff Sharkey6efba222013-09-27 16:44:11 -0700625 * also set. If the value of this flag changes, such as when a root
626 * becomes non-empty, you must send a content changed notification for
627 * {@link DocumentsContract#buildRootsUri(String)}.
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700628 *
629 * @see #COLUMN_FLAGS
Jeff Sharkey6efba222013-09-27 16:44:11 -0700630 * @see ContentResolver#notifyChange(Uri,
631 * android.database.ContentObserver, boolean)
632 * @hide
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700633 */
Jeff Sharkey6efba222013-09-27 16:44:11 -0700634 public static final int FLAG_EMPTY = 1 << 16;
635
636 /**
Aga Wronska1719b352016-03-21 11:28:03 -0700637 * Flag indicating that this root should only be visible to advanced
638 * users.
639 *
640 * @see #COLUMN_FLAGS
641 * @hide
642 */
643 public static final int FLAG_ADVANCED = 1 << 17;
644
645 /**
Jeff Sharkey1407d4c2015-04-12 21:52:24 -0700646 * Flag indicating that this root has settings.
647 *
648 * @see #COLUMN_FLAGS
649 * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
650 * @hide
651 */
Aga Wronska1719b352016-03-21 11:28:03 -0700652 public static final int FLAG_HAS_SETTINGS = 1 << 18;
Steve McKayba23e542016-03-02 15:15:00 -0800653
654 /**
655 * Flag indicating that this root is on removable SD card storage.
656 *
657 * @see #COLUMN_FLAGS
658 * @hide
659 */
Aga Wronska1719b352016-03-21 11:28:03 -0700660 public static final int FLAG_REMOVABLE_SD = 1 << 19;
Steve McKayba23e542016-03-02 15:15:00 -0800661
662 /**
663 * Flag indicating that this root is on removable USB storage.
664 *
665 * @see #COLUMN_FLAGS
666 * @hide
667 */
Aga Wronska1719b352016-03-21 11:28:03 -0700668 public static final int FLAG_REMOVABLE_USB = 1 << 20;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700669 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700670
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700671 /**
672 * Optional boolean flag included in a directory {@link Cursor#getExtras()}
673 * indicating that a document provider is still loading data. For example, a
674 * provider has returned some results, but is still waiting on an
675 * outstanding network request. The provider must send a content changed
676 * notification when loading is finished.
677 *
678 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
679 * boolean)
680 */
681 public static final String EXTRA_LOADING = "loading";
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700682
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700683 /**
684 * Optional string included in a directory {@link Cursor#getExtras()}
685 * providing an informational message that should be shown to a user. For
686 * example, a provider may wish to indicate that not all documents are
687 * available.
688 */
689 public static final String EXTRA_INFO = "info";
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700690
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700691 /**
692 * Optional string included in a directory {@link Cursor#getExtras()}
693 * providing an error message that should be shown to a user. For example, a
694 * provider may wish to indicate that a network error occurred. The user may
695 * choose to retry, resulting in a new query.
696 */
697 public static final String EXTRA_ERROR = "error";
698
Steve McKayd3afdee2015-11-19 17:27:12 -0800699 /**
700 * Optional result (I'm thinking boolean) answer to a question.
701 * {@hide}
702 */
703 public static final String EXTRA_RESULT = "result";
704
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700705 /** {@hide} */
706 public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
707 /** {@hide} */
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700708 public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
709 /** {@hide} */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700710 public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700711 /** {@hide} */
712 public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900713 /** {@hide} */
714 public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
Steve McKayd3afdee2015-11-19 17:27:12 -0800715 /** {@hide} */
716 public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900717 /** {@hide} */
718 public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
Ben Line7822fb2016-06-24 15:21:08 -0700719 /** {@hide} */
720 public static final String METHOD_EJECT_ROOT = "android:ejectRoot";
Garfield Tanaba97f32016-10-06 17:34:19 +0000721 /** {@hide} */
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700722 public static final String METHOD_FIND_DOCUMENT_PATH = "android:findDocumentPath";
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900723 /** {@hide} */
724 public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent";
Julian Mancinib6505152017-06-27 13:29:09 -0700725 /** {@hide} */
726 public static final String METHOD_GET_DOCUMENT_METADATA = "android:getDocumentMetadata";
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700727
728 /** {@hide} */
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900729 public static final String EXTRA_PARENT_URI = "parentUri";
730 /** {@hide} */
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700731 public static final String EXTRA_URI = "uri";
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700732
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900733 /**
734 * @see #createWebLinkIntent(ContentResolver, Uri, Bundle)
735 * {@hide}
736 */
737 public static final String EXTRA_OPTIONS = "options";
738
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700739 private static final String PATH_ROOT = "root";
740 private static final String PATH_RECENT = "recent";
741 private static final String PATH_DOCUMENT = "document";
742 private static final String PATH_CHILDREN = "children";
743 private static final String PATH_SEARCH = "search";
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700744 private static final String PATH_TREE = "tree";
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700745
746 private static final String PARAM_QUERY = "query";
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700747 private static final String PARAM_MANAGE = "manage";
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700748
749 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700750 * Build URI representing the roots of a document provider. When queried, a
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700751 * provider will return one or more rows with columns defined by
752 * {@link Root}.
753 *
754 * @see DocumentsProvider#queryRoots(String[])
755 */
756 public static Uri buildRootsUri(String authority) {
757 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
758 .authority(authority).appendPath(PATH_ROOT).build();
759 }
760
761 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700762 * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700763 * document provider.
764 *
765 * @see #getRootId(Uri)
766 */
767 public static Uri buildRootUri(String authority, String rootId) {
768 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
769 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
770 }
771
772 /**
Steve McKayb67bfbf2015-12-08 17:02:03 -0800773 * Builds URI for user home directory on external (local) storage.
774 * {@hide}
775 */
776 public static Uri buildHomeUri() {
777 // TODO: Avoid this type of interpackage copying. Added here to avoid
778 // direct coupling, but not ideal.
Garfield Tan92b96ba2016-11-01 14:33:48 -0700779 return DocumentsContract.buildRootUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "home");
Steve McKayb67bfbf2015-12-08 17:02:03 -0800780 }
781
782 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700783 * Build URI representing the recently modified documents of a specific root
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700784 * in a document provider. When queried, a provider will return zero or more
785 * rows with columns defined by {@link Document}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700786 *
787 * @see DocumentsProvider#queryRecentDocuments(String, String[])
788 * @see #getRootId(Uri)
789 */
790 public static Uri buildRecentDocumentsUri(String authority, String rootId) {
791 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
792 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
793 .appendPath(PATH_RECENT).build();
794 }
795
796 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700797 * Build URI representing access to descendant documents of the given
798 * {@link Document#COLUMN_DOCUMENT_ID}.
799 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700800 * @see #getTreeDocumentId(Uri)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700801 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700802 public static Uri buildTreeDocumentUri(String authority, String documentId) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700803 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700804 .appendPath(PATH_TREE).appendPath(documentId).build();
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700805 }
806
807 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700808 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
809 * a document provider. When queried, a provider will return a single row
810 * with columns defined by {@link Document}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700811 *
812 * @see DocumentsProvider#queryDocument(String, String[])
813 * @see #getDocumentId(Uri)
814 */
815 public static Uri buildDocumentUri(String authority, String documentId) {
816 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
817 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
818 }
819
820 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700821 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
822 * a document provider. When queried, a provider will return a single row
823 * with columns defined by {@link Document}.
824 * <p>
825 * However, instead of directly accessing the target document, the returned
826 * URI will leverage access granted through a subtree URI, typically
827 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
828 * must be a descendant (child, grandchild, etc) of the subtree.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700829 * <p>
830 * This is typically used to access documents under a user-selected
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700831 * directory tree, since it doesn't require the user to separately confirm
832 * each new document access.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700833 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700834 * @param treeUri the subtree to leverage to gain access to the target
835 * document. The target directory must be a descendant of this
836 * subtree.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700837 * @param documentId the target document, which the caller may not have
838 * direct access to.
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700839 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700840 * @see DocumentsProvider#isChildDocument(String, String)
841 * @see #buildDocumentUri(String, String)
842 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700843 public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700844 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700845 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
846 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700847 .appendPath(documentId).build();
848 }
849
850 /** {@hide} */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700851 public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
852 if (isTreeUri(baseUri)) {
853 return buildDocumentUriUsingTree(baseUri, documentId);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700854 } else {
855 return buildDocumentUri(baseUri.getAuthority(), documentId);
856 }
857 }
858
859 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700860 * Build URI representing the children of the target directory in a document
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700861 * provider. When queried, a provider will return zero or more rows with
862 * columns defined by {@link Document}.
863 *
864 * @param parentDocumentId the document to return children for, which must
865 * be a directory with MIME type of
866 * {@link Document#MIME_TYPE_DIR}.
867 * @see DocumentsProvider#queryChildDocuments(String, String[], String)
868 * @see #getDocumentId(Uri)
869 */
870 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
871 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
872 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
873 .build();
874 }
875
876 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700877 * Build URI representing the children of the target directory in a document
878 * provider. When queried, a provider will return zero or more rows with
879 * columns defined by {@link Document}.
880 * <p>
881 * However, instead of directly accessing the target directory, the returned
882 * URI will leverage access granted through a subtree URI, typically
883 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
884 * directory must be a descendant (child, grandchild, etc) of the subtree.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700885 * <p>
886 * This is typically used to access documents under a user-selected
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700887 * directory tree, since it doesn't require the user to separately confirm
888 * each new document access.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700889 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700890 * @param treeUri the subtree to leverage to gain access to the target
891 * document. The target directory must be a descendant of this
892 * subtree.
893 * @param parentDocumentId the document to return children for, which the
894 * caller may not have direct access to, and which must be a
895 * directory with MIME type of {@link Document#MIME_TYPE_DIR}.
896 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700897 * @see DocumentsProvider#isChildDocument(String, String)
898 * @see #buildChildDocumentsUri(String, String)
899 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700900 public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700901 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700902 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
903 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700904 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
905 }
906
907 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700908 * Build URI representing a search for matching documents under a specific
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700909 * root in a document provider. When queried, a provider will return zero or
910 * more rows with columns defined by {@link Document}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700911 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700912 * @see DocumentsProvider#querySearchDocuments(String, String, String[])
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700913 * @see #getRootId(Uri)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700914 * @see #getSearchDocumentsQuery(Uri)
915 */
916 public static Uri buildSearchDocumentsUri(
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700917 String authority, String rootId, String query) {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700918 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700919 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700920 .appendQueryParameter(PARAM_QUERY, query).build();
921 }
922
923 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700924 * Test if the given URI represents a {@link Document} backed by a
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700925 * {@link DocumentsProvider}.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700926 *
927 * @see #buildDocumentUri(String, String)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700928 * @see #buildDocumentUriUsingTree(Uri, String)
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700929 */
Steve McKay323ee3e2015-09-25 16:02:56 -0700930 public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
931 if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
932 final List<String> paths = uri.getPathSegments();
933 if (paths.size() == 2) {
934 return PATH_DOCUMENT.equals(paths.get(0));
935 } else if (paths.size() == 4) {
936 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
937 }
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700938 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700939 return false;
940 }
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700941
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700942 /** {@hide} */
Steve McKay323ee3e2015-09-25 16:02:56 -0700943 public static boolean isRootUri(Context context, @Nullable Uri uri) {
944 if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
945 final List<String> paths = uri.getPathSegments();
946 return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
947 }
948 return false;
949 }
950
951 /** {@hide} */
952 public static boolean isContentUri(@Nullable Uri uri) {
953 return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
954 }
955
Tomasz Mikolajewski7db9c812016-01-28 09:50:01 +0900956 /**
957 * Test if the given URI represents a {@link Document} tree.
958 *
959 * @see #buildTreeDocumentUri(String, String)
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900960 * @see #getTreeDocumentId(Uri)
Tomasz Mikolajewski7db9c812016-01-28 09:50:01 +0900961 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700962 public static boolean isTreeUri(Uri uri) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700963 final List<String> paths = uri.getPathSegments();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700964 return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700965 }
966
967 private static boolean isDocumentsProvider(Context context, String authority) {
Jeff Sharkeyd2e1e812013-10-09 13:31:13 -0700968 final Intent intent = new Intent(PROVIDER_INTERFACE);
969 final List<ResolveInfo> infos = context.getPackageManager()
970 .queryIntentContentProviders(intent, 0);
971 for (ResolveInfo info : infos) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700972 if (authority.equals(info.providerInfo.authority)) {
Jeff Sharkeyd2e1e812013-10-09 13:31:13 -0700973 return true;
974 }
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700975 }
976 return false;
977 }
978
979 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700980 * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700981 */
982 public static String getRootId(Uri rootUri) {
983 final List<String> paths = rootUri.getPathSegments();
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700984 if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
985 return paths.get(1);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700986 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700987 throw new IllegalArgumentException("Invalid URI: " + rootUri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700988 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700989
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700990 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700991 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700992 *
993 * @see #isDocumentUri(Context, Uri)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700994 */
995 public static String getDocumentId(Uri documentUri) {
996 final List<String> paths = documentUri.getPathSegments();
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700997 if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
998 return paths.get(1);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700999 }
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001000 if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001001 && PATH_DOCUMENT.equals(paths.get(2))) {
1002 return paths.get(3);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001003 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001004 throw new IllegalArgumentException("Invalid URI: " + documentUri);
1005 }
1006
1007 /**
1008 * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001009 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001010 public static String getTreeDocumentId(Uri documentUri) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001011 final List<String> paths = documentUri.getPathSegments();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001012 if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001013 return paths.get(1);
1014 }
1015 throw new IllegalArgumentException("Invalid URI: " + documentUri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001016 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001017
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001018 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001019 * Extract the search query from a URI built by
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001020 * {@link #buildSearchDocumentsUri(String, String, String)}.
1021 */
1022 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
1023 return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
Jeff Sharkey20d96d82013-07-30 17:08:39 -07001024 }
1025
Jeff Sharkey4ec97392013-09-10 12:04:26 -07001026 /** {@hide} */
1027 public static Uri setManageMode(Uri uri) {
1028 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
1029 }
1030
1031 /** {@hide} */
1032 public static boolean isManageMode(Uri uri) {
1033 return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
1034 }
1035
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001036 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001037 * Return thumbnail representing the document at the given URI. Callers are
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001038 * responsible for their own in-memory caching.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001039 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001040 * @param documentUri document to return thumbnail for, which must have
1041 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
1042 * @param size optimal thumbnail size desired. A provider may return a
1043 * thumbnail of a different size, but never more than double the
1044 * requested size.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001045 * @param signal signal used to indicate if caller is no longer interested
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001046 * in the thumbnail.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001047 * @return decoded thumbnail, or {@code null} if problem was encountered.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001048 * @see DocumentsProvider#openDocumentThumbnail(String, Point,
1049 * android.os.CancellationSignal)
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001050 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001051 public static Bitmap getDocumentThumbnail(
Ben Lin8ea82002017-03-08 17:30:16 -08001052 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)
1053 throws FileNotFoundException {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001054 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1055 documentUri.getAuthority());
1056 try {
1057 return getDocumentThumbnail(client, documentUri, size, signal);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001058 } catch (Exception e) {
Jeff Sharkey33819312013-10-29 11:56:37 -07001059 if (!(e instanceof OperationCanceledException)) {
1060 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
1061 }
Ben Lin8ea82002017-03-08 17:30:16 -08001062 rethrowIfNecessary(resolver, e);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001063 return null;
1064 } finally {
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001065 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001066 }
1067 }
1068
1069 /** {@hide} */
1070 public static Bitmap getDocumentThumbnail(
1071 ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001072 throws RemoteException, IOException {
Jeff Sharkey63983432013-08-21 11:33:50 -07001073 final Bundle openOpts = new Bundle();
Jeff Sharkey5b836f22014-08-27 14:46:32 -07001074 openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001075
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001076 AssetFileDescriptor afd = null;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001077 Bitmap bitmap = null;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001078 try {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001079 afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001080
1081 final FileDescriptor fd = afd.getFileDescriptor();
Jeff Sharkey63983432013-08-21 11:33:50 -07001082 final long offset = afd.getStartOffset();
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001083
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001084 // Try seeking on the returned FD, since it gives us the most
1085 // optimal decode path; otherwise fall back to buffering.
1086 BufferedInputStream is = null;
1087 try {
Elliott Hughes34385d32014-04-28 11:11:32 -07001088 Os.lseek(fd, offset, SEEK_SET);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001089 } catch (ErrnoException e) {
1090 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
1091 is.mark(THUMBNAIL_BUFFER_SIZE);
Jeff Sharkey63983432013-08-21 11:33:50 -07001092 }
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001093
Jeff Sharkey63983432013-08-21 11:33:50 -07001094 // We requested a rough thumbnail size, but the remote size may have
1095 // returned something giant, so defensively scale down as needed.
1096 final BitmapFactory.Options opts = new BitmapFactory.Options();
1097 opts.inJustDecodeBounds = true;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001098 if (is != null) {
1099 BitmapFactory.decodeStream(is, null, opts);
Jeff Sharkey63983432013-08-21 11:33:50 -07001100 } else {
1101 BitmapFactory.decodeFileDescriptor(fd, null, opts);
1102 }
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001103
Jeff Sharkey63983432013-08-21 11:33:50 -07001104 final int widthSample = opts.outWidth / size.x;
1105 final int heightSample = opts.outHeight / size.y;
1106
1107 opts.inJustDecodeBounds = false;
1108 opts.inSampleSize = Math.min(widthSample, heightSample);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001109 if (is != null) {
1110 is.reset();
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001111 bitmap = BitmapFactory.decodeStream(is, null, opts);
Jeff Sharkey63983432013-08-21 11:33:50 -07001112 } else {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001113 try {
Elliott Hughes34385d32014-04-28 11:11:32 -07001114 Os.lseek(fd, offset, SEEK_SET);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001115 } catch (ErrnoException e) {
1116 e.rethrowAsIOException();
1117 }
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001118 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
1119 }
1120
1121 // Transform the bitmap if requested. We use a side-channel to
1122 // communicate the orientation, since EXIF thumbnails don't contain
1123 // the rotation flags of the original image.
1124 final Bundle extras = afd.getExtras();
1125 final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
1126 if (orientation != 0) {
1127 final int width = bitmap.getWidth();
1128 final int height = bitmap.getHeight();
1129
1130 final Matrix m = new Matrix();
1131 m.setRotate(orientation, width / 2, height / 2);
1132 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
Jeff Sharkey63983432013-08-21 11:33:50 -07001133 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001134 } finally {
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001135 IoUtils.closeQuietly(afd);
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001136 }
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001137
1138 return bitmap;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001139 }
1140
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001141 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001142 * Create a new document with given MIME type and display name.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001143 *
Ben Lin8ea82002017-03-08 17:30:16 -08001144 * @param parentDocumentUri directory with {@link Document#FLAG_DIR_SUPPORTS_CREATE}
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001145 * @param mimeType MIME type of new document
1146 * @param displayName name of new document
1147 * @return newly created document, or {@code null} if failed
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001148 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001149 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001150 String mimeType, String displayName) throws FileNotFoundException {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001151 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1152 parentDocumentUri.getAuthority());
1153 try {
1154 return createDocument(client, parentDocumentUri, mimeType, displayName);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001155 } catch (Exception e) {
1156 Log.w(TAG, "Failed to create document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001157 rethrowIfNecessary(resolver, e);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001158 return null;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001159 } finally {
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001160 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001161 }
1162 }
1163
1164 /** {@hide} */
1165 public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001166 String mimeType, String displayName) throws RemoteException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001167 final Bundle in = new Bundle();
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001168 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001169 in.putString(Document.COLUMN_MIME_TYPE, mimeType);
1170 in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001171
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001172 final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001173 return out.getParcelable(DocumentsContract.EXTRA_URI);
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001174 }
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001175
Steve McKayd3afdee2015-11-19 17:27:12 -08001176 /** {@hide} */
1177 public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
1178 Uri childDocumentUri) throws RemoteException {
1179
1180 final Bundle in = new Bundle();
1181 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
1182 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
1183
1184 final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
1185 if (out == null) {
1186 throw new RemoteException("Failed to get a reponse from isChildDocument query.");
1187 }
1188 if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
1189 throw new RemoteException("Response did not include result field..");
1190 }
1191 return out.getBoolean(DocumentsContract.EXTRA_RESULT);
1192 }
1193
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001194 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001195 * Change the display name of an existing document.
1196 * <p>
1197 * If the underlying provider needs to create a new
1198 * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
1199 * name, that new document is returned and the original document is no
1200 * longer valid. Otherwise, the original document is returned.
1201 *
1202 * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
1203 * @param displayName updated name for document
1204 * @return the existing or new document after the rename, or {@code null} if
1205 * failed.
1206 */
1207 public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001208 String displayName) throws FileNotFoundException {
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001209 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1210 documentUri.getAuthority());
1211 try {
1212 return renameDocument(client, documentUri, displayName);
1213 } catch (Exception e) {
1214 Log.w(TAG, "Failed to rename document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001215 rethrowIfNecessary(resolver, e);
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001216 return null;
1217 } finally {
1218 ContentProviderClient.releaseQuietly(client);
1219 }
1220 }
1221
1222 /** {@hide} */
1223 public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
1224 String displayName) throws RemoteException {
1225 final Bundle in = new Bundle();
1226 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1227 in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
1228
1229 final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
1230 final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
1231 return (outUri != null) ? outUri : documentUri;
1232 }
1233
1234 /**
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001235 * Delete the given document.
1236 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001237 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001238 * @return if the document was deleted successfully.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001239 */
Ben Lin8ea82002017-03-08 17:30:16 -08001240 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri)
1241 throws FileNotFoundException {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001242 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1243 documentUri.getAuthority());
1244 try {
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001245 deleteDocument(client, documentUri);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001246 return true;
1247 } catch (Exception e) {
1248 Log.w(TAG, "Failed to delete document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001249 rethrowIfNecessary(resolver, e);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001250 return false;
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001251 } finally {
1252 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001253 }
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001254 }
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001255
1256 /** {@hide} */
1257 public static void deleteDocument(ContentProviderClient client, Uri documentUri)
1258 throws RemoteException {
1259 final Bundle in = new Bundle();
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001260 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001261
1262 client.call(METHOD_DELETE_DOCUMENT, null, in);
1263 }
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001264
1265 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001266 * Copies the given document.
1267 *
1268 * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
1269 * @param targetParentDocumentUri document which will become a parent of the source
1270 * document's copy.
1271 * @return the copied document, or {@code null} if failed.
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001272 */
1273 public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001274 Uri targetParentDocumentUri) throws FileNotFoundException {
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001275 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1276 sourceDocumentUri.getAuthority());
1277 try {
1278 return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
1279 } catch (Exception e) {
1280 Log.w(TAG, "Failed to copy document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001281 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001282 return null;
1283 } finally {
1284 ContentProviderClient.releaseQuietly(client);
1285 }
1286 }
1287
1288 /** {@hide} */
1289 public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
1290 Uri targetParentDocumentUri) throws RemoteException {
1291 final Bundle in = new Bundle();
1292 in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
1293 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1294
1295 final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
1296 return out.getParcelable(DocumentsContract.EXTRA_URI);
1297 }
1298
1299 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001300 * Moves the given document under a new parent.
1301 *
1302 * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001303 * @param sourceParentDocumentUri parent document of the document to move.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001304 * @param targetParentDocumentUri document which will become a new parent of the source
1305 * document.
1306 * @return the moved document, or {@code null} if failed.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001307 */
1308 public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001309 Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001310 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1311 sourceDocumentUri.getAuthority());
1312 try {
Tomasz Mikolajewskidcb4efe2016-02-10 19:14:49 +09001313 return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001314 targetParentDocumentUri);
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001315 } catch (Exception e) {
1316 Log.w(TAG, "Failed to move document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001317 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001318 return null;
1319 } finally {
1320 ContentProviderClient.releaseQuietly(client);
1321 }
1322 }
1323
1324 /** {@hide} */
1325 public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001326 Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001327 final Bundle in = new Bundle();
1328 in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001329 in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001330 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1331
1332 final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
1333 return out.getParcelable(DocumentsContract.EXTRA_URI);
1334 }
1335
1336 /**
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001337 * Removes the given document from a parent directory.
1338 *
1339 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
1340 * This method is especially useful if the document can be in multiple parents.
1341 *
1342 * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
1343 * @param parentDocumentUri parent document of the document to remove.
1344 * @return true if the document was removed successfully.
1345 */
1346 public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001347 Uri parentDocumentUri) throws FileNotFoundException {
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001348 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1349 documentUri.getAuthority());
1350 try {
1351 removeDocument(client, documentUri, parentDocumentUri);
1352 return true;
1353 } catch (Exception e) {
1354 Log.w(TAG, "Failed to remove document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001355 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001356 return false;
1357 } finally {
1358 ContentProviderClient.releaseQuietly(client);
1359 }
1360 }
1361
1362 /** {@hide} */
1363 public static void removeDocument(ContentProviderClient client, Uri documentUri,
1364 Uri parentDocumentUri) throws RemoteException {
1365 final Bundle in = new Bundle();
1366 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1367 in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
1368
1369 client.call(METHOD_REMOVE_DOCUMENT, null, in);
1370 }
1371
Garfield Tan87877032017-03-22 12:01:14 -07001372 /**
1373 * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
1374 *
1375 * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
1376 */
1377 public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
Ben Line7822fb2016-06-24 15:21:08 -07001378 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1379 rootUri.getAuthority());
1380 try {
Garfield Tan87877032017-03-22 12:01:14 -07001381 ejectRoot(client, rootUri);
1382 } catch (RemoteException e) {
1383 e.rethrowAsRuntimeException();
Ben Line7822fb2016-06-24 15:21:08 -07001384 } finally {
1385 ContentProviderClient.releaseQuietly(client);
1386 }
1387 }
1388
1389 /** {@hide} */
Garfield Tan87877032017-03-22 12:01:14 -07001390 public static void ejectRoot(ContentProviderClient client, Uri rootUri)
Ben Line7822fb2016-06-24 15:21:08 -07001391 throws RemoteException {
1392 final Bundle in = new Bundle();
1393 in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
1394
Garfield Tan87877032017-03-22 12:01:14 -07001395 client.call(METHOD_EJECT_ROOT, null, in);
Ben Line7822fb2016-06-24 15:21:08 -07001396 }
1397
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001398 /**
Julian Mancinib6505152017-06-27 13:29:09 -07001399 * Returns metadata associated with the document. The type of metadata returned
1400 * is specific to the document type. For example image files will largely return EXIF
1401 * metadata.
1402 *
1403 * <p>The returned {@link Bundle} will contain zero or more entries.
1404 * <p>Each entry represents a specific type of metadata.
1405 *
1406 * <p>if tags == null, then a list of default tags will be used.
1407 *
1408 * @param documentUri a Document URI
1409 * @param tags an array of keys to choose which data are added to the Bundle. If the Document
1410 * is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
1411 * If tags are null, a set of default tags will be used. If the tags don't
1412 * match with any relevant data, they will not be added to the Bundle.
1413 * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
1414 * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
1415 * types of metadata gathered.
1416 *
1417 * <pre><code>
1418 * Bundle metadata = DocumentsContract.getDocumentMetadata(resolver, imageDocUri, tags);
1419 * int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
1420 * </code></pre>
1421 *
1422 * {@hide}
1423 */
1424 public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri,
1425 @Nullable String[] tags)
1426 throws FileNotFoundException {
1427 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1428 documentUri.getAuthority());
1429
1430 try {
1431 return getDocumentMetadata(client, documentUri, tags);
1432 } catch (Exception e) {
1433 Log.w(TAG, "Failed to get document metadata");
1434 rethrowIfNecessary(resolver, e);
1435 return null;
1436 } finally {
1437 ContentProviderClient.releaseQuietly(client);
1438 }
1439 }
1440
1441 /**
1442 * Returns metadata associated with the document. The type of metadata returned
1443 * is specific to the document type. For example image files will largely return EXIF
1444 * metadata.
1445 *
1446 * <p>The returned {@link Bundle} will contain zero or more entries.
1447 * <p>Each entry represents a specific type of metadata.
1448 *
1449 * <p>if tags == null, then a list of default tags will be used.
1450 *
1451 * @param documentUri a Document URI
1452 * @param tags an array of keys to choose which data are added to the Bundle. If the Document
1453 * is a JPG or ExifInterface compatible, send keys from {@link ExifInterface}.
1454 * If tags are null, a set of default tags will be used. If the tags don't
1455 * match with any relevant data, they will not be added to the Bundle.
1456 * @return a Bundle of Bundles. If metadata exists within the Bundle, there will also
1457 * be a String under DocumentsContract.METADATA_TYPES that will return a String[] of the
1458 * types of metadata gathered.
1459 *
1460 * <pre><code>
1461 * Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
1462 * int imageLength = metadata.getInt(ExifInterface.TAG_IMAGE_LENGTH);
1463 * </code></pre>
1464 *
1465 * {@hide}
1466 */
1467 public static Bundle getDocumentMetadata(ContentProviderClient client,
1468 Uri documentUri, @Nullable String[] tags) throws RemoteException {
1469 final Bundle in = new Bundle();
1470 in.putParcelable(EXTRA_URI, documentUri);
1471 in.putStringArray(EXTRA_METADATA_TAGS, tags);
1472
1473 final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in);
1474
1475 if (out == null) {
1476 throw new RemoteException("Failed to get a response from getDocumentMetadata");
1477 }
1478 return out;
1479 }
1480
1481 /**
Garfield Tanb690b4d2017-03-01 16:05:23 -08001482 * Finds the canonical path from the top of the document tree.
Garfield Tanaba97f32016-10-06 17:34:19 +00001483 *
Garfield Tanb690b4d2017-03-01 16:05:23 -08001484 * The {@link Path#getPath()} of the return value contains the document ID
1485 * of all documents along the path from the top the document tree to the
1486 * requested document, both inclusive.
1487 *
1488 * The {@link Path#getRootId()} of the return value returns {@code null}.
Garfield Tan06940e12016-10-07 16:03:17 -07001489 *
1490 * @param treeUri treeUri of the document which path is requested.
Garfield Tanb690b4d2017-03-01 16:05:23 -08001491 * @return the path of the document, or {@code null} if failed.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001492 * @see DocumentsProvider#findDocumentPath(String, String)
Garfield Tanaba97f32016-10-06 17:34:19 +00001493 */
Ben Lin8ea82002017-03-08 17:30:16 -08001494 public static Path findDocumentPath(ContentResolver resolver, Uri treeUri)
1495 throws FileNotFoundException {
Garfield Tan06940e12016-10-07 16:03:17 -07001496 checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
1497
Garfield Tanaba97f32016-10-06 17:34:19 +00001498 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
Garfield Tan06940e12016-10-07 16:03:17 -07001499 treeUri.getAuthority());
Garfield Tanaba97f32016-10-06 17:34:19 +00001500 try {
Garfield Tanb690b4d2017-03-01 16:05:23 -08001501 return findDocumentPath(client, treeUri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001502 } catch (Exception e) {
1503 Log.w(TAG, "Failed to find path", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001504 rethrowIfNecessary(resolver, e);
Garfield Tanaba97f32016-10-06 17:34:19 +00001505 return null;
1506 } finally {
1507 ContentProviderClient.releaseQuietly(client);
1508 }
1509 }
1510
Garfield Tan06940e12016-10-07 16:03:17 -07001511 /**
Garfield Tanb690b4d2017-03-01 16:05:23 -08001512 * Finds the canonical path. If uri is a document uri returns path from a root and
1513 * its associated root id. If uri is a tree uri returns the path from the top of
1514 * the tree. The {@link Path#getPath()} of the return value contains document ID
1515 * starts from the top of the tree or the root document to the requested document,
1516 * both inclusive.
Garfield Tan06940e12016-10-07 16:03:17 -07001517 *
Garfield Tanb690b4d2017-03-01 16:05:23 -08001518 * Callers can expect the root ID returned from multiple calls to this method is
1519 * consistent.
Garfield Tan06940e12016-10-07 16:03:17 -07001520 *
1521 * @param uri uri of the document which path is requested. It can be either a
1522 * plain document uri or a tree uri.
1523 * @return the path of the document.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001524 * @see DocumentsProvider#findDocumentPath(String, String)
Garfield Tan06940e12016-10-07 16:03:17 -07001525 *
1526 * {@hide}
1527 */
Garfield Tan0b3cf662016-10-31 12:59:45 -07001528 public static Path findDocumentPath(ContentProviderClient client, Uri uri)
1529 throws RemoteException {
1530
Garfield Tanaba97f32016-10-06 17:34:19 +00001531 final Bundle in = new Bundle();
Garfield Tan06940e12016-10-07 16:03:17 -07001532 in.putParcelable(DocumentsContract.EXTRA_URI, uri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001533
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001534 final Bundle out = client.call(METHOD_FIND_DOCUMENT_PATH, null, in);
Garfield Tanaba97f32016-10-06 17:34:19 +00001535
1536 return out.getParcelable(DocumentsContract.EXTRA_RESULT);
1537 }
1538
1539 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001540 * Creates an intent for obtaining a web link for the specified document.
1541 *
1542 * <p>Note, that due to internal limitations, if there is already a web link
1543 * intent created for the specified document but with different options,
1544 * then it may be overriden.
1545 *
1546 * <p>Providers are required to show confirmation UI for all new permissions granted
1547 * for the linked document.
1548 *
Tomasz Mikolajewski463b6862017-03-22 17:34:05 +09001549 * <p>If list of recipients is known, then it should be passed in options as
1550 * {@link Intent#EXTRA_EMAIL} as a list of email addresses. Note, that
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001551 * this is just a hint for the provider, which can ignore the list. In either
1552 * case the provider is required to show a UI for letting the user confirm
1553 * any new permission grants.
1554 *
Tomasz Mikolajewski463b6862017-03-22 17:34:05 +09001555 * <p>Note, that the entire <code>options</code> bundle will be sent to the provider
1556 * backing the passed <code>uri</code>. Make sure that you trust the provider
1557 * before passing any sensitive information.
Tomasz Mikolajewskif1d55402017-03-03 15:09:40 +09001558 *
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001559 * <p>Since this API may show a UI, it cannot be called from background.
1560 *
1561 * <p>In order to obtain the Web Link use code like this:
1562 * <pre><code>
1563 * void onSomethingHappened() {
1564 * IntentSender sender = DocumentsContract.createWebLinkIntent(<i>...</i>);
1565 * if (sender != null) {
1566 * startIntentSenderForResult(
Tomasz Mikolajewskif1d55402017-03-03 15:09:40 +09001567 * sender,
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001568 * WEB_LINK_REQUEST_CODE,
1569 * null, 0, 0, 0, null);
1570 * }
1571 * }
1572 *
1573 * <i>(...)</i>
1574 *
1575 * void onActivityResult(int requestCode, int resultCode, Intent data) {
1576 * if (requestCode == WEB_LINK_REQUEST_CODE && resultCode == RESULT_OK) {
1577 * Uri weblinkUri = data.getData();
1578 * <i>...</i>
1579 * }
1580 * }
1581 * </code></pre>
1582 *
1583 * @param uri uri for the document to create a link to.
1584 * @param options Extra information for generating the link.
1585 * @return an intent sender to obtain the web link, or null if the document
1586 * is not linkable, or creating the intent sender failed.
1587 * @see DocumentsProvider#createWebLinkIntent(String, Bundle)
1588 * @see Intent#EXTRA_EMAIL
1589 */
1590 public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri,
Ben Lin8ea82002017-03-08 17:30:16 -08001591 Bundle options) throws FileNotFoundException {
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001592 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1593 uri.getAuthority());
1594 try {
1595 return createWebLinkIntent(client, uri, options);
1596 } catch (Exception e) {
1597 Log.w(TAG, "Failed to create a web link intent", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001598 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001599 return null;
1600 } finally {
1601 ContentProviderClient.releaseQuietly(client);
1602 }
1603 }
1604
1605 /**
1606 * {@hide}
1607 */
1608 public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri,
1609 Bundle options) throws RemoteException {
1610 final Bundle in = new Bundle();
1611 in.putParcelable(DocumentsContract.EXTRA_URI, uri);
1612
1613 // Options may be provider specific, so put them in a separate bundle to
1614 // avoid overriding the Uri.
1615 if (options != null) {
1616 in.putBundle(EXTRA_OPTIONS, options);
1617 }
1618
1619 final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, null, in);
1620 return out.getParcelable(DocumentsContract.EXTRA_RESULT);
1621 }
1622
1623 /**
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001624 * Open the given image for thumbnail purposes, using any embedded EXIF
1625 * thumbnail if available, and providing orientation hints from the parent
1626 * image.
1627 *
1628 * @hide
1629 */
1630 public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
1631 final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
1632 file, ParcelFileDescriptor.MODE_READ_ONLY);
1633 Bundle extras = null;
1634
1635 try {
1636 final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
1637
1638 switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
1639 case ExifInterface.ORIENTATION_ROTATE_90:
1640 extras = new Bundle(1);
1641 extras.putInt(EXTRA_ORIENTATION, 90);
1642 break;
1643 case ExifInterface.ORIENTATION_ROTATE_180:
1644 extras = new Bundle(1);
1645 extras.putInt(EXTRA_ORIENTATION, 180);
1646 break;
1647 case ExifInterface.ORIENTATION_ROTATE_270:
1648 extras = new Bundle(1);
1649 extras.putInt(EXTRA_ORIENTATION, 270);
1650 break;
1651 }
1652
1653 final long[] thumb = exif.getThumbnailRange();
1654 if (thumb != null) {
1655 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
1656 }
1657 } catch (IOException e) {
1658 }
1659
1660 return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
1661 }
Garfield Tanaba97f32016-10-06 17:34:19 +00001662
Ben Lin8ea82002017-03-08 17:30:16 -08001663 private static void rethrowIfNecessary(ContentResolver resolver, Exception e)
1664 throws FileNotFoundException {
1665 // We only want to throw applications targetting O and above
1666 if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
1667 if (e instanceof ParcelableException) {
1668 ((ParcelableException) e).maybeRethrow(FileNotFoundException.class);
Ben Lin227e6e82017-03-13 15:36:13 -07001669 } else if (e instanceof RemoteException) {
Ben Lin8ea82002017-03-08 17:30:16 -08001670 ((RemoteException) e).rethrowAsRuntimeException();
1671 } else if (e instanceof RuntimeException) {
1672 throw (RuntimeException) e;
1673 }
1674 }
1675 }
1676
Garfield Tanaba97f32016-10-06 17:34:19 +00001677 /**
Garfield Tan0b3cf662016-10-31 12:59:45 -07001678 * Holds a path from a document to a particular document under it. It
1679 * may also contains the root ID where the path resides.
Garfield Tanaba97f32016-10-06 17:34:19 +00001680 */
1681 public static final class Path implements Parcelable {
1682
Garfield Tan06940e12016-10-07 16:03:17 -07001683 private final @Nullable String mRootId;
1684 private final List<String> mPath;
Garfield Tanaba97f32016-10-06 17:34:19 +00001685
1686 /**
1687 * Creates a Path.
Garfield Tan06940e12016-10-07 16:03:17 -07001688 *
1689 * @param rootId the ID of the root. May be null.
Garfield Tan0b3cf662016-10-31 12:59:45 -07001690 * @param path the list of document ID from the parent document at
Garfield Tan06940e12016-10-07 16:03:17 -07001691 * position 0 to the child document.
Garfield Tanaba97f32016-10-06 17:34:19 +00001692 */
Garfield Tan5f214802016-10-26 14:52:46 -07001693 public Path(@Nullable String rootId, List<String> path) {
Garfield Tan06940e12016-10-07 16:03:17 -07001694 checkCollectionNotEmpty(path, "path");
1695 checkCollectionElementsNotNull(path, "path");
1696
Garfield Tanaba97f32016-10-06 17:34:19 +00001697 mRootId = rootId;
1698 mPath = path;
1699 }
1700
Garfield Tan06940e12016-10-07 16:03:17 -07001701 /**
1702 * Returns the root id or null if the calling package doesn't have
1703 * permission to access root information.
1704 */
1705 public @Nullable String getRootId() {
1706 return mRootId;
1707 }
1708
1709 /**
1710 * Returns the path. The path is trimmed to the top of tree if
1711 * calling package doesn't have permission to access those
1712 * documents.
1713 */
1714 public List<String> getPath() {
1715 return mPath;
1716 }
1717
1718 @Override
1719 public boolean equals(Object o) {
1720 if (this == o) {
1721 return true;
1722 }
1723 if (o == null || !(o instanceof Path)) {
1724 return false;
1725 }
1726 Path path = (Path) o;
1727 return Objects.equals(mRootId, path.mRootId) &&
1728 Objects.equals(mPath, path.mPath);
1729 }
1730
1731 @Override
1732 public int hashCode() {
1733 return Objects.hash(mRootId, mPath);
1734 }
1735
1736 @Override
1737 public String toString() {
1738 return new StringBuilder()
1739 .append("DocumentsContract.Path{")
1740 .append("rootId=")
1741 .append(mRootId)
1742 .append(", path=")
1743 .append(mPath)
1744 .append("}")
1745 .toString();
1746 }
1747
Garfield Tanaba97f32016-10-06 17:34:19 +00001748 @Override
1749 public void writeToParcel(Parcel dest, int flags) {
1750 dest.writeString(mRootId);
1751 dest.writeStringList(mPath);
1752 }
1753
1754 @Override
1755 public int describeContents() {
1756 return 0;
1757 }
1758
1759 public static final Creator<Path> CREATOR = new Creator<Path>() {
1760 @Override
1761 public Path createFromParcel(Parcel in) {
1762 final String rootId = in.readString();
1763 final List<String> path = in.createStringArrayList();
1764 return new Path(rootId, path);
1765 }
1766
1767 @Override
1768 public Path[] newArray(int size) {
1769 return new Path[size];
1770 }
1771 };
1772 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001773}