DocumentsContract.java revision 77797400ec103b1691e1c3fa602c329b49ac18ca
1c383a500aa59423264811be3874461bf8adbfea0Zonr Chang/* 2c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * Copyright (C) 2013 The Android Open Source Project 3c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * 4c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * Licensed under the Apache License, Version 2.0 (the "License"); 5c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * you may not use this file except in compliance with the License. 6c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * You may obtain a copy of the License at 7c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * 8c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * http://www.apache.org/licenses/LICENSE-2.0 9c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * 10c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * Unless required by applicable law or agreed to in writing, software 11c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * distributed under the License is distributed on an "AS IS" BASIS, 12c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * See the License for the specific language governing permissions and 14c383a500aa59423264811be3874461bf8adbfea0Zonr Chang * limitations under the License. 15c383a500aa59423264811be3874461bf8adbfea0Zonr Chang */ 16c383a500aa59423264811be3874461bf8adbfea0Zonr Chang 176315f76e3cc6ff2d012d1183a0b030d4ff0dc808zonrpackage android.provider; 18462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao 19e639eb5caa2c386b4a60659a4929e8a6141a2cbeStephen Hinesimport static android.net.TrafficStats.KB_IN_BYTES; 200a3f20ec28ed6f5ae1ed5d61f6b6e3e577f7f5d1Shih-wei Liaoimport static android.system.OsConstants.SEEK_SET; 210da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 229ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liaoimport android.content.ContentProviderClient; 23462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liaoimport android.content.ContentResolver; 2423c4358f12bd9d0ba7166eceebd683db95a41b3fStephen Hinesimport android.content.Context; 2523c4358f12bd9d0ba7166eceebd683db95a41b3fStephen Hinesimport android.content.Intent; 26e639eb5caa2c386b4a60659a4929e8a6141a2cbeStephen Hinesimport android.content.pm.ResolveInfo; 276e6578a360497f78a181e63d7783422a9c9bfb15Stephen Hinesimport android.content.res.AssetFileDescriptor; 286315f76e3cc6ff2d012d1183a0b030d4ff0dc808zonrimport android.database.Cursor; 296315f76e3cc6ff2d012d1183a0b030d4ff0dc808zonrimport android.graphics.Bitmap; 30e639eb5caa2c386b4a60659a4929e8a6141a2cbeStephen Hinesimport android.graphics.BitmapFactory; 31462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liaoimport android.graphics.Matrix; 325baf6324a97430016026419deaef246ad75430fcStephen Hinesimport android.graphics.Point; 335baf6324a97430016026419deaef246ad75430fcStephen Hinesimport android.media.ExifInterface; 345baf6324a97430016026419deaef246ad75430fcStephen Hinesimport android.net.Uri; 35d3f7527b105d21f1c69d3473eb88a762f2c3ab5aJean-Luc Brouilletimport android.os.Bundle; 365baf6324a97430016026419deaef246ad75430fcStephen Hinesimport android.os.CancellationSignal; 37d3f7527b105d21f1c69d3473eb88a762f2c3ab5aJean-Luc Brouilletimport android.os.OperationCanceledException; 385baf6324a97430016026419deaef246ad75430fcStephen Hinesimport android.os.ParcelFileDescriptor; 39ee4016d1247d3fbe50822de279d3da273d8aef4cTim Murrayimport android.os.ParcelFileDescriptor.OnCloseListener; 40d3f7527b105d21f1c69d3473eb88a762f2c3ab5aJean-Luc Brouilletimport android.os.RemoteException; 41d3f7527b105d21f1c69d3473eb88a762f2c3ab5aJean-Luc Brouilletimport android.system.ErrnoException; 42d3f7527b105d21f1c69d3473eb88a762f2c3ab5aJean-Luc Brouilletimport android.system.Os; 435baf6324a97430016026419deaef246ad75430fcStephen Hinesimport android.util.Log; 445baf6324a97430016026419deaef246ad75430fcStephen Hines 455baf6324a97430016026419deaef246ad75430fcStephen Hinesimport libcore.io.IoUtils; 465baf6324a97430016026419deaef246ad75430fcStephen Hines 475baf6324a97430016026419deaef246ad75430fcStephen Hinesimport java.io.BufferedInputStream; 485baf6324a97430016026419deaef246ad75430fcStephen Hinesimport java.io.File; 495baf6324a97430016026419deaef246ad75430fcStephen Hinesimport java.io.FileDescriptor; 509ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liaoimport java.io.FileInputStream; 519ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liaoimport java.io.FileNotFoundException; 529ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liaoimport java.io.IOException; 539ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liaoimport java.util.List; 54462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao 556e6578a360497f78a181e63d7783422a9c9bfb15Stephen Hines/** 56462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao * Defines the contract between a documents provider and the platform. 57d3f7527b105d21f1c69d3473eb88a762f2c3ab5aJean-Luc Brouillet * <p> 585abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes * To create a document provider, extend {@link DocumentsProvider}, which 595baf6324a97430016026419deaef246ad75430fcStephen Hines * provides a foundational implementation of this contract. 605baf6324a97430016026419deaef246ad75430fcStephen Hines * <p> 613fa286b4c2f110c6be2bbfac9c715bb1ec880338Shih-wei Liao * All client apps must hold a valid URI permission grant to access documents, 62462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao * typically issued when a user makes a selection through 630da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT}, 640da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. 655abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes * 660da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * @see DocumentsProvider 679e5b503349719144f63ccb7c62ee9c291a7d83b8Stephen Hines */ 680da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Changpublic final class DocumentsContract { 69eca0534a31b6185d6ab758f5e97acd7a4cb21e8eJean-Luc Brouillet private static final String TAG = "Documents"; 700da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 710da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang // content://com.example/root/ 729e5b503349719144f63ccb7c62ee9c291a7d83b8Stephen Hines // content://com.example/root/sdcard/ 739e5b503349719144f63ccb7c62ee9c291a7d83b8Stephen Hines // content://com.example/root/sdcard/recent/ 740da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang // content://com.example/root/sdcard/search/?query=pony 75cc887ba8fe42cca706caae636932ae6a61a30ed2Shih-wei Liao // content://com.example/document/12/ 769e5b503349719144f63ccb7c62ee9c291a7d83b8Stephen Hines // content://com.example/document/12/children/ 770da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang // content://com.example/tree/12/document/24/ 780da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang // content://com.example/tree/12/document/24/children/ 790da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 800da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang private DocumentsContract() { 810da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang } 820da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 830da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang /** 840da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Intent action used to identify {@link DocumentsProvider} instances. This 850da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * is used in the {@code <intent-filter>} of a {@code <provider>}. 860da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang */ 870da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; 880da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 899e5b503349719144f63ccb7c62ee9c291a7d83b8Stephen Hines /** {@hide} */ 900da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; 910da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 92cc887ba8fe42cca706caae636932ae6a61a30ed2Shih-wei Liao /** {@hide} */ 930da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED"; 940da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 955abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes /** 965abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes * Set this in a DocumentsUI intent to cause a package's own roots to be 97fd6ea6ad7074944a5f730d05241a97f43042b1b7Shih-wei Liao * excluded from the roots list. 9843730fe3c839af391efe6bdf56b0479860121924Shih-wei Liao */ 990da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; 1000da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 1010da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang /** 1020da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Included in {@link AssetFileDescriptor#getExtras()} when returned 1030da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * thumbnail should be rotated. 1049e5b503349719144f63ccb7c62ee9c291a7d83b8Stephen Hines * 1056e6578a360497f78a181e63d7783422a9c9bfb15Stephen Hines * @see MediaStore.Images.ImageColumns#ORIENTATION 1060da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * @hide 1070da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang */ 10813fad85b3c99a37c17d8acfec72f46b8ee64e912Stephen Hines public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION"; 1090da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 1105abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes /** {@hide} */ 1115baf6324a97430016026419deaef246ad75430fcStephen Hines public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT"; 1120da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang /** {@hide} */ 1135baf6324a97430016026419deaef246ad75430fcStephen Hines public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; 1145abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes 115462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao /** {@hide} */ 1160da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String 1176e6578a360497f78a181e63d7783422a9c9bfb15Stephen Hines ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT"; 1180da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 1190da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang /** {@hide} */ 1200da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String 1219ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liao ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; 122462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao 1239ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liao /** 124462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao * Buffer is large enough to rewind past any EXIF headers. 125462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao */ 1260da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES); 127ab992e59a36a18df49bf4878968ef0598299afd3Logan Chien 1285abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes /** 1290da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Constants related to a document, including {@link Cursor} column names 1300da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * and flags. 1310da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * <p> 132462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao * A document can be either an openable stream (with a specific MIME type), 1335abbe0e9ca2508260b627ffef2bf01e2554e8357Chris Wailes * or a directory containing additional documents (with the 1349ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liao * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a 1350da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * subtree containing zero or more documents, which can recursively contain 1360da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * even more documents and directories. 1370da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * <p> 1380da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * All columns are <em>read-only</em> to client applications. 1399ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liao */ 1400da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public final static class Document { 14123c4358f12bd9d0ba7166eceebd683db95a41b3fStephen Hines private Document() { 1429ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liao } 1430da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 1440da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang /** 1450da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Unique ID of a document. This ID is both provided by and interpreted 1460da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * by a {@link DocumentsProvider}, and should be treated as an opaque 1479ef2f785e0cc490af678dfd685995dec787321ffShih-wei Liao * value by client applications. This column is required. 148ab992e59a36a18df49bf4878968ef0598299afd3Logan Chien * <p> 149ab992e59a36a18df49bf4878968ef0598299afd3Logan Chien * Each document must have a unique ID within a provider, but that 1500da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * single document may be included as a child of multiple directories. 1510da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * <p> 1520da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * A provider must always return durable IDs, since they will be used to 1530da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * issue long-term URI permission grants when an application interacts 154462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao * with {@link Intent#ACTION_OPEN_DOCUMENT} and 1550da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * {@link Intent#ACTION_CREATE_DOCUMENT}. 1560da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * <p> 1570da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Type: STRING 158462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao */ 1590da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang public static final String COLUMN_DOCUMENT_ID = "document_id"; 1600da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang 1610da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang /** 1620da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Concrete MIME type of a document. For example, "image/png" or 163c95381a2c3b6e9117901eef0687e861e4d533bfeJean-Luc Brouillet * "application/pdf" for openable files. A document can also be a 16423c4358f12bd9d0ba7166eceebd683db95a41b3fStephen Hines * directory containing additional documents, which is represented with 1650da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * the {@link #MIME_TYPE_DIR} MIME type. This column is required. 1660da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * <p> 1670da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * Type: STRING 1680da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang * 1691f0d88fbff28e4e2dd563d93c8fe0047381c09ccShih-wei Liao * @see #MIME_TYPE_DIR 1700da0a7dc51c25943fe31d0bfccbdfee326a3199cZonr Chang */ 171462aefd62cc646d2ff753c1d003ef3cd7bbea26Shih-wei Liao public static final String COLUMN_MIME_TYPE = "mime_type"; 172e639eb5caa2c386b4a60659a4929e8a6141a2cbeStephen Hines 173e639eb5caa2c386b4a60659a4929e8a6141a2cbeStephen Hines /** 174 * Display name of a document, used as the primary title displayed to a 175 * user. This column is required. 176 * <p> 177 * Type: STRING 178 */ 179 public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; 180 181 /** 182 * Summary of a document, which may be shown to a user. This column is 183 * optional, and may be {@code null}. 184 * <p> 185 * Type: STRING 186 */ 187 public static final String COLUMN_SUMMARY = "summary"; 188 189 /** 190 * Timestamp when a document was last modified, in milliseconds since 191 * January 1, 1970 00:00:00.0 UTC. This column is required, and may be 192 * {@code null} if unknown. A {@link DocumentsProvider} can update this 193 * field using events from {@link OnCloseListener} or other reliable 194 * {@link ParcelFileDescriptor} transports. 195 * <p> 196 * Type: INTEGER (long) 197 * 198 * @see System#currentTimeMillis() 199 */ 200 public static final String COLUMN_LAST_MODIFIED = "last_modified"; 201 202 /** 203 * Specific icon resource ID for a document. This column is optional, 204 * and may be {@code null} to use a platform-provided default icon based 205 * on {@link #COLUMN_MIME_TYPE}. 206 * <p> 207 * Type: INTEGER (int) 208 */ 209 public static final String COLUMN_ICON = "icon"; 210 211 /** 212 * Flags that apply to a document. This column is required. 213 * <p> 214 * Type: INTEGER (int) 215 * 216 * @see #FLAG_SUPPORTS_WRITE 217 * @see #FLAG_SUPPORTS_DELETE 218 * @see #FLAG_SUPPORTS_THUMBNAIL 219 * @see #FLAG_DIR_PREFERS_GRID 220 * @see #FLAG_DIR_PREFERS_LAST_MODIFIED 221 */ 222 public static final String COLUMN_FLAGS = "flags"; 223 224 /** 225 * Size of a document, in bytes, or {@code null} if unknown. This column 226 * is required. 227 * <p> 228 * Type: INTEGER (long) 229 */ 230 public static final String COLUMN_SIZE = OpenableColumns.SIZE; 231 232 /** 233 * MIME type of a document which is a directory that may contain 234 * additional documents. 235 * 236 * @see #COLUMN_MIME_TYPE 237 */ 238 public static final String MIME_TYPE_DIR = "vnd.android.document/directory"; 239 240 /** 241 * Flag indicating that a document can be represented as a thumbnail. 242 * 243 * @see #COLUMN_FLAGS 244 * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri, 245 * Point, CancellationSignal) 246 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 247 * android.os.CancellationSignal) 248 */ 249 public static final int FLAG_SUPPORTS_THUMBNAIL = 1; 250 251 /** 252 * Flag indicating that a document supports writing. 253 * <p> 254 * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT}, 255 * the calling application is granted both 256 * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and 257 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual 258 * writability of a document may change over time, for example due to 259 * remote access changes. This flag indicates that a document client can 260 * expect {@link ContentResolver#openOutputStream(Uri)} to succeed. 261 * 262 * @see #COLUMN_FLAGS 263 */ 264 public static final int FLAG_SUPPORTS_WRITE = 1 << 1; 265 266 /** 267 * Flag indicating that a document is deletable. 268 * 269 * @see #COLUMN_FLAGS 270 * @see DocumentsContract#deleteDocument(ContentResolver, Uri) 271 * @see DocumentsProvider#deleteDocument(String) 272 */ 273 public static final int FLAG_SUPPORTS_DELETE = 1 << 2; 274 275 /** 276 * Flag indicating that a document is a directory that supports creation 277 * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is 278 * {@link #MIME_TYPE_DIR}. 279 * 280 * @see #COLUMN_FLAGS 281 * @see DocumentsProvider#createDocument(String, String, String) 282 */ 283 public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3; 284 285 /** 286 * Flag indicating that a directory prefers its contents be shown in a 287 * larger format grid. Usually suitable when a directory contains mostly 288 * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is 289 * {@link #MIME_TYPE_DIR}. 290 * 291 * @see #COLUMN_FLAGS 292 */ 293 public static final int FLAG_DIR_PREFERS_GRID = 1 << 4; 294 295 /** 296 * Flag indicating that a directory prefers its contents be sorted by 297 * {@link #COLUMN_LAST_MODIFIED}. Only valid when 298 * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. 299 * 300 * @see #COLUMN_FLAGS 301 */ 302 public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; 303 304 /** 305 * Flag indicating that a document can be renamed. 306 * 307 * @see #COLUMN_FLAGS 308 * @see DocumentsContract#renameDocument(ContentProviderClient, Uri, 309 * String) 310 * @see DocumentsProvider#renameDocument(String, String) 311 */ 312 public static final int FLAG_SUPPORTS_RENAME = 1 << 6; 313 314 /** 315 * Flag indicating that document titles should be hidden when viewing 316 * this directory in a larger format grid. For example, a directory 317 * containing only images may want the image thumbnails to speak for 318 * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is 319 * {@link #MIME_TYPE_DIR}. 320 * 321 * @see #COLUMN_FLAGS 322 * @see #FLAG_DIR_PREFERS_GRID 323 * @hide 324 */ 325 public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16; 326 } 327 328 /** 329 * Constants related to a root of documents, including {@link Cursor} column 330 * names and flags. A root is the start of a tree of documents, such as a 331 * physical storage device, or an account. Each root starts at the directory 332 * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively 333 * contain both documents and directories. 334 * <p> 335 * All columns are <em>read-only</em> to client applications. 336 */ 337 public final static class Root { 338 private Root() { 339 } 340 341 /** 342 * Unique ID of a root. This ID is both provided by and interpreted by a 343 * {@link DocumentsProvider}, and should be treated as an opaque value 344 * by client applications. This column is required. 345 * <p> 346 * Type: STRING 347 */ 348 public static final String COLUMN_ROOT_ID = "root_id"; 349 350 /** 351 * Flags that apply to a root. This column is required. 352 * <p> 353 * Type: INTEGER (int) 354 * 355 * @see #FLAG_LOCAL_ONLY 356 * @see #FLAG_SUPPORTS_CREATE 357 * @see #FLAG_SUPPORTS_RECENTS 358 * @see #FLAG_SUPPORTS_SEARCH 359 */ 360 public static final String COLUMN_FLAGS = "flags"; 361 362 /** 363 * Icon resource ID for a root. This column is required. 364 * <p> 365 * Type: INTEGER (int) 366 */ 367 public static final String COLUMN_ICON = "icon"; 368 369 /** 370 * Title for a root, which will be shown to a user. This column is 371 * required. For a single storage service surfacing multiple accounts as 372 * different roots, this title should be the name of the service. 373 * <p> 374 * Type: STRING 375 */ 376 public static final String COLUMN_TITLE = "title"; 377 378 /** 379 * Summary for this root, which may be shown to a user. This column is 380 * optional, and may be {@code null}. For a single storage service 381 * surfacing multiple accounts as different roots, this summary should 382 * be the name of the account. 383 * <p> 384 * Type: STRING 385 */ 386 public static final String COLUMN_SUMMARY = "summary"; 387 388 /** 389 * Document which is a directory that represents the top directory of 390 * this root. This column is required. 391 * <p> 392 * Type: STRING 393 * 394 * @see Document#COLUMN_DOCUMENT_ID 395 */ 396 public static final String COLUMN_DOCUMENT_ID = "document_id"; 397 398 /** 399 * Number of bytes available in this root. This column is optional, and 400 * may be {@code null} if unknown or unbounded. 401 * <p> 402 * Type: INTEGER (long) 403 */ 404 public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; 405 406 /** 407 * MIME types supported by this root. This column is optional, and if 408 * {@code null} the root is assumed to support all MIME types. Multiple 409 * MIME types can be separated by a newline. For example, a root 410 * supporting audio might return "audio/*\napplication/x-flac". 411 * <p> 412 * Type: STRING 413 */ 414 public static final String COLUMN_MIME_TYPES = "mime_types"; 415 416 /** {@hide} */ 417 public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; 418 419 /** 420 * Flag indicating that at least one directory under this root supports 421 * creating content. Roots with this flag will be shown when an 422 * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}. 423 * 424 * @see #COLUMN_FLAGS 425 */ 426 public static final int FLAG_SUPPORTS_CREATE = 1; 427 428 /** 429 * Flag indicating that this root offers content that is strictly local 430 * on the device. That is, no network requests are made for the content. 431 * 432 * @see #COLUMN_FLAGS 433 * @see Intent#EXTRA_LOCAL_ONLY 434 */ 435 public static final int FLAG_LOCAL_ONLY = 1 << 1; 436 437 /** 438 * Flag indicating that this root can be queried to provide recently 439 * modified documents. 440 * 441 * @see #COLUMN_FLAGS 442 * @see DocumentsContract#buildRecentDocumentsUri(String, String) 443 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 444 */ 445 public static final int FLAG_SUPPORTS_RECENTS = 1 << 2; 446 447 /** 448 * Flag indicating that this root supports search. 449 * 450 * @see #COLUMN_FLAGS 451 * @see DocumentsContract#buildSearchDocumentsUri(String, String, 452 * String) 453 * @see DocumentsProvider#querySearchDocuments(String, String, 454 * String[]) 455 */ 456 public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; 457 458 /** 459 * Flag indicating that this root supports testing parent child 460 * relationships. 461 * 462 * @see #COLUMN_FLAGS 463 * @see DocumentsProvider#isChildDocument(String, String) 464 */ 465 public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4; 466 467 /** 468 * Flag indicating that this root is currently empty. This may be used 469 * to hide the root when opening documents, but the root will still be 470 * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is 471 * also set. If the value of this flag changes, such as when a root 472 * becomes non-empty, you must send a content changed notification for 473 * {@link DocumentsContract#buildRootsUri(String)}. 474 * 475 * @see #COLUMN_FLAGS 476 * @see ContentResolver#notifyChange(Uri, 477 * android.database.ContentObserver, boolean) 478 * @hide 479 */ 480 public static final int FLAG_EMPTY = 1 << 16; 481 482 /** 483 * Flag indicating that this root should only be visible to advanced 484 * users. 485 * 486 * @see #COLUMN_FLAGS 487 * @hide 488 */ 489 public static final int FLAG_ADVANCED = 1 << 17; 490 491 /** 492 * Flag indicating that this root has settings. 493 * 494 * @see #COLUMN_FLAGS 495 * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS 496 * @hide 497 */ 498 public static final int FLAG_HAS_SETTINGS = 1 << 18; 499 } 500 501 /** 502 * Optional boolean flag included in a directory {@link Cursor#getExtras()} 503 * indicating that a document provider is still loading data. For example, a 504 * provider has returned some results, but is still waiting on an 505 * outstanding network request. The provider must send a content changed 506 * notification when loading is finished. 507 * 508 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, 509 * boolean) 510 */ 511 public static final String EXTRA_LOADING = "loading"; 512 513 /** 514 * Optional string included in a directory {@link Cursor#getExtras()} 515 * providing an informational message that should be shown to a user. For 516 * example, a provider may wish to indicate that not all documents are 517 * available. 518 */ 519 public static final String EXTRA_INFO = "info"; 520 521 /** 522 * Optional string included in a directory {@link Cursor#getExtras()} 523 * providing an error message that should be shown to a user. For example, a 524 * provider may wish to indicate that a network error occurred. The user may 525 * choose to retry, resulting in a new query. 526 */ 527 public static final String EXTRA_ERROR = "error"; 528 529 /** {@hide} */ 530 public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; 531 /** {@hide} */ 532 public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; 533 /** {@hide} */ 534 public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; 535 536 /** {@hide} */ 537 public static final String EXTRA_URI = "uri"; 538 539 private static final String PATH_ROOT = "root"; 540 private static final String PATH_RECENT = "recent"; 541 private static final String PATH_DOCUMENT = "document"; 542 private static final String PATH_CHILDREN = "children"; 543 private static final String PATH_SEARCH = "search"; 544 private static final String PATH_TREE = "tree"; 545 546 private static final String PARAM_QUERY = "query"; 547 private static final String PARAM_MANAGE = "manage"; 548 549 /** 550 * Build URI representing the roots of a document provider. When queried, a 551 * provider will return one or more rows with columns defined by 552 * {@link Root}. 553 * 554 * @see DocumentsProvider#queryRoots(String[]) 555 */ 556 public static Uri buildRootsUri(String authority) { 557 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 558 .authority(authority).appendPath(PATH_ROOT).build(); 559 } 560 561 /** 562 * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a 563 * document provider. 564 * 565 * @see #getRootId(Uri) 566 */ 567 public static Uri buildRootUri(String authority, String rootId) { 568 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 569 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); 570 } 571 572 /** 573 * Build URI representing the recently modified documents of a specific root 574 * in a document provider. When queried, a provider will return zero or more 575 * rows with columns defined by {@link Document}. 576 * 577 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 578 * @see #getRootId(Uri) 579 */ 580 public static Uri buildRecentDocumentsUri(String authority, String rootId) { 581 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 582 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) 583 .appendPath(PATH_RECENT).build(); 584 } 585 586 /** 587 * Build URI representing access to descendant documents of the given 588 * {@link Document#COLUMN_DOCUMENT_ID}. 589 * 590 * @see #getTreeDocumentId(Uri) 591 */ 592 public static Uri buildTreeDocumentUri(String authority, String documentId) { 593 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 594 .appendPath(PATH_TREE).appendPath(documentId).build(); 595 } 596 597 /** 598 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in 599 * a document provider. When queried, a provider will return a single row 600 * with columns defined by {@link Document}. 601 * 602 * @see DocumentsProvider#queryDocument(String, String[]) 603 * @see #getDocumentId(Uri) 604 */ 605 public static Uri buildDocumentUri(String authority, String documentId) { 606 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 607 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); 608 } 609 610 /** 611 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in 612 * a document provider. When queried, a provider will return a single row 613 * with columns defined by {@link Document}. 614 * <p> 615 * However, instead of directly accessing the target document, the returned 616 * URI will leverage access granted through a subtree URI, typically 617 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document 618 * must be a descendant (child, grandchild, etc) of the subtree. 619 * <p> 620 * This is typically used to access documents under a user-selected 621 * directory tree, since it doesn't require the user to separately confirm 622 * each new document access. 623 * 624 * @param treeUri the subtree to leverage to gain access to the target 625 * document. The target directory must be a descendant of this 626 * subtree. 627 * @param documentId the target document, which the caller may not have 628 * direct access to. 629 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 630 * @see DocumentsProvider#isChildDocument(String, String) 631 * @see #buildDocumentUri(String, String) 632 */ 633 public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) { 634 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 635 .authority(treeUri.getAuthority()).appendPath(PATH_TREE) 636 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) 637 .appendPath(documentId).build(); 638 } 639 640 /** {@hide} */ 641 public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) { 642 if (isTreeUri(baseUri)) { 643 return buildDocumentUriUsingTree(baseUri, documentId); 644 } else { 645 return buildDocumentUri(baseUri.getAuthority(), documentId); 646 } 647 } 648 649 /** 650 * Build URI representing the children of the target directory in a document 651 * provider. When queried, a provider will return zero or more rows with 652 * columns defined by {@link Document}. 653 * 654 * @param parentDocumentId the document to return children for, which must 655 * be a directory with MIME type of 656 * {@link Document#MIME_TYPE_DIR}. 657 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 658 * @see #getDocumentId(Uri) 659 */ 660 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { 661 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 662 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) 663 .build(); 664 } 665 666 /** 667 * Build URI representing the children of the target directory in a document 668 * provider. When queried, a provider will return zero or more rows with 669 * columns defined by {@link Document}. 670 * <p> 671 * However, instead of directly accessing the target directory, the returned 672 * URI will leverage access granted through a subtree URI, typically 673 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target 674 * directory must be a descendant (child, grandchild, etc) of the subtree. 675 * <p> 676 * This is typically used to access documents under a user-selected 677 * directory tree, since it doesn't require the user to separately confirm 678 * each new document access. 679 * 680 * @param treeUri the subtree to leverage to gain access to the target 681 * document. The target directory must be a descendant of this 682 * subtree. 683 * @param parentDocumentId the document to return children for, which the 684 * caller may not have direct access to, and which must be a 685 * directory with MIME type of {@link Document#MIME_TYPE_DIR}. 686 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 687 * @see DocumentsProvider#isChildDocument(String, String) 688 * @see #buildChildDocumentsUri(String, String) 689 */ 690 public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) { 691 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 692 .authority(treeUri.getAuthority()).appendPath(PATH_TREE) 693 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) 694 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build(); 695 } 696 697 /** 698 * Build URI representing a search for matching documents under a specific 699 * root in a document provider. When queried, a provider will return zero or 700 * more rows with columns defined by {@link Document}. 701 * 702 * @see DocumentsProvider#querySearchDocuments(String, String, String[]) 703 * @see #getRootId(Uri) 704 * @see #getSearchDocumentsQuery(Uri) 705 */ 706 public static Uri buildSearchDocumentsUri( 707 String authority, String rootId, String query) { 708 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 709 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH) 710 .appendQueryParameter(PARAM_QUERY, query).build(); 711 } 712 713 /** 714 * Test if the given URI represents a {@link Document} backed by a 715 * {@link DocumentsProvider}. 716 * 717 * @see #buildDocumentUri(String, String) 718 * @see #buildDocumentUriUsingTree(Uri, String) 719 */ 720 public static boolean isDocumentUri(Context context, Uri uri) { 721 final List<String> paths = uri.getPathSegments(); 722 if (paths.size() == 2 && PATH_DOCUMENT.equals(paths.get(0))) { 723 return isDocumentsProvider(context, uri.getAuthority()); 724 } 725 if (paths.size() == 4 && PATH_TREE.equals(paths.get(0)) 726 && PATH_DOCUMENT.equals(paths.get(2))) { 727 return isDocumentsProvider(context, uri.getAuthority()); 728 } 729 return false; 730 } 731 732 /** {@hide} */ 733 public static boolean isTreeUri(Uri uri) { 734 final List<String> paths = uri.getPathSegments(); 735 return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))); 736 } 737 738 private static boolean isDocumentsProvider(Context context, String authority) { 739 final Intent intent = new Intent(PROVIDER_INTERFACE); 740 final List<ResolveInfo> infos = context.getPackageManager() 741 .queryIntentContentProviders(intent, 0); 742 for (ResolveInfo info : infos) { 743 if (authority.equals(info.providerInfo.authority)) { 744 return true; 745 } 746 } 747 return false; 748 } 749 750 /** 751 * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI. 752 */ 753 public static String getRootId(Uri rootUri) { 754 final List<String> paths = rootUri.getPathSegments(); 755 if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) { 756 return paths.get(1); 757 } 758 throw new IllegalArgumentException("Invalid URI: " + rootUri); 759 } 760 761 /** 762 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI. 763 * 764 * @see #isDocumentUri(Context, Uri) 765 */ 766 public static String getDocumentId(Uri documentUri) { 767 final List<String> paths = documentUri.getPathSegments(); 768 if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) { 769 return paths.get(1); 770 } 771 if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0)) 772 && PATH_DOCUMENT.equals(paths.get(2))) { 773 return paths.get(3); 774 } 775 throw new IllegalArgumentException("Invalid URI: " + documentUri); 776 } 777 778 /** 779 * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI. 780 */ 781 public static String getTreeDocumentId(Uri documentUri) { 782 final List<String> paths = documentUri.getPathSegments(); 783 if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) { 784 return paths.get(1); 785 } 786 throw new IllegalArgumentException("Invalid URI: " + documentUri); 787 } 788 789 /** 790 * Extract the search query from a URI built by 791 * {@link #buildSearchDocumentsUri(String, String, String)}. 792 */ 793 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { 794 return searchDocumentsUri.getQueryParameter(PARAM_QUERY); 795 } 796 797 /** {@hide} */ 798 public static Uri setManageMode(Uri uri) { 799 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); 800 } 801 802 /** {@hide} */ 803 public static boolean isManageMode(Uri uri) { 804 return uri.getBooleanQueryParameter(PARAM_MANAGE, false); 805 } 806 807 /** 808 * Return thumbnail representing the document at the given URI. Callers are 809 * responsible for their own in-memory caching. 810 * 811 * @param documentUri document to return thumbnail for, which must have 812 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. 813 * @param size optimal thumbnail size desired. A provider may return a 814 * thumbnail of a different size, but never more than double the 815 * requested size. 816 * @param signal signal used to indicate if caller is no longer interested 817 * in the thumbnail. 818 * @return decoded thumbnail, or {@code null} if problem was encountered. 819 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 820 * android.os.CancellationSignal) 821 */ 822 public static Bitmap getDocumentThumbnail( 823 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 824 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 825 documentUri.getAuthority()); 826 try { 827 return getDocumentThumbnail(client, documentUri, size, signal); 828 } catch (Exception e) { 829 if (!(e instanceof OperationCanceledException)) { 830 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 831 } 832 return null; 833 } finally { 834 ContentProviderClient.releaseQuietly(client); 835 } 836 } 837 838 /** {@hide} */ 839 public static Bitmap getDocumentThumbnail( 840 ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) 841 throws RemoteException, IOException { 842 final Bundle openOpts = new Bundle(); 843 openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size); 844 845 AssetFileDescriptor afd = null; 846 Bitmap bitmap = null; 847 try { 848 afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); 849 850 final FileDescriptor fd = afd.getFileDescriptor(); 851 final long offset = afd.getStartOffset(); 852 853 // Try seeking on the returned FD, since it gives us the most 854 // optimal decode path; otherwise fall back to buffering. 855 BufferedInputStream is = null; 856 try { 857 Os.lseek(fd, offset, SEEK_SET); 858 } catch (ErrnoException e) { 859 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); 860 is.mark(THUMBNAIL_BUFFER_SIZE); 861 } 862 863 // We requested a rough thumbnail size, but the remote size may have 864 // returned something giant, so defensively scale down as needed. 865 final BitmapFactory.Options opts = new BitmapFactory.Options(); 866 opts.inJustDecodeBounds = true; 867 if (is != null) { 868 BitmapFactory.decodeStream(is, null, opts); 869 } else { 870 BitmapFactory.decodeFileDescriptor(fd, null, opts); 871 } 872 873 final int widthSample = opts.outWidth / size.x; 874 final int heightSample = opts.outHeight / size.y; 875 876 opts.inJustDecodeBounds = false; 877 opts.inSampleSize = Math.min(widthSample, heightSample); 878 if (is != null) { 879 is.reset(); 880 bitmap = BitmapFactory.decodeStream(is, null, opts); 881 } else { 882 try { 883 Os.lseek(fd, offset, SEEK_SET); 884 } catch (ErrnoException e) { 885 e.rethrowAsIOException(); 886 } 887 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts); 888 } 889 890 // Transform the bitmap if requested. We use a side-channel to 891 // communicate the orientation, since EXIF thumbnails don't contain 892 // the rotation flags of the original image. 893 final Bundle extras = afd.getExtras(); 894 final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; 895 if (orientation != 0) { 896 final int width = bitmap.getWidth(); 897 final int height = bitmap.getHeight(); 898 899 final Matrix m = new Matrix(); 900 m.setRotate(orientation, width / 2, height / 2); 901 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); 902 } 903 } finally { 904 IoUtils.closeQuietly(afd); 905 } 906 907 return bitmap; 908 } 909 910 /** 911 * Create a new document with given MIME type and display name. 912 * 913 * @param parentDocumentUri directory with 914 * {@link Document#FLAG_DIR_SUPPORTS_CREATE} 915 * @param mimeType MIME type of new document 916 * @param displayName name of new document 917 * @return newly created document, or {@code null} if failed 918 */ 919 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, 920 String mimeType, String displayName) { 921 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 922 parentDocumentUri.getAuthority()); 923 try { 924 return createDocument(client, parentDocumentUri, mimeType, displayName); 925 } catch (Exception e) { 926 Log.w(TAG, "Failed to create document", e); 927 return null; 928 } finally { 929 ContentProviderClient.releaseQuietly(client); 930 } 931 } 932 933 /** {@hide} */ 934 public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, 935 String mimeType, String displayName) throws RemoteException { 936 final Bundle in = new Bundle(); 937 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); 938 in.putString(Document.COLUMN_MIME_TYPE, mimeType); 939 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 940 941 final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); 942 return out.getParcelable(DocumentsContract.EXTRA_URI); 943 } 944 945 /** 946 * Change the display name of an existing document. 947 * <p> 948 * If the underlying provider needs to create a new 949 * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display 950 * name, that new document is returned and the original document is no 951 * longer valid. Otherwise, the original document is returned. 952 * 953 * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME} 954 * @param displayName updated name for document 955 * @return the existing or new document after the rename, or {@code null} if 956 * failed. 957 */ 958 public static Uri renameDocument(ContentResolver resolver, Uri documentUri, 959 String displayName) { 960 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 961 documentUri.getAuthority()); 962 try { 963 return renameDocument(client, documentUri, displayName); 964 } catch (Exception e) { 965 Log.w(TAG, "Failed to rename document", e); 966 return null; 967 } finally { 968 ContentProviderClient.releaseQuietly(client); 969 } 970 } 971 972 /** {@hide} */ 973 public static Uri renameDocument(ContentProviderClient client, Uri documentUri, 974 String displayName) throws RemoteException { 975 final Bundle in = new Bundle(); 976 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 977 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 978 979 final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); 980 final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI); 981 return (outUri != null) ? outUri : documentUri; 982 } 983 984 /** 985 * Delete the given document. 986 * 987 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} 988 * @return if the document was deleted successfully. 989 */ 990 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 991 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 992 documentUri.getAuthority()); 993 try { 994 deleteDocument(client, documentUri); 995 return true; 996 } catch (Exception e) { 997 Log.w(TAG, "Failed to delete document", e); 998 return false; 999 } finally { 1000 ContentProviderClient.releaseQuietly(client); 1001 } 1002 } 1003 1004 /** {@hide} */ 1005 public static void deleteDocument(ContentProviderClient client, Uri documentUri) 1006 throws RemoteException { 1007 final Bundle in = new Bundle(); 1008 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 1009 1010 client.call(METHOD_DELETE_DOCUMENT, null, in); 1011 } 1012 1013 /** 1014 * Open the given image for thumbnail purposes, using any embedded EXIF 1015 * thumbnail if available, and providing orientation hints from the parent 1016 * image. 1017 * 1018 * @hide 1019 */ 1020 public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException { 1021 final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 1022 file, ParcelFileDescriptor.MODE_READ_ONLY); 1023 Bundle extras = null; 1024 1025 try { 1026 final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); 1027 1028 switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) { 1029 case ExifInterface.ORIENTATION_ROTATE_90: 1030 extras = new Bundle(1); 1031 extras.putInt(EXTRA_ORIENTATION, 90); 1032 break; 1033 case ExifInterface.ORIENTATION_ROTATE_180: 1034 extras = new Bundle(1); 1035 extras.putInt(EXTRA_ORIENTATION, 180); 1036 break; 1037 case ExifInterface.ORIENTATION_ROTATE_270: 1038 extras = new Bundle(1); 1039 extras.putInt(EXTRA_ORIENTATION, 270); 1040 break; 1041 } 1042 1043 final long[] thumb = exif.getThumbnailRange(); 1044 if (thumb != null) { 1045 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras); 1046 } 1047 } catch (IOException e) { 1048 } 1049 1050 return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); 1051 } 1052} 1053