blob: acb3fa816bdbbd0dc1276d7b167e8a1063518f86 [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
Elliott Hughes34385d32014-04-28 11:11:32 -070019import static android.system.OsConstants.SEEK_SET;
Julian Mancinib6505152017-06-27 13:29:09 -070020
Garfield Tan06940e12016-10-07 16:03:17 -070021import static com.android.internal.util.Preconditions.checkArgument;
22import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
23import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
24
Steve McKay323ee3e2015-09-25 16:02:56 -070025import android.annotation.Nullable;
Mathew Inwoodba503112018-08-10 09:37:35 +010026import android.annotation.UnsupportedAppUsage;
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 Sharkey9f2dc052018-01-07 16:47:31 -070054import android.util.DataUnit;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070055import android.util.Log;
56
57import libcore.io.IoUtils;
58
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070059import java.io.BufferedInputStream;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -070060import java.io.File;
Jeff Sharkey9d0843d2013-05-07 12:41:33 -070061import java.io.FileDescriptor;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070062import java.io.FileInputStream;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -070063import java.io.FileNotFoundException;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070064import java.io.IOException;
Jeff Sharkeydc2963a2013-08-02 15:55:26 -070065import java.util.List;
Garfield Tan06940e12016-10-07 16:03:17 -070066import java.util.Objects;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070067
68/**
Jeff Sharkeybd3b9022013-08-20 15:20:04 -070069 * Defines the contract between a documents provider and the platform.
70 * <p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070071 * To create a document provider, extend {@link DocumentsProvider}, which
72 * provides a foundational implementation of this contract.
Jeff Sharkey21de56a2014-04-05 19:05:24 -070073 * <p>
74 * All client apps must hold a valid URI permission grant to access documents,
75 * typically issued when a user makes a selection through
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070076 * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
Felipe Leme04a5d402016-02-08 16:44:06 -080077 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
78 * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
Jeff Sharkeybd3b9022013-08-20 15:20:04 -070079 *
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070080 * @see DocumentsProvider
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070081 */
82public final class DocumentsContract {
Steve McKayd3afdee2015-11-19 17:27:12 -080083 private static final String TAG = "DocumentsContract";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070084
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070085 // content://com.example/root/
86 // content://com.example/root/sdcard/
87 // content://com.example/root/sdcard/recent/
Jeff Sharkey3e1189b2013-09-12 21:59:06 -070088 // content://com.example/root/sdcard/search/?query=pony
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070089 // content://com.example/document/12/
90 // content://com.example/document/12/children/
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070091 // content://com.example/tree/12/document/24/
92 // content://com.example/tree/12/document/24/children/
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070093
94 private DocumentsContract() {
95 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -070096
Jeff Sharkey85f5f812013-10-07 10:16:12 -070097 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070098 * Intent action used to identify {@link DocumentsProvider} instances. This
99 * is used in the {@code <intent-filter>} of a {@code <provider>}.
Jeff Sharkey85f5f812013-10-07 10:16:12 -0700100 */
101 public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
102
Jeff Sharkey5b83f852013-08-14 18:29:19 -0700103 /** {@hide} */
Jeff Sharkey15be8362013-10-09 13:52:17 -0700104 public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
105
Jeff Sharkey96c62052013-10-25 16:30:54 -0700106 /** {@hide} */
Aga Wronska1719b352016-03-21 11:28:03 -0700107 public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
108
109 /** {@hide} */
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700110 public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI";
111
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700112 /**
Garfield Tanb44ae612016-11-07 16:46:37 -0800113 * Sets the desired initial location visible to user when file chooser is shown.
114 *
115 * <p>Applicable to {@link Intent} with actions:
116 * <ul>
117 * <li>{@link Intent#ACTION_OPEN_DOCUMENT}</li>
118 * <li>{@link Intent#ACTION_CREATE_DOCUMENT}</li>
119 * <li>{@link Intent#ACTION_OPEN_DOCUMENT_TREE}</li>
120 * </ul>
121 *
122 * <p>Location should specify a document URI or a tree URI with document ID. If
123 * this URI identifies a non-directory, document navigator will attempt to use the parent
124 * of the document as the initial location.
Garfield Tan40d7b352017-03-02 15:30:30 -0800125 *
126 * <p>The initial location is system specific if this extra is missing or document navigator
127 * failed to locate the desired initial location.
Garfield Tanb44ae612016-11-07 16:46:37 -0800128 */
129 public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
130
131 /**
Ben Kwa77797402015-05-29 15:40:31 -0700132 * Set this in a DocumentsUI intent to cause a package's own roots to be
133 * excluded from the roots list.
134 */
135 public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF";
136
137 /**
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700138 * Included in {@link AssetFileDescriptor#getExtras()} when returned
139 * thumbnail should be rotated.
140 *
141 * @see MediaStore.Images.ImageColumns#ORIENTATION
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700142 */
Tomasz Mikolajewski5f53f652016-03-31 09:34:51 +0900143 public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION";
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -0700144
Tomasz Mikolajewski0e591f92015-06-12 16:22:17 -0700145 /**
146 * Overrides the default prompt text in DocumentsUI when set in an intent.
147 */
148 public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT";
149
Ben Linbd036d82016-12-15 15:41:08 -0800150 /**
151 * Action of intent issued by DocumentsUI when user wishes to open/configure/manage a particular
152 * document in the provider application.
Ben Lin8ea82002017-03-08 17:30:16 -0800153 *
Ben Linbd036d82016-12-15 15:41:08 -0800154 * <p>When issued, the intent will include the URI of the document as the intent data.
Ben Lin8ea82002017-03-08 17:30:16 -0800155 *
Ben Linbd036d82016-12-15 15:41:08 -0800156 * <p>A provider wishing to provide support for this action should do two things.
157 * <li>Add an {@code <intent-filter>} matching this action.
158 * <li>When supplying information in {@link DocumentsProvider#queryChildDocuments}, include
159 * {@link Document#FLAG_SUPPORTS_SETTINGS} in the flags for each document that supports
160 * settings.
161 *
162 * @see DocumentsContact#Document#FLAG_SUPPORTS_SETTINGS
163 */
164 public static final String
165 ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS";
166
Jeff Sharkey15be8362013-10-09 13:52:17 -0700167 /** {@hide} */
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700168 public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700169
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700170 /** {@hide} */
Jeff Sharkey1407d4c2015-04-12 21:52:24 -0700171 public static final String
172 ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700173
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700174 /**
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700175 * Buffer is large enough to rewind past any EXIF headers.
176 */
Jeff Sharkey9f2dc052018-01-07 16:47:31 -0700177 private static final int THUMBNAIL_BUFFER_SIZE = (int) DataUnit.KIBIBYTES.toBytes(128);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700178
Jeff Sharkeycc2ae6b42015-09-29 13:04:46 -0700179 /** {@hide} */
Garfield Tan92b96ba2016-11-01 14:33:48 -0700180 public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
181 "com.android.externalstorage.documents";
182
183 /** {@hide} */
Jeff Sharkeycc2ae6b42015-09-29 13:04:46 -0700184 public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui";
185
Julian Mancinib6505152017-06-27 13:29:09 -0700186 /** {@hide} */
187 public static final String METADATA_TYPES = "android:documentMetadataType";
188
189 /** {@hide} */
190 public static final String METADATA_EXIF = "android:documentExif";
191
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700192 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700193 * Constants related to a document, including {@link Cursor} column names
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700194 * and flags.
195 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700196 * A document can be either an openable stream (with a specific MIME type),
197 * or a directory containing additional documents (with the
198 * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
199 * subtree containing zero or more documents, which can recursively contain
200 * even more documents and directories.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700201 * <p>
202 * All columns are <em>read-only</em> to client applications.
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700203 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700204 public final static class Document {
205 private Document() {
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700206 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700207
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700208 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700209 * Unique ID of a document. This ID is both provided by and interpreted
210 * by a {@link DocumentsProvider}, and should be treated as an opaque
Jeff Sharkey6efba222013-09-27 16:44:11 -0700211 * value by client applications. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700212 * <p>
213 * Each document must have a unique ID within a provider, but that
214 * single document may be included as a child of multiple directories.
215 * <p>
216 * A provider must always return durable IDs, since they will be used to
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700217 * issue long-term URI permission grants when an application interacts
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700218 * with {@link Intent#ACTION_OPEN_DOCUMENT} and
219 * {@link Intent#ACTION_CREATE_DOCUMENT}.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700220 * <p>
221 * Type: STRING
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700222 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700223 public static final String COLUMN_DOCUMENT_ID = "document_id";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700224
225 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700226 * Concrete MIME type of a document. For example, "image/png" or
227 * "application/pdf" for openable files. A document can also be a
228 * directory containing additional documents, which is represented with
Jeff Sharkey6efba222013-09-27 16:44:11 -0700229 * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700230 * <p>
231 * Type: STRING
232 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700233 * @see #MIME_TYPE_DIR
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700234 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700235 public static final String COLUMN_MIME_TYPE = "mime_type";
236
237 /**
238 * Display name of a document, used as the primary title displayed to a
Jeff Sharkey6efba222013-09-27 16:44:11 -0700239 * user. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700240 * <p>
241 * Type: STRING
242 */
243 public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
244
245 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700246 * Summary of a document, which may be shown to a user. This column is
247 * optional, and may be {@code null}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700248 * <p>
249 * Type: STRING
250 */
251 public static final String COLUMN_SUMMARY = "summary";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700252
253 /**
254 * Timestamp when a document was last modified, in milliseconds since
Jeff Sharkey6efba222013-09-27 16:44:11 -0700255 * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
256 * {@code null} if unknown. A {@link DocumentsProvider} can update this
257 * field using events from {@link OnCloseListener} or other reliable
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700258 * {@link ParcelFileDescriptor} transports.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700259 * <p>
260 * Type: INTEGER (long)
261 *
262 * @see System#currentTimeMillis()
263 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700264 public static final String COLUMN_LAST_MODIFIED = "last_modified";
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700265
266 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700267 * Specific icon resource ID for a document. This column is optional,
268 * and may be {@code null} to use a platform-provided default icon based
269 * on {@link #COLUMN_MIME_TYPE}.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700270 * <p>
271 * Type: INTEGER (int)
272 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700273 public static final String COLUMN_ICON = "icon";
Jeff Sharkey66516692013-08-06 11:26:10 -0700274
275 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700276 * Flags that apply to a document. This column is required.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700277 * <p>
278 * Type: INTEGER (int)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700279 *
280 * @see #FLAG_SUPPORTS_WRITE
281 * @see #FLAG_SUPPORTS_DELETE
282 * @see #FLAG_SUPPORTS_THUMBNAIL
283 * @see #FLAG_DIR_PREFERS_GRID
Jeff Sharkey6efba222013-09-27 16:44:11 -0700284 * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900285 * @see #FLAG_VIRTUAL_DOCUMENT
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900286 * @see #FLAG_SUPPORTS_COPY
287 * @see #FLAG_SUPPORTS_MOVE
288 * @see #FLAG_SUPPORTS_REMOVE
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700289 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700290 public static final String COLUMN_FLAGS = "flags";
291
292 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700293 * Size of a document, in bytes, or {@code null} if unknown. This column
294 * is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700295 * <p>
296 * Type: INTEGER (long)
297 */
298 public static final String COLUMN_SIZE = OpenableColumns.SIZE;
299
300 /**
301 * MIME type of a document which is a directory that may contain
302 * additional documents.
303 *
304 * @see #COLUMN_MIME_TYPE
305 */
306 public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
307
308 /**
309 * Flag indicating that a document can be represented as a thumbnail.
310 *
311 * @see #COLUMN_FLAGS
312 * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
313 * Point, CancellationSignal)
314 * @see DocumentsProvider#openDocumentThumbnail(String, Point,
315 * android.os.CancellationSignal)
316 */
317 public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
318
319 /**
320 * Flag indicating that a document supports writing.
321 * <p>
322 * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
323 * the calling application is granted both
324 * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
325 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
326 * writability of a document may change over time, for example due to
327 * remote access changes. This flag indicates that a document client can
328 * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
Steve McKay83df8c02015-09-16 15:07:31 -0700329 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700330 * @see #COLUMN_FLAGS
331 */
332 public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
333
334 /**
335 * Flag indicating that a document is deletable.
336 *
337 * @see #COLUMN_FLAGS
338 * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
339 * @see DocumentsProvider#deleteDocument(String)
340 */
341 public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
342
343 /**
344 * Flag indicating that a document is a directory that supports creation
345 * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
346 * {@link #MIME_TYPE_DIR}.
347 *
348 * @see #COLUMN_FLAGS
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700349 * @see DocumentsProvider#createDocument(String, String, String)
350 */
351 public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
352
353 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700354 * Flag indicating that a directory prefers its contents be shown in a
355 * larger format grid. Usually suitable when a directory contains mostly
356 * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
357 * {@link #MIME_TYPE_DIR}.
358 *
359 * @see #COLUMN_FLAGS
360 */
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700361 public static final int FLAG_DIR_PREFERS_GRID = 1 << 4;
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700362
363 /**
364 * Flag indicating that a directory prefers its contents be sorted by
365 * {@link #COLUMN_LAST_MODIFIED}. Only valid when
366 * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
367 *
368 * @see #COLUMN_FLAGS
369 */
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700370 public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5;
Jeff Sharkeyf6db1542013-09-13 13:42:19 -0700371
372 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700373 * Flag indicating that a document can be renamed.
374 *
375 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900376 * @see DocumentsContract#renameDocument(ContentResolver, Uri,
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700377 * String)
378 * @see DocumentsProvider#renameDocument(String, String)
379 */
380 public static final int FLAG_SUPPORTS_RENAME = 1 << 6;
381
382 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700383 * Flag indicating that a document can be copied to another location
384 * within the same document provider.
385 *
386 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900387 * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900388 * @see DocumentsProvider#copyDocument(String, String)
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700389 */
390 public static final int FLAG_SUPPORTS_COPY = 1 << 7;
391
392 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900393 * Flag indicating that a document can be moved to another location
394 * within the same document provider.
395 *
396 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900397 * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri)
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900398 * @see DocumentsProvider#moveDocument(String, String, String)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900399 */
400 public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
401
402 /**
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900403 * Flag indicating that a document is virtual, and doesn't have byte
404 * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
405 *
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +0900406 * <p><em>Virtual documents must have at least one alternative streamable
407 * format via {@link DocumentsProvider#openTypedDocument}</em>
408 *
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900409 * @see #COLUMN_FLAGS
410 * @see #COLUMN_MIME_TYPE
411 * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
412 * android.os.CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +0900413 * @see DocumentsProvider#getDocumentStreamTypes(String, String)
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900414 */
Tomasz Mikolajewski75395652016-01-07 07:19:22 +0000415 public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
Tomasz Mikolajewskia8057a92015-11-16 11:41:28 +0900416
417 /**
Tomasz Mikolajewski9b055e12016-02-01 13:01:34 +0900418 * Flag indicating that a document can be removed from a parent.
419 *
420 * @see #COLUMN_FLAGS
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900421 * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri)
Tomasz Mikolajewski9b055e12016-02-01 13:01:34 +0900422 * @see DocumentsProvider#removeDocument(String, String)
423 */
424 public static final int FLAG_SUPPORTS_REMOVE = 1 << 10;
425
426 /**
Ben Linbd036d82016-12-15 15:41:08 -0800427 * Flag indicating that a document has settings that can be configured by user.
428 *
429 * @see #COLUMN_FLAGS
430 * @see #ACTION_DOCUMENT_SETTINGS
431 */
432 public static final int FLAG_SUPPORTS_SETTINGS = 1 << 11;
433
434 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900435 * Flag indicating that a Web link can be obtained for the document.
436 *
437 * @see #COLUMN_FLAGS
438 * @see DocumentsContract#createWebLinkIntent(PackageManager, Uri, Bundle)
439 */
440 public static final int FLAG_WEB_LINKABLE = 1 << 12;
441
442 /**
Steve McKay168e4642016-03-14 13:02:56 -0700443 * Flag indicating that a document is not complete, likely its
444 * contents are being downloaded. Partial files cannot be opened,
445 * copied, moved in the UI. But they can be deleted and retried
446 * if they represent a failed download.
447 *
448 * @see #COLUMN_FLAGS
449 * @hide
450 */
451 public static final int FLAG_PARTIAL = 1 << 16;
Julian Mancinib6505152017-06-27 13:29:09 -0700452
453 /**
454 * Flag indicating that a document has available metadata that can be read
455 * using DocumentsContract#getDocumentMetadata
456 * @hide
457 */
458 public static final int FLAG_SUPPORTS_METADATA = 1 << 17;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -0700459 }
460
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700461 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700462 * Constants related to a root of documents, including {@link Cursor} column
463 * names and flags. A root is the start of a tree of documents, such as a
464 * physical storage device, or an account. Each root starts at the directory
465 * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
466 * contain both documents and directories.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700467 * <p>
468 * All columns are <em>read-only</em> to client applications.
Jeff Sharkeybd3b9022013-08-20 15:20:04 -0700469 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700470 public final static class Root {
471 private Root() {
472 }
473
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700474 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700475 * Unique ID of a root. This ID is both provided by and interpreted by a
476 * {@link DocumentsProvider}, and should be treated as an opaque value
Jeff Sharkey6efba222013-09-27 16:44:11 -0700477 * by client applications. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700478 * <p>
479 * Type: STRING
480 */
481 public static final String COLUMN_ROOT_ID = "root_id";
482
483 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700484 * Flags that apply to a root. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700485 * <p>
486 * Type: INTEGER (int)
487 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700488 * @see #FLAG_LOCAL_ONLY
489 * @see #FLAG_SUPPORTS_CREATE
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700490 * @see #FLAG_SUPPORTS_RECENTS
491 * @see #FLAG_SUPPORTS_SEARCH
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700492 */
493 public static final String COLUMN_FLAGS = "flags";
494
495 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700496 * Icon resource ID for a root. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700497 * <p>
498 * Type: INTEGER (int)
499 */
500 public static final String COLUMN_ICON = "icon";
501
502 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700503 * Title for a root, which will be shown to a user. This column is
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700504 * required. For a single storage service surfacing multiple accounts as
505 * different roots, this title should be the name of the service.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700506 * <p>
507 * Type: STRING
508 */
509 public static final String COLUMN_TITLE = "title";
510
511 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700512 * Summary for this root, which may be shown to a user. This column is
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700513 * optional, and may be {@code null}. For a single storage service
514 * surfacing multiple accounts as different roots, this summary should
515 * be the name of the account.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700516 * <p>
517 * Type: STRING
518 */
519 public static final String COLUMN_SUMMARY = "summary";
520
521 /**
522 * Document which is a directory that represents the top directory of
Jeff Sharkey6efba222013-09-27 16:44:11 -0700523 * this root. This column is required.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700524 * <p>
525 * Type: STRING
526 *
527 * @see Document#COLUMN_DOCUMENT_ID
528 */
529 public static final String COLUMN_DOCUMENT_ID = "document_id";
530
531 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700532 * Number of bytes available in this root. This column is optional, and
533 * may be {@code null} if unknown or unbounded.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700534 * <p>
535 * Type: INTEGER (long)
536 */
537 public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
538
539 /**
Tomasz Mikolajewski3f78e172015-06-25 16:17:26 +0900540 * Capacity of a root in bytes. This column is optional, and may be
541 * {@code null} if unknown or unbounded.
Tomasz Mikolajewski3f78e172015-06-25 16:17:26 +0900542 * <p>
543 * Type: INTEGER (long)
544 */
545 public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes";
546
547 /**
Jeff Sharkey6efba222013-09-27 16:44:11 -0700548 * MIME types supported by this root. This column is optional, and if
549 * {@code null} the root is assumed to support all MIME types. Multiple
550 * MIME types can be separated by a newline. For example, a root
551 * supporting audio might return "audio/*\napplication/x-flac".
Jeff Sharkey923396b2013-09-05 13:55:35 -0700552 * <p>
Jeff Sharkey6efba222013-09-27 16:44:11 -0700553 * Type: STRING
Jeff Sharkey923396b2013-09-05 13:55:35 -0700554 */
555 public static final String COLUMN_MIME_TYPES = "mime_types";
556
Garfield Tana7e852e2017-02-21 12:34:08 -0800557 /**
558 * MIME type for a root.
559 */
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700560 public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
561
Jeff Sharkey923396b2013-09-05 13:55:35 -0700562 /**
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700563 * Flag indicating that at least one directory under this root supports
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700564 * creating content. Roots with this flag will be shown when an
565 * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700566 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700567 * @see #COLUMN_FLAGS
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700568 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700569 public static final int FLAG_SUPPORTS_CREATE = 1;
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700570
571 /**
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700572 * Flag indicating that this root offers content that is strictly local
573 * on the device. That is, no network requests are made for the content.
574 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700575 * @see #COLUMN_FLAGS
576 * @see Intent#EXTRA_LOCAL_ONLY
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700577 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700578 public static final int FLAG_LOCAL_ONLY = 1 << 1;
579
Jeff Sharkey20d96d82013-07-30 17:08:39 -0700580 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700581 * Flag indicating that this root can be queried to provide recently
582 * modified documents.
Jeff Sharkey251097b2013-09-02 15:07:28 -0700583 *
584 * @see #COLUMN_FLAGS
585 * @see DocumentsContract#buildRecentDocumentsUri(String, String)
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700586 * @see DocumentsProvider#queryRecentDocuments(String, String[])
Jeff Sharkey251097b2013-09-02 15:07:28 -0700587 */
Jeff Sharkey6efba222013-09-27 16:44:11 -0700588 public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700589
590 /**
591 * Flag indicating that this root supports search.
592 *
593 * @see #COLUMN_FLAGS
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700594 * @see DocumentsContract#buildSearchDocumentsUri(String, String,
595 * String)
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700596 * @see DocumentsProvider#querySearchDocuments(String, String,
597 * String[])
598 */
Jeff Sharkey6efba222013-09-27 16:44:11 -0700599 public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700600
601 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700602 * Flag indicating that this root supports testing parent child
603 * relationships.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700604 *
605 * @see #COLUMN_FLAGS
606 * @see DocumentsProvider#isChildDocument(String, String)
607 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700608 public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4;
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700609
610 /**
Garfield Tan87877032017-03-22 12:01:14 -0700611 * Flag indicating that this root can be ejected.
612 *
613 * @see #COLUMN_FLAGS
614 * @see DocumentsContract#ejectRoot(ContentResolver, Uri)
615 * @see DocumentsProvider#ejectRoot(String)
616 */
617 public static final int FLAG_SUPPORTS_EJECT = 1 << 5;
618
619 /**
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700620 * Flag indicating that this root is currently empty. This may be used
621 * to hide the root when opening documents, but the root will still be
622 * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
Jeff Sharkey6efba222013-09-27 16:44:11 -0700623 * also set. If the value of this flag changes, such as when a root
624 * becomes non-empty, you must send a content changed notification for
625 * {@link DocumentsContract#buildRootsUri(String)}.
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700626 *
627 * @see #COLUMN_FLAGS
Jeff Sharkey6efba222013-09-27 16:44:11 -0700628 * @see ContentResolver#notifyChange(Uri,
629 * android.database.ContentObserver, boolean)
630 * @hide
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700631 */
Jeff Sharkey6efba222013-09-27 16:44:11 -0700632 public static final int FLAG_EMPTY = 1 << 16;
633
634 /**
Aga Wronska1719b352016-03-21 11:28:03 -0700635 * Flag indicating that this root should only be visible to advanced
636 * users.
637 *
638 * @see #COLUMN_FLAGS
639 * @hide
640 */
Mathew Inwoodba503112018-08-10 09:37:35 +0100641 @UnsupportedAppUsage
Aga Wronska1719b352016-03-21 11:28:03 -0700642 public static final int FLAG_ADVANCED = 1 << 17;
643
644 /**
Jeff Sharkey1407d4c2015-04-12 21:52:24 -0700645 * Flag indicating that this root has settings.
646 *
647 * @see #COLUMN_FLAGS
648 * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS
649 * @hide
650 */
Aga Wronska1719b352016-03-21 11:28:03 -0700651 public static final int FLAG_HAS_SETTINGS = 1 << 18;
Steve McKayba23e542016-03-02 15:15:00 -0800652
653 /**
654 * Flag indicating that this root is on removable SD card storage.
655 *
656 * @see #COLUMN_FLAGS
657 * @hide
658 */
Aga Wronska1719b352016-03-21 11:28:03 -0700659 public static final int FLAG_REMOVABLE_SD = 1 << 19;
Steve McKayba23e542016-03-02 15:15:00 -0800660
661 /**
662 * Flag indicating that this root is on removable USB storage.
663 *
664 * @see #COLUMN_FLAGS
665 * @hide
666 */
Aga Wronska1719b352016-03-21 11:28:03 -0700667 public static final int FLAG_REMOVABLE_USB = 1 << 20;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700668 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700669
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700670 /**
671 * Optional boolean flag included in a directory {@link Cursor#getExtras()}
672 * indicating that a document provider is still loading data. For example, a
673 * provider has returned some results, but is still waiting on an
674 * outstanding network request. The provider must send a content changed
675 * notification when loading is finished.
676 *
677 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
678 * boolean)
679 */
680 public static final String EXTRA_LOADING = "loading";
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700681
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700682 /**
683 * Optional string included in a directory {@link Cursor#getExtras()}
684 * providing an informational message that should be shown to a user. For
685 * example, a provider may wish to indicate that not all documents are
686 * available.
687 */
688 public static final String EXTRA_INFO = "info";
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700689
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700690 /**
691 * Optional string included in a directory {@link Cursor#getExtras()}
692 * providing an error message that should be shown to a user. For example, a
693 * provider may wish to indicate that a network error occurred. The user may
694 * choose to retry, resulting in a new query.
695 */
696 public static final String EXTRA_ERROR = "error";
697
Steve McKayd3afdee2015-11-19 17:27:12 -0800698 /**
699 * Optional result (I'm thinking boolean) answer to a question.
700 * {@hide}
701 */
702 public static final String EXTRA_RESULT = "result";
703
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700704 /** {@hide} */
Mathew Inwoodba503112018-08-10 09:37:35 +0100705 @UnsupportedAppUsage
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700706 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";
Mathew Inwoodba503112018-08-10 09:37:35 +0100741 @UnsupportedAppUsage
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700742 private static final String PATH_DOCUMENT = "document";
743 private static final String PATH_CHILDREN = "children";
744 private static final String PATH_SEARCH = "search";
Felipe Leme23a0c7a2018-01-24 08:43:34 -0800745 // TODO(b/72055774): make private again once ScopedAccessProvider is refactored
746 /** {@hide} */
Mathew Inwoodba503112018-08-10 09:37:35 +0100747 @UnsupportedAppUsage
Felipe Leme23a0c7a2018-01-24 08:43:34 -0800748 public static final String PATH_TREE = "tree";
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700749
750 private static final String PARAM_QUERY = "query";
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700751 private static final String PARAM_MANAGE = "manage";
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700752
753 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700754 * Build URI representing the roots of a document provider. When queried, a
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700755 * provider will return one or more rows with columns defined by
756 * {@link Root}.
757 *
758 * @see DocumentsProvider#queryRoots(String[])
759 */
760 public static Uri buildRootsUri(String authority) {
761 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
762 .authority(authority).appendPath(PATH_ROOT).build();
763 }
764
765 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700766 * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700767 * document provider.
768 *
769 * @see #getRootId(Uri)
770 */
771 public static Uri buildRootUri(String authority, String rootId) {
772 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
773 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build();
774 }
775
776 /**
Steve McKayb67bfbf2015-12-08 17:02:03 -0800777 * Builds URI for user home directory on external (local) storage.
778 * {@hide}
779 */
780 public static Uri buildHomeUri() {
781 // TODO: Avoid this type of interpackage copying. Added here to avoid
782 // direct coupling, but not ideal.
Garfield Tan92b96ba2016-11-01 14:33:48 -0700783 return DocumentsContract.buildRootUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "home");
Steve McKayb67bfbf2015-12-08 17:02:03 -0800784 }
785
786 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700787 * Build URI representing the recently modified documents of a specific root
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700788 * in a document provider. When queried, a provider will return zero or more
789 * rows with columns defined by {@link Document}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700790 *
791 * @see DocumentsProvider#queryRecentDocuments(String, String[])
792 * @see #getRootId(Uri)
793 */
794 public static Uri buildRecentDocumentsUri(String authority, String rootId) {
795 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
796 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
797 .appendPath(PATH_RECENT).build();
798 }
799
800 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700801 * Build URI representing access to descendant documents of the given
802 * {@link Document#COLUMN_DOCUMENT_ID}.
803 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700804 * @see #getTreeDocumentId(Uri)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700805 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700806 public static Uri buildTreeDocumentUri(String authority, String documentId) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700807 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700808 .appendPath(PATH_TREE).appendPath(documentId).build();
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700809 }
810
811 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700812 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
813 * a document provider. When queried, a provider will return a single row
814 * with columns defined by {@link Document}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700815 *
816 * @see DocumentsProvider#queryDocument(String, String[])
817 * @see #getDocumentId(Uri)
818 */
819 public static Uri buildDocumentUri(String authority, String documentId) {
820 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
821 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
822 }
823
824 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700825 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
826 * a document provider. When queried, a provider will return a single row
827 * with columns defined by {@link Document}.
828 * <p>
829 * However, instead of directly accessing the target document, the returned
830 * URI will leverage access granted through a subtree URI, typically
831 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document
832 * must be a descendant (child, grandchild, etc) of the subtree.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700833 * <p>
834 * This is typically used to access documents under a user-selected
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700835 * directory tree, since it doesn't require the user to separately confirm
836 * each new document access.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700837 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700838 * @param treeUri the subtree to leverage to gain access to the target
839 * document. The target directory must be a descendant of this
840 * subtree.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700841 * @param documentId the target document, which the caller may not have
842 * direct access to.
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700843 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700844 * @see DocumentsProvider#isChildDocument(String, String)
845 * @see #buildDocumentUri(String, String)
846 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700847 public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700848 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700849 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
850 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700851 .appendPath(documentId).build();
852 }
853
854 /** {@hide} */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700855 public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) {
856 if (isTreeUri(baseUri)) {
857 return buildDocumentUriUsingTree(baseUri, documentId);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700858 } else {
859 return buildDocumentUri(baseUri.getAuthority(), documentId);
860 }
861 }
862
863 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700864 * Build URI representing the children of the target directory in a document
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700865 * provider. When queried, a provider will return zero or more rows with
866 * columns defined by {@link Document}.
867 *
868 * @param parentDocumentId the document to return children for, which must
869 * be a directory with MIME type of
870 * {@link Document#MIME_TYPE_DIR}.
871 * @see DocumentsProvider#queryChildDocuments(String, String[], String)
872 * @see #getDocumentId(Uri)
873 */
874 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
875 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
876 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
877 .build();
878 }
879
880 /**
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700881 * Build URI representing the children of the target directory in a document
882 * provider. When queried, a provider will return zero or more rows with
883 * columns defined by {@link Document}.
884 * <p>
885 * However, instead of directly accessing the target directory, the returned
886 * URI will leverage access granted through a subtree URI, typically
887 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target
888 * directory must be a descendant (child, grandchild, etc) of the subtree.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700889 * <p>
890 * This is typically used to access documents under a user-selected
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700891 * directory tree, since it doesn't require the user to separately confirm
892 * each new document access.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700893 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700894 * @param treeUri the subtree to leverage to gain access to the target
895 * document. The target directory must be a descendant of this
896 * subtree.
897 * @param parentDocumentId the document to return children for, which the
898 * caller may not have direct access to, and which must be a
899 * directory with MIME type of {@link Document#MIME_TYPE_DIR}.
900 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700901 * @see DocumentsProvider#isChildDocument(String, String)
902 * @see #buildChildDocumentsUri(String, String)
903 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700904 public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700905 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700906 .authority(treeUri.getAuthority()).appendPath(PATH_TREE)
907 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700908 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build();
909 }
910
911 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700912 * Build URI representing a search for matching documents under a specific
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700913 * root in a document provider. When queried, a provider will return zero or
914 * more rows with columns defined by {@link Document}.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700915 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700916 * @see DocumentsProvider#querySearchDocuments(String, String, String[])
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700917 * @see #getRootId(Uri)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700918 * @see #getSearchDocumentsQuery(Uri)
919 */
920 public static Uri buildSearchDocumentsUri(
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700921 String authority, String rootId, String query) {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700922 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700923 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700924 .appendQueryParameter(PARAM_QUERY, query).build();
925 }
926
927 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700928 * Test if the given URI represents a {@link Document} backed by a
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700929 * {@link DocumentsProvider}.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700930 *
931 * @see #buildDocumentUri(String, String)
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700932 * @see #buildDocumentUriUsingTree(Uri, String)
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700933 */
Steve McKay323ee3e2015-09-25 16:02:56 -0700934 public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
935 if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
936 final List<String> paths = uri.getPathSegments();
937 if (paths.size() == 2) {
938 return PATH_DOCUMENT.equals(paths.get(0));
939 } else if (paths.size() == 4) {
940 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
941 }
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700942 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700943 return false;
944 }
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700945
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700946 /** {@hide} */
Steve McKay323ee3e2015-09-25 16:02:56 -0700947 public static boolean isRootUri(Context context, @Nullable Uri uri) {
948 if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
949 final List<String> paths = uri.getPathSegments();
950 return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
951 }
952 return false;
953 }
954
955 /** {@hide} */
956 public static boolean isContentUri(@Nullable Uri uri) {
957 return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
958 }
959
Tomasz Mikolajewski7db9c812016-01-28 09:50:01 +0900960 /**
961 * Test if the given URI represents a {@link Document} tree.
962 *
963 * @see #buildTreeDocumentUri(String, String)
Tomasz Mikolajewski90a75332016-07-13 10:14:59 +0900964 * @see #getTreeDocumentId(Uri)
Tomasz Mikolajewski7db9c812016-01-28 09:50:01 +0900965 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700966 public static boolean isTreeUri(Uri uri) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700967 final List<String> paths = uri.getPathSegments();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700968 return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700969 }
970
971 private static boolean isDocumentsProvider(Context context, String authority) {
Jeff Sharkeyd2e1e812013-10-09 13:31:13 -0700972 final Intent intent = new Intent(PROVIDER_INTERFACE);
973 final List<ResolveInfo> infos = context.getPackageManager()
974 .queryIntentContentProviders(intent, 0);
975 for (ResolveInfo info : infos) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700976 if (authority.equals(info.providerInfo.authority)) {
Jeff Sharkeyd2e1e812013-10-09 13:31:13 -0700977 return true;
978 }
Jeff Sharkeyee2f7df2013-09-26 11:32:30 -0700979 }
980 return false;
981 }
982
983 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700984 * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700985 */
986 public static String getRootId(Uri rootUri) {
987 final List<String> paths = rootUri.getPathSegments();
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700988 if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) {
989 return paths.get(1);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700990 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700991 throw new IllegalArgumentException("Invalid URI: " + rootUri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700992 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700993
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700994 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700995 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700996 *
997 * @see #isDocumentUri(Context, Uri)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700998 */
999 public static String getDocumentId(Uri documentUri) {
1000 final List<String> paths = documentUri.getPathSegments();
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001001 if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) {
1002 return paths.get(1);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001003 }
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001004 if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0))
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001005 && PATH_DOCUMENT.equals(paths.get(2))) {
1006 return paths.get(3);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001007 }
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001008 throw new IllegalArgumentException("Invalid URI: " + documentUri);
1009 }
1010
1011 /**
1012 * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001013 */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001014 public static String getTreeDocumentId(Uri documentUri) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001015 final List<String> paths = documentUri.getPathSegments();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001016 if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001017 return paths.get(1);
1018 }
1019 throw new IllegalArgumentException("Invalid URI: " + documentUri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001020 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001021
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001022 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001023 * Extract the search query from a URI built by
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001024 * {@link #buildSearchDocumentsUri(String, String, String)}.
1025 */
1026 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
1027 return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
Jeff Sharkey20d96d82013-07-30 17:08:39 -07001028 }
1029
Jeff Sharkey4ec97392013-09-10 12:04:26 -07001030 /** {@hide} */
Mathew Inwoodba503112018-08-10 09:37:35 +01001031 @UnsupportedAppUsage
Jeff Sharkey4ec97392013-09-10 12:04:26 -07001032 public static Uri setManageMode(Uri uri) {
1033 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
1034 }
1035
1036 /** {@hide} */
1037 public static boolean isManageMode(Uri uri) {
1038 return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
1039 }
1040
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001041 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001042 * Return thumbnail representing the document at the given URI. Callers are
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001043 * responsible for their own in-memory caching.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001044 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001045 * @param documentUri document to return thumbnail for, which must have
1046 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
1047 * @param size optimal thumbnail size desired. A provider may return a
1048 * thumbnail of a different size, but never more than double the
1049 * requested size.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001050 * @param signal signal used to indicate if caller is no longer interested
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001051 * in the thumbnail.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001052 * @return decoded thumbnail, or {@code null} if problem was encountered.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001053 * @see DocumentsProvider#openDocumentThumbnail(String, Point,
1054 * android.os.CancellationSignal)
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001055 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001056 public static Bitmap getDocumentThumbnail(
Ben Lin8ea82002017-03-08 17:30:16 -08001057 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal)
1058 throws FileNotFoundException {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001059 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1060 documentUri.getAuthority());
1061 try {
1062 return getDocumentThumbnail(client, documentUri, size, signal);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001063 } catch (Exception e) {
Jeff Sharkey33819312013-10-29 11:56:37 -07001064 if (!(e instanceof OperationCanceledException)) {
1065 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
1066 }
Ben Lin8ea82002017-03-08 17:30:16 -08001067 rethrowIfNecessary(resolver, e);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001068 return null;
1069 } finally {
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001070 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001071 }
1072 }
1073
1074 /** {@hide} */
Mathew Inwoodba503112018-08-10 09:37:35 +01001075 @UnsupportedAppUsage
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001076 public static Bitmap getDocumentThumbnail(
1077 ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001078 throws RemoteException, IOException {
Jeff Sharkey63983432013-08-21 11:33:50 -07001079 final Bundle openOpts = new Bundle();
Jeff Sharkey5b836f22014-08-27 14:46:32 -07001080 openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size);
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001081
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001082 AssetFileDescriptor afd = null;
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001083 Bitmap bitmap = null;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001084 try {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001085 afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001086
1087 final FileDescriptor fd = afd.getFileDescriptor();
Jeff Sharkey63983432013-08-21 11:33:50 -07001088 final long offset = afd.getStartOffset();
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001089
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001090 // Try seeking on the returned FD, since it gives us the most
1091 // optimal decode path; otherwise fall back to buffering.
1092 BufferedInputStream is = null;
1093 try {
Elliott Hughes34385d32014-04-28 11:11:32 -07001094 Os.lseek(fd, offset, SEEK_SET);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001095 } catch (ErrnoException e) {
1096 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE);
1097 is.mark(THUMBNAIL_BUFFER_SIZE);
Jeff Sharkey63983432013-08-21 11:33:50 -07001098 }
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001099
Jeff Sharkey63983432013-08-21 11:33:50 -07001100 // We requested a rough thumbnail size, but the remote size may have
1101 // returned something giant, so defensively scale down as needed.
1102 final BitmapFactory.Options opts = new BitmapFactory.Options();
1103 opts.inJustDecodeBounds = true;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001104 if (is != null) {
1105 BitmapFactory.decodeStream(is, null, opts);
Jeff Sharkey63983432013-08-21 11:33:50 -07001106 } else {
1107 BitmapFactory.decodeFileDescriptor(fd, null, opts);
1108 }
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001109
Jeff Sharkey63983432013-08-21 11:33:50 -07001110 final int widthSample = opts.outWidth / size.x;
1111 final int heightSample = opts.outHeight / size.y;
1112
1113 opts.inJustDecodeBounds = false;
1114 opts.inSampleSize = Math.min(widthSample, heightSample);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001115 if (is != null) {
1116 is.reset();
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001117 bitmap = BitmapFactory.decodeStream(is, null, opts);
Jeff Sharkey63983432013-08-21 11:33:50 -07001118 } else {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001119 try {
Elliott Hughes34385d32014-04-28 11:11:32 -07001120 Os.lseek(fd, offset, SEEK_SET);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001121 } catch (ErrnoException e) {
1122 e.rethrowAsIOException();
1123 }
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001124 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
1125 }
1126
1127 // Transform the bitmap if requested. We use a side-channel to
1128 // communicate the orientation, since EXIF thumbnails don't contain
1129 // the rotation flags of the original image.
1130 final Bundle extras = afd.getExtras();
1131 final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
1132 if (orientation != 0) {
1133 final int width = bitmap.getWidth();
1134 final int height = bitmap.getHeight();
1135
1136 final Matrix m = new Matrix();
1137 m.setRotate(orientation, width / 2, height / 2);
1138 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
Jeff Sharkey63983432013-08-21 11:33:50 -07001139 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001140 } finally {
Jeff Sharkey9d0843d2013-05-07 12:41:33 -07001141 IoUtils.closeQuietly(afd);
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001142 }
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001143
1144 return bitmap;
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001145 }
1146
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001147 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001148 * Create a new document with given MIME type and display name.
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001149 *
Ben Lin8ea82002017-03-08 17:30:16 -08001150 * @param parentDocumentUri directory with {@link Document#FLAG_DIR_SUPPORTS_CREATE}
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001151 * @param mimeType MIME type of new document
1152 * @param displayName name of new document
1153 * @return newly created document, or {@code null} if failed
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001154 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001155 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001156 String mimeType, String displayName) throws FileNotFoundException {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001157 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1158 parentDocumentUri.getAuthority());
1159 try {
1160 return createDocument(client, parentDocumentUri, mimeType, displayName);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001161 } catch (Exception e) {
1162 Log.w(TAG, "Failed to create document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001163 rethrowIfNecessary(resolver, e);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001164 return null;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001165 } finally {
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001166 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001167 }
1168 }
1169
1170 /** {@hide} */
1171 public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001172 String mimeType, String displayName) throws RemoteException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001173 final Bundle in = new Bundle();
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001174 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001175 in.putString(Document.COLUMN_MIME_TYPE, mimeType);
1176 in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001177
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001178 final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001179 return out.getParcelable(DocumentsContract.EXTRA_URI);
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001180 }
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001181
Steve McKayd3afdee2015-11-19 17:27:12 -08001182 /** {@hide} */
1183 public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
1184 Uri childDocumentUri) throws RemoteException {
1185
1186 final Bundle in = new Bundle();
1187 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
1188 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
1189
1190 final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
1191 if (out == null) {
1192 throw new RemoteException("Failed to get a reponse from isChildDocument query.");
1193 }
1194 if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
1195 throw new RemoteException("Response did not include result field..");
1196 }
1197 return out.getBoolean(DocumentsContract.EXTRA_RESULT);
1198 }
1199
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001200 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001201 * Change the display name of an existing document.
1202 * <p>
1203 * If the underlying provider needs to create a new
1204 * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display
1205 * name, that new document is returned and the original document is no
1206 * longer valid. Otherwise, the original document is returned.
1207 *
1208 * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME}
1209 * @param displayName updated name for document
1210 * @return the existing or new document after the rename, or {@code null} if
1211 * failed.
1212 */
1213 public static Uri renameDocument(ContentResolver resolver, Uri documentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001214 String displayName) throws FileNotFoundException {
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001215 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1216 documentUri.getAuthority());
1217 try {
1218 return renameDocument(client, documentUri, displayName);
1219 } catch (Exception e) {
1220 Log.w(TAG, "Failed to rename document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001221 rethrowIfNecessary(resolver, e);
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001222 return null;
1223 } finally {
1224 ContentProviderClient.releaseQuietly(client);
1225 }
1226 }
1227
1228 /** {@hide} */
1229 public static Uri renameDocument(ContentProviderClient client, Uri documentUri,
1230 String displayName) throws RemoteException {
1231 final Bundle in = new Bundle();
1232 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1233 in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
1234
1235 final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
1236 final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI);
1237 return (outUri != null) ? outUri : documentUri;
1238 }
1239
1240 /**
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001241 * Delete the given document.
1242 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001243 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001244 * @return if the document was deleted successfully.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001245 */
Ben Lin8ea82002017-03-08 17:30:16 -08001246 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri)
1247 throws FileNotFoundException {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -07001248 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1249 documentUri.getAuthority());
1250 try {
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001251 deleteDocument(client, documentUri);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001252 return true;
1253 } catch (Exception e) {
1254 Log.w(TAG, "Failed to delete document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001255 rethrowIfNecessary(resolver, e);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001256 return false;
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001257 } finally {
1258 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001259 }
Jeff Sharkey5b83f852013-08-14 18:29:19 -07001260 }
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001261
1262 /** {@hide} */
1263 public static void deleteDocument(ContentProviderClient client, Uri documentUri)
1264 throws RemoteException {
1265 final Bundle in = new Bundle();
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001266 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
Jeff Sharkey7aa76012013-09-30 14:26:27 -07001267
1268 client.call(METHOD_DELETE_DOCUMENT, null, in);
1269 }
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001270
1271 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001272 * Copies the given document.
1273 *
1274 * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
1275 * @param targetParentDocumentUri document which will become a parent of the source
1276 * document's copy.
1277 * @return the copied document, or {@code null} if failed.
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001278 */
1279 public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001280 Uri targetParentDocumentUri) throws FileNotFoundException {
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001281 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1282 sourceDocumentUri.getAuthority());
1283 try {
1284 return copyDocument(client, sourceDocumentUri, targetParentDocumentUri);
1285 } catch (Exception e) {
1286 Log.w(TAG, "Failed to copy document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001287 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -07001288 return null;
1289 } finally {
1290 ContentProviderClient.releaseQuietly(client);
1291 }
1292 }
1293
1294 /** {@hide} */
1295 public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri,
1296 Uri targetParentDocumentUri) throws RemoteException {
1297 final Bundle in = new Bundle();
1298 in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
1299 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1300
1301 final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in);
1302 return out.getParcelable(DocumentsContract.EXTRA_URI);
1303 }
1304
1305 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001306 * Moves the given document under a new parent.
1307 *
1308 * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001309 * @param sourceParentDocumentUri parent document of the document to move.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001310 * @param targetParentDocumentUri document which will become a new parent of the source
1311 * document.
1312 * @return the moved document, or {@code null} if failed.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001313 */
1314 public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001315 Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001316 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1317 sourceDocumentUri.getAuthority());
1318 try {
Tomasz Mikolajewskidcb4efe2016-02-10 19:14:49 +09001319 return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri,
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001320 targetParentDocumentUri);
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001321 } catch (Exception e) {
1322 Log.w(TAG, "Failed to move document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001323 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001324 return null;
1325 } finally {
1326 ContentProviderClient.releaseQuietly(client);
1327 }
1328 }
1329
1330 /** {@hide} */
Mathew Inwoodba503112018-08-10 09:37:35 +01001331 @UnsupportedAppUsage
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001332 public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001333 Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException {
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001334 final Bundle in = new Bundle();
1335 in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001336 in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri);
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001337 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
1338
1339 final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
1340 return out.getParcelable(DocumentsContract.EXTRA_URI);
1341 }
1342
1343 /**
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001344 * Removes the given document from a parent directory.
1345 *
1346 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
1347 * This method is especially useful if the document can be in multiple parents.
1348 *
1349 * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
1350 * @param parentDocumentUri parent document of the document to remove.
1351 * @return true if the document was removed successfully.
1352 */
1353 public static boolean removeDocument(ContentResolver resolver, Uri documentUri,
Ben Lin8ea82002017-03-08 17:30:16 -08001354 Uri parentDocumentUri) throws FileNotFoundException {
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001355 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1356 documentUri.getAuthority());
1357 try {
1358 removeDocument(client, documentUri, parentDocumentUri);
1359 return true;
1360 } catch (Exception e) {
1361 Log.w(TAG, "Failed to remove document", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001362 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001363 return false;
1364 } finally {
1365 ContentProviderClient.releaseQuietly(client);
1366 }
1367 }
1368
1369 /** {@hide} */
1370 public static void removeDocument(ContentProviderClient client, Uri documentUri,
1371 Uri parentDocumentUri) throws RemoteException {
1372 final Bundle in = new Bundle();
1373 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
1374 in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri);
1375
1376 client.call(METHOD_REMOVE_DOCUMENT, null, in);
1377 }
1378
Garfield Tan87877032017-03-22 12:01:14 -07001379 /**
1380 * Ejects the given root. It throws {@link IllegalStateException} when ejection failed.
1381 *
1382 * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected
1383 */
1384 public static void ejectRoot(ContentResolver resolver, Uri rootUri) {
Ben Line7822fb2016-06-24 15:21:08 -07001385 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1386 rootUri.getAuthority());
1387 try {
Garfield Tan87877032017-03-22 12:01:14 -07001388 ejectRoot(client, rootUri);
1389 } catch (RemoteException e) {
1390 e.rethrowAsRuntimeException();
Ben Line7822fb2016-06-24 15:21:08 -07001391 } finally {
1392 ContentProviderClient.releaseQuietly(client);
1393 }
1394 }
1395
1396 /** {@hide} */
Garfield Tan87877032017-03-22 12:01:14 -07001397 public static void ejectRoot(ContentProviderClient client, Uri rootUri)
Ben Line7822fb2016-06-24 15:21:08 -07001398 throws RemoteException {
1399 final Bundle in = new Bundle();
1400 in.putParcelable(DocumentsContract.EXTRA_URI, rootUri);
1401
Garfield Tan87877032017-03-22 12:01:14 -07001402 client.call(METHOD_EJECT_ROOT, null, in);
Ben Line7822fb2016-06-24 15:21:08 -07001403 }
1404
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001405 /**
Julian Mancinib6505152017-06-27 13:29:09 -07001406 * Returns metadata associated with the document. The type of metadata returned
Steve McKay17a9ce32017-07-27 13:37:14 -07001407 * is specific to the document type. For example the data returned for an image
1408 * file will likely consist primarily or soley of EXIF metadata.
Julian Mancinib6505152017-06-27 13:29:09 -07001409 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001410 * <p>The returned {@link Bundle} will contain zero or more entries depending
1411 * on the type of data supported by the document provider.
Julian Mancinib6505152017-06-27 13:29:09 -07001412 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001413 * <ol>
1414 * <li>A {@link DocumentsContract.METADATA_TYPES} containing a {@code String[]} value.
1415 * The string array identifies the type or types of metadata returned. Each
1416 * value in the can be used to access a {@link Bundle} of data
1417 * containing that type of data.
1418 * <li>An entry each for each type of returned metadata. Each set of metadata is
1419 * itself represented as a bundle and accessible via a string key naming
1420 * the type of data.
1421 * </ol>
Julian Mancinib6505152017-06-27 13:29:09 -07001422 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001423 * <p>Example:
1424 * <p><pre><code>
1425 * Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
1426 * if (metadata.containsKey(DocumentsContract.METADATA_EXIF)) {
1427 * Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF);
1428 * int imageLength = exif.getInt(ExifInterface.TAG_IMAGE_LENGTH);
1429 * }
Julian Mancinib6505152017-06-27 13:29:09 -07001430 * </code></pre>
1431 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001432 * @param documentUri a Document URI
1433 * @return a Bundle of Bundles.
Julian Mancinib6505152017-06-27 13:29:09 -07001434 * {@hide}
1435 */
Steve McKay17a9ce32017-07-27 13:37:14 -07001436 public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri)
Julian Mancinib6505152017-06-27 13:29:09 -07001437 throws FileNotFoundException {
1438 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1439 documentUri.getAuthority());
1440
1441 try {
Steve McKay17a9ce32017-07-27 13:37:14 -07001442 return getDocumentMetadata(client, documentUri);
Julian Mancinib6505152017-06-27 13:29:09 -07001443 } catch (Exception e) {
1444 Log.w(TAG, "Failed to get document metadata");
1445 rethrowIfNecessary(resolver, e);
1446 return null;
1447 } finally {
1448 ContentProviderClient.releaseQuietly(client);
1449 }
1450 }
1451
1452 /**
1453 * Returns metadata associated with the document. The type of metadata returned
Steve McKay17a9ce32017-07-27 13:37:14 -07001454 * is specific to the document type. For example the data returned for an image
1455 * file will likely consist primarily or soley of EXIF metadata.
Julian Mancinib6505152017-06-27 13:29:09 -07001456 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001457 * <p>The returned {@link Bundle} will contain zero or more entries depending
1458 * on the type of data supported by the document provider.
Julian Mancinib6505152017-06-27 13:29:09 -07001459 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001460 * <ol>
1461 * <li>A {@link DocumentsContract.METADATA_TYPES} containing a {@code String[]} value.
1462 * The string array identifies the type or types of metadata returned. Each
1463 * value in the can be used to access a {@link Bundle} of data
1464 * containing that type of data.
1465 * <li>An entry each for each type of returned metadata. Each set of metadata is
1466 * itself represented as a bundle and accessible via a string key naming
1467 * the type of data.
1468 * </ol>
Julian Mancinib6505152017-06-27 13:29:09 -07001469 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001470 * <p>Example:
1471 * <p><pre><code>
Julian Mancinib6505152017-06-27 13:29:09 -07001472 * Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags);
Steve McKay17a9ce32017-07-27 13:37:14 -07001473 * if (metadata.containsKey(DocumentsContract.METADATA_EXIF)) {
1474 * Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF);
1475 * int imageLength = exif.getInt(ExifInterface.TAG_IMAGE_LENGTH);
1476 * }
Julian Mancinib6505152017-06-27 13:29:09 -07001477 * </code></pre>
1478 *
Steve McKay17a9ce32017-07-27 13:37:14 -07001479 * @param documentUri a Document URI
1480 * @return a Bundle of Bundles.
Julian Mancinib6505152017-06-27 13:29:09 -07001481 * {@hide}
1482 */
Steve McKay17a9ce32017-07-27 13:37:14 -07001483 public static Bundle getDocumentMetadata(
1484 ContentProviderClient client, Uri documentUri) throws RemoteException {
Julian Mancinib6505152017-06-27 13:29:09 -07001485 final Bundle in = new Bundle();
1486 in.putParcelable(EXTRA_URI, documentUri);
Julian Mancinib6505152017-06-27 13:29:09 -07001487
1488 final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in);
1489
1490 if (out == null) {
1491 throw new RemoteException("Failed to get a response from getDocumentMetadata");
1492 }
1493 return out;
1494 }
1495
1496 /**
Garfield Tanb690b4d2017-03-01 16:05:23 -08001497 * Finds the canonical path from the top of the document tree.
Garfield Tanaba97f32016-10-06 17:34:19 +00001498 *
Garfield Tanb690b4d2017-03-01 16:05:23 -08001499 * The {@link Path#getPath()} of the return value contains the document ID
1500 * of all documents along the path from the top the document tree to the
1501 * requested document, both inclusive.
1502 *
1503 * The {@link Path#getRootId()} of the return value returns {@code null}.
Garfield Tan06940e12016-10-07 16:03:17 -07001504 *
1505 * @param treeUri treeUri of the document which path is requested.
Garfield Tanb690b4d2017-03-01 16:05:23 -08001506 * @return the path of the document, or {@code null} if failed.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001507 * @see DocumentsProvider#findDocumentPath(String, String)
Garfield Tanaba97f32016-10-06 17:34:19 +00001508 */
Ben Lin8ea82002017-03-08 17:30:16 -08001509 public static Path findDocumentPath(ContentResolver resolver, Uri treeUri)
1510 throws FileNotFoundException {
Garfield Tan06940e12016-10-07 16:03:17 -07001511 checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
1512
Garfield Tanaba97f32016-10-06 17:34:19 +00001513 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
Garfield Tan06940e12016-10-07 16:03:17 -07001514 treeUri.getAuthority());
Garfield Tanaba97f32016-10-06 17:34:19 +00001515 try {
Garfield Tanb690b4d2017-03-01 16:05:23 -08001516 return findDocumentPath(client, treeUri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001517 } catch (Exception e) {
1518 Log.w(TAG, "Failed to find path", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001519 rethrowIfNecessary(resolver, e);
Garfield Tanaba97f32016-10-06 17:34:19 +00001520 return null;
1521 } finally {
1522 ContentProviderClient.releaseQuietly(client);
1523 }
1524 }
1525
Garfield Tan06940e12016-10-07 16:03:17 -07001526 /**
Garfield Tanb690b4d2017-03-01 16:05:23 -08001527 * Finds the canonical path. If uri is a document uri returns path from a root and
1528 * its associated root id. If uri is a tree uri returns the path from the top of
1529 * the tree. The {@link Path#getPath()} of the return value contains document ID
1530 * starts from the top of the tree or the root document to the requested document,
1531 * both inclusive.
Garfield Tan06940e12016-10-07 16:03:17 -07001532 *
Garfield Tanb690b4d2017-03-01 16:05:23 -08001533 * Callers can expect the root ID returned from multiple calls to this method is
1534 * consistent.
Garfield Tan06940e12016-10-07 16:03:17 -07001535 *
1536 * @param uri uri of the document which path is requested. It can be either a
1537 * plain document uri or a tree uri.
1538 * @return the path of the document.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001539 * @see DocumentsProvider#findDocumentPath(String, String)
Garfield Tan06940e12016-10-07 16:03:17 -07001540 *
1541 * {@hide}
1542 */
Garfield Tan0b3cf662016-10-31 12:59:45 -07001543 public static Path findDocumentPath(ContentProviderClient client, Uri uri)
1544 throws RemoteException {
1545
Garfield Tanaba97f32016-10-06 17:34:19 +00001546 final Bundle in = new Bundle();
Garfield Tan06940e12016-10-07 16:03:17 -07001547 in.putParcelable(DocumentsContract.EXTRA_URI, uri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001548
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001549 final Bundle out = client.call(METHOD_FIND_DOCUMENT_PATH, null, in);
Garfield Tanaba97f32016-10-06 17:34:19 +00001550
1551 return out.getParcelable(DocumentsContract.EXTRA_RESULT);
1552 }
1553
1554 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001555 * Creates an intent for obtaining a web link for the specified document.
1556 *
1557 * <p>Note, that due to internal limitations, if there is already a web link
1558 * intent created for the specified document but with different options,
koprivadebd4ee2018-09-13 10:59:46 -07001559 * then it may be overridden.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001560 *
1561 * <p>Providers are required to show confirmation UI for all new permissions granted
1562 * for the linked document.
1563 *
Tomasz Mikolajewski463b6862017-03-22 17:34:05 +09001564 * <p>If list of recipients is known, then it should be passed in options as
1565 * {@link Intent#EXTRA_EMAIL} as a list of email addresses. Note, that
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001566 * this is just a hint for the provider, which can ignore the list. In either
1567 * case the provider is required to show a UI for letting the user confirm
1568 * any new permission grants.
1569 *
Tomasz Mikolajewski463b6862017-03-22 17:34:05 +09001570 * <p>Note, that the entire <code>options</code> bundle will be sent to the provider
1571 * backing the passed <code>uri</code>. Make sure that you trust the provider
1572 * before passing any sensitive information.
Tomasz Mikolajewskif1d55402017-03-03 15:09:40 +09001573 *
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001574 * <p>Since this API may show a UI, it cannot be called from background.
1575 *
1576 * <p>In order to obtain the Web Link use code like this:
1577 * <pre><code>
1578 * void onSomethingHappened() {
1579 * IntentSender sender = DocumentsContract.createWebLinkIntent(<i>...</i>);
1580 * if (sender != null) {
1581 * startIntentSenderForResult(
Tomasz Mikolajewskif1d55402017-03-03 15:09:40 +09001582 * sender,
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001583 * WEB_LINK_REQUEST_CODE,
1584 * null, 0, 0, 0, null);
1585 * }
1586 * }
1587 *
1588 * <i>(...)</i>
1589 *
1590 * void onActivityResult(int requestCode, int resultCode, Intent data) {
1591 * if (requestCode == WEB_LINK_REQUEST_CODE && resultCode == RESULT_OK) {
1592 * Uri weblinkUri = data.getData();
1593 * <i>...</i>
1594 * }
1595 * }
1596 * </code></pre>
1597 *
1598 * @param uri uri for the document to create a link to.
1599 * @param options Extra information for generating the link.
1600 * @return an intent sender to obtain the web link, or null if the document
1601 * is not linkable, or creating the intent sender failed.
1602 * @see DocumentsProvider#createWebLinkIntent(String, Bundle)
1603 * @see Intent#EXTRA_EMAIL
1604 */
1605 public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri,
Ben Lin8ea82002017-03-08 17:30:16 -08001606 Bundle options) throws FileNotFoundException {
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001607 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
1608 uri.getAuthority());
1609 try {
1610 return createWebLinkIntent(client, uri, options);
1611 } catch (Exception e) {
1612 Log.w(TAG, "Failed to create a web link intent", e);
Ben Lin8ea82002017-03-08 17:30:16 -08001613 rethrowIfNecessary(resolver, e);
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001614 return null;
1615 } finally {
1616 ContentProviderClient.releaseQuietly(client);
1617 }
1618 }
1619
1620 /**
1621 * {@hide}
1622 */
1623 public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri,
1624 Bundle options) throws RemoteException {
1625 final Bundle in = new Bundle();
1626 in.putParcelable(DocumentsContract.EXTRA_URI, uri);
1627
1628 // Options may be provider specific, so put them in a separate bundle to
1629 // avoid overriding the Uri.
1630 if (options != null) {
1631 in.putBundle(EXTRA_OPTIONS, options);
1632 }
1633
1634 final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, null, in);
1635 return out.getParcelable(DocumentsContract.EXTRA_RESULT);
1636 }
1637
1638 /**
Jeff Sharkeyc1c8f3f2013-10-14 14:57:33 -07001639 * Open the given image for thumbnail purposes, using any embedded EXIF
1640 * thumbnail if available, and providing orientation hints from the parent
1641 * image.
1642 *
1643 * @hide
1644 */
1645 public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
1646 final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
1647 file, ParcelFileDescriptor.MODE_READ_ONLY);
1648 Bundle extras = null;
1649
1650 try {
1651 final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
1652
1653 switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
1654 case ExifInterface.ORIENTATION_ROTATE_90:
1655 extras = new Bundle(1);
1656 extras.putInt(EXTRA_ORIENTATION, 90);
1657 break;
1658 case ExifInterface.ORIENTATION_ROTATE_180:
1659 extras = new Bundle(1);
1660 extras.putInt(EXTRA_ORIENTATION, 180);
1661 break;
1662 case ExifInterface.ORIENTATION_ROTATE_270:
1663 extras = new Bundle(1);
1664 extras.putInt(EXTRA_ORIENTATION, 270);
1665 break;
1666 }
1667
1668 final long[] thumb = exif.getThumbnailRange();
1669 if (thumb != null) {
1670 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
1671 }
1672 } catch (IOException e) {
1673 }
1674
1675 return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
1676 }
Garfield Tanaba97f32016-10-06 17:34:19 +00001677
Ben Lin8ea82002017-03-08 17:30:16 -08001678 private static void rethrowIfNecessary(ContentResolver resolver, Exception e)
1679 throws FileNotFoundException {
1680 // We only want to throw applications targetting O and above
1681 if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
1682 if (e instanceof ParcelableException) {
1683 ((ParcelableException) e).maybeRethrow(FileNotFoundException.class);
Ben Lin227e6e82017-03-13 15:36:13 -07001684 } else if (e instanceof RemoteException) {
Ben Lin8ea82002017-03-08 17:30:16 -08001685 ((RemoteException) e).rethrowAsRuntimeException();
1686 } else if (e instanceof RuntimeException) {
1687 throw (RuntimeException) e;
1688 }
1689 }
1690 }
1691
Garfield Tanaba97f32016-10-06 17:34:19 +00001692 /**
Garfield Tan0b3cf662016-10-31 12:59:45 -07001693 * Holds a path from a document to a particular document under it. It
1694 * may also contains the root ID where the path resides.
Garfield Tanaba97f32016-10-06 17:34:19 +00001695 */
1696 public static final class Path implements Parcelable {
1697
Garfield Tan06940e12016-10-07 16:03:17 -07001698 private final @Nullable String mRootId;
1699 private final List<String> mPath;
Garfield Tanaba97f32016-10-06 17:34:19 +00001700
1701 /**
1702 * Creates a Path.
Garfield Tan06940e12016-10-07 16:03:17 -07001703 *
1704 * @param rootId the ID of the root. May be null.
Garfield Tan0b3cf662016-10-31 12:59:45 -07001705 * @param path the list of document ID from the parent document at
Garfield Tan06940e12016-10-07 16:03:17 -07001706 * position 0 to the child document.
Garfield Tanaba97f32016-10-06 17:34:19 +00001707 */
Garfield Tan5f214802016-10-26 14:52:46 -07001708 public Path(@Nullable String rootId, List<String> path) {
Garfield Tan06940e12016-10-07 16:03:17 -07001709 checkCollectionNotEmpty(path, "path");
1710 checkCollectionElementsNotNull(path, "path");
1711
Garfield Tanaba97f32016-10-06 17:34:19 +00001712 mRootId = rootId;
1713 mPath = path;
1714 }
1715
Garfield Tan06940e12016-10-07 16:03:17 -07001716 /**
1717 * Returns the root id or null if the calling package doesn't have
1718 * permission to access root information.
1719 */
1720 public @Nullable String getRootId() {
1721 return mRootId;
1722 }
1723
1724 /**
1725 * Returns the path. The path is trimmed to the top of tree if
1726 * calling package doesn't have permission to access those
1727 * documents.
1728 */
1729 public List<String> getPath() {
1730 return mPath;
1731 }
1732
1733 @Override
1734 public boolean equals(Object o) {
1735 if (this == o) {
1736 return true;
1737 }
1738 if (o == null || !(o instanceof Path)) {
1739 return false;
1740 }
1741 Path path = (Path) o;
1742 return Objects.equals(mRootId, path.mRootId) &&
1743 Objects.equals(mPath, path.mPath);
1744 }
1745
1746 @Override
1747 public int hashCode() {
1748 return Objects.hash(mRootId, mPath);
1749 }
1750
1751 @Override
1752 public String toString() {
1753 return new StringBuilder()
1754 .append("DocumentsContract.Path{")
1755 .append("rootId=")
1756 .append(mRootId)
1757 .append(", path=")
1758 .append(mPath)
1759 .append("}")
1760 .toString();
1761 }
1762
Garfield Tanaba97f32016-10-06 17:34:19 +00001763 @Override
1764 public void writeToParcel(Parcel dest, int flags) {
1765 dest.writeString(mRootId);
1766 dest.writeStringList(mPath);
1767 }
1768
1769 @Override
1770 public int describeContents() {
1771 return 0;
1772 }
1773
1774 public static final Creator<Path> CREATOR = new Creator<Path>() {
1775 @Override
1776 public Path createFromParcel(Parcel in) {
1777 final String rootId = in.readString();
1778 final List<String> path = in.createStringArrayList();
1779 return new Path(rootId, path);
1780 }
1781
1782 @Override
1783 public Path[] newArray(int size) {
1784 return new Path[size];
1785 }
1786 };
1787 }
Jeff Sharkey9ecfee02013-04-19 14:05:03 -07001788}