DocumentsProvider.java revision a61dc8e03e6e863005b3a4629ca8f3801d33d3c4
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.provider; 18 19import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; 20import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; 21import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; 22import static android.provider.DocumentsContract.getDocumentId; 23import static android.provider.DocumentsContract.getRootId; 24import static android.provider.DocumentsContract.getSearchDocumentsQuery; 25 26import android.content.ContentProvider; 27import android.content.ContentValues; 28import android.content.Context; 29import android.content.Intent; 30import android.content.UriMatcher; 31import android.content.pm.PackageManager; 32import android.content.pm.ProviderInfo; 33import android.content.res.AssetFileDescriptor; 34import android.database.Cursor; 35import android.graphics.Point; 36import android.net.Uri; 37import android.os.Binder; 38import android.os.Bundle; 39import android.os.CancellationSignal; 40import android.os.ParcelFileDescriptor; 41import android.os.ParcelFileDescriptor.OnCloseListener; 42import android.provider.DocumentsContract.Document; 43import android.util.Log; 44 45import com.android.internal.util.ArrayUtils; 46 47import libcore.io.IoUtils; 48 49import java.io.FileNotFoundException; 50 51/** 52 * Base class for a document provider. A document provider should extend this 53 * class and implement the abstract methods. 54 * <p> 55 * Each document provider expresses one or more "roots" which each serve as the 56 * top-level of a tree. For example, a root could represent an account, or a 57 * physical storage device. Under each root, documents are referenced by 58 * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. 59 * <p> 60 * Documents can be either an openable file (with a specific MIME type), or a 61 * directory containing additional documents (with the 62 * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different 63 * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same 64 * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories. 65 * <p> 66 * Document providers must be protected with the 67 * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can 68 * only be requested by the system. The system-provided UI then issues narrow 69 * Uri permission grants for individual documents when the user explicitly picks 70 * documents. 71 * 72 * @see Intent#ACTION_OPEN_DOCUMENT 73 * @see Intent#ACTION_CREATE_DOCUMENT 74 */ 75public abstract class DocumentsProvider extends ContentProvider { 76 private static final String TAG = "DocumentsProvider"; 77 78 private static final int MATCH_ROOTS = 1; 79 private static final int MATCH_ROOT = 2; 80 private static final int MATCH_RECENT = 3; 81 private static final int MATCH_DOCUMENT = 4; 82 private static final int MATCH_CHILDREN = 5; 83 private static final int MATCH_SEARCH = 6; 84 85 private String mAuthority; 86 87 private UriMatcher mMatcher; 88 89 /** 90 * Implementation is provided by the parent class. 91 */ 92 @Override 93 public void attachInfo(Context context, ProviderInfo info) { 94 mAuthority = info.authority; 95 96 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); 97 mMatcher.addURI(mAuthority, "root", MATCH_ROOTS); 98 mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT); 99 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); 100 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); 101 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); 102 mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH); 103 104 // Sanity check our setup 105 if (!info.exported) { 106 throw new SecurityException("Provider must be exported"); 107 } 108 if (!info.grantUriPermissions) { 109 throw new SecurityException("Provider must grantUriPermissions"); 110 } 111 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission) 112 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) { 113 throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS"); 114 } 115 116 super.attachInfo(context, info); 117 } 118 119 /** 120 * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}. 121 * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to 122 * represent the document, which must not change once returned. 123 * 124 * @param documentId the parent directory to create the new document under. 125 * @param mimeType the MIME type associated with the new document. 126 * @param displayName the display name of the new document. 127 */ 128 @SuppressWarnings("unused") 129 public String createDocument(String documentId, String mimeType, String displayName) 130 throws FileNotFoundException { 131 throw new UnsupportedOperationException("Create not supported"); 132 } 133 134 /** 135 * Delete the given document. Upon returning, any Uri permission grants for 136 * the given document will be revoked. If additional documents were deleted 137 * as a side effect of this call, such as documents inside a directory, the 138 * implementor is responsible for revoking those permissions. 139 * 140 * @param documentId the document to delete. 141 */ 142 @SuppressWarnings("unused") 143 public void deleteDocument(String documentId) throws FileNotFoundException { 144 throw new UnsupportedOperationException("Delete not supported"); 145 } 146 147 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; 148 149 @SuppressWarnings("unused") 150 public Cursor queryRecentDocuments(String rootId, String[] projection) 151 throws FileNotFoundException { 152 throw new UnsupportedOperationException("Recent not supported"); 153 } 154 155 /** 156 * Return metadata for the given document. A provider should avoid making 157 * network requests to keep this request fast. 158 * 159 * @param documentId the document to return. 160 */ 161 public abstract Cursor queryDocument(String documentId, String[] projection) 162 throws FileNotFoundException; 163 164 /** 165 * Return the children of the given document which is a directory. 166 * 167 * @param parentDocumentId the directory to return children for. 168 */ 169 public abstract Cursor queryChildDocuments( 170 String parentDocumentId, String[] projection, String sortOrder) 171 throws FileNotFoundException; 172 173 /** 174 * Return documents that that match the given query, starting the search at 175 * the given directory. 176 * 177 * @param parentDocumentId the directory to start search at. 178 */ 179 @SuppressWarnings("unused") 180 public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection) 181 throws FileNotFoundException { 182 throw new UnsupportedOperationException("Search not supported"); 183 } 184 185 /** 186 * Return MIME type for the given document. Must match the value of 187 * {@link Document#COLUMN_MIME_TYPE} for this document. 188 */ 189 public String getDocumentType(String documentId) throws FileNotFoundException { 190 final Cursor cursor = queryDocument(documentId, null); 191 try { 192 if (cursor.moveToFirst()) { 193 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 194 } else { 195 return null; 196 } 197 } finally { 198 IoUtils.closeQuietly(cursor); 199 } 200 } 201 202 /** 203 * Open and return the requested document. A provider should return a 204 * reliable {@link ParcelFileDescriptor} to detect when the remote caller 205 * has finished reading or writing the document. A provider may return a 206 * pipe or socket pair if the mode is exclusively 207 * {@link ParcelFileDescriptor#MODE_READ_ONLY} or 208 * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, but complex modes like 209 * {@link ParcelFileDescriptor#MODE_READ_WRITE} require a normal file on 210 * disk. If a provider blocks while downloading content, it should 211 * periodically check {@link CancellationSignal#isCanceled()} to abort 212 * abandoned open requests. 213 * 214 * @param docId the document to return. 215 * @param mode the mode to open with, such as 'r', 'w', or 'rw'. 216 * @param signal used by the caller to signal if the request should be 217 * cancelled. 218 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, 219 * OnCloseListener) 220 * @see ParcelFileDescriptor#createReliablePipe() 221 * @see ParcelFileDescriptor#createReliableSocketPair() 222 */ 223 public abstract ParcelFileDescriptor openDocument( 224 String docId, String mode, CancellationSignal signal) throws FileNotFoundException; 225 226 /** 227 * Open and return a thumbnail of the requested document. A provider should 228 * return a thumbnail closely matching the hinted size, attempting to serve 229 * from a local cache if possible. A provider should never return images 230 * more than double the hinted size. If a provider performs expensive 231 * operations to download or generate a thumbnail, it should periodically 232 * check {@link CancellationSignal#isCanceled()} to abort abandoned 233 * thumbnail requests. 234 * 235 * @param docId the document to return. 236 * @param sizeHint hint of the optimal thumbnail dimensions. 237 * @param signal used by the caller to signal if the request should be 238 * cancelled. 239 * @see Document#FLAG_SUPPORTS_THUMBNAIL 240 */ 241 @SuppressWarnings("unused") 242 public AssetFileDescriptor openDocumentThumbnail( 243 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { 244 throw new UnsupportedOperationException("Thumbnails not supported"); 245 } 246 247 /** 248 * Implementation is provided by the parent class. Cannot be overriden. 249 * 250 * @see #queryRoots(String[]) 251 * @see #queryRecentDocuments(String, String[]) 252 * @see #queryDocument(String, String[]) 253 * @see #queryChildDocuments(String, String[], String) 254 * @see #querySearchDocuments(String, String, String[]) 255 */ 256 @Override 257 public final Cursor query(Uri uri, String[] projection, String selection, 258 String[] selectionArgs, String sortOrder) { 259 try { 260 switch (mMatcher.match(uri)) { 261 case MATCH_ROOTS: 262 return queryRoots(projection); 263 case MATCH_RECENT: 264 return queryRecentDocuments(getRootId(uri), projection); 265 case MATCH_DOCUMENT: 266 return queryDocument(getDocumentId(uri), projection); 267 case MATCH_CHILDREN: 268 return queryChildDocuments(getDocumentId(uri), projection, sortOrder); 269 case MATCH_SEARCH: 270 return querySearchDocuments( 271 getDocumentId(uri), getSearchDocumentsQuery(uri), projection); 272 default: 273 throw new UnsupportedOperationException("Unsupported Uri " + uri); 274 } 275 } catch (FileNotFoundException e) { 276 Log.w(TAG, "Failed during query", e); 277 return null; 278 } 279 } 280 281 /** 282 * Implementation is provided by the parent class. Cannot be overriden. 283 * 284 * @see #getDocumentType(String) 285 */ 286 @Override 287 public final String getType(Uri uri) { 288 try { 289 switch (mMatcher.match(uri)) { 290 case MATCH_ROOT: 291 return DocumentsContract.Root.MIME_TYPE_ITEM; 292 case MATCH_DOCUMENT: 293 return getDocumentType(getDocumentId(uri)); 294 default: 295 return null; 296 } 297 } catch (FileNotFoundException e) { 298 Log.w(TAG, "Failed during getType", e); 299 return null; 300 } 301 } 302 303 /** 304 * Implementation is provided by the parent class. Throws by default, and 305 * cannot be overriden. 306 * 307 * @see #createDocument(String, String, String) 308 */ 309 @Override 310 public final Uri insert(Uri uri, ContentValues values) { 311 throw new UnsupportedOperationException("Insert not supported"); 312 } 313 314 /** 315 * Implementation is provided by the parent class. Throws by default, and 316 * cannot be overriden. 317 * 318 * @see #deleteDocument(String) 319 */ 320 @Override 321 public final int delete(Uri uri, String selection, String[] selectionArgs) { 322 throw new UnsupportedOperationException("Delete not supported"); 323 } 324 325 /** 326 * Implementation is provided by the parent class. Throws by default, and 327 * cannot be overriden. 328 */ 329 @Override 330 public final int update( 331 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 332 throw new UnsupportedOperationException("Update not supported"); 333 } 334 335 /** {@hide} */ 336 @Override 337 public final Bundle callFromPackage( 338 String callingPackage, String method, String arg, Bundle extras) { 339 final Context context = getContext(); 340 341 if (!method.startsWith("android:")) { 342 // Let non-platform methods pass through 343 return super.callFromPackage(callingPackage, method, arg, extras); 344 } 345 346 final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); 347 final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); 348 349 // Require that caller can manage given document 350 final boolean callerHasManage = 351 context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) 352 == PackageManager.PERMISSION_GRANTED; 353 if (!callerHasManage) { 354 getContext().enforceCallingOrSelfUriPermission( 355 documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); 356 } 357 358 final Bundle out = new Bundle(); 359 try { 360 if (METHOD_CREATE_DOCUMENT.equals(method)) { 361 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); 362 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 363 364 final String newDocumentId = createDocument(documentId, mimeType, displayName); 365 out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); 366 367 // Extend permission grant towards caller if needed 368 if (!callerHasManage) { 369 final Uri newDocumentUri = DocumentsContract.buildDocumentUri( 370 mAuthority, newDocumentId); 371 context.grantUriPermission(callingPackage, newDocumentUri, 372 Intent.FLAG_GRANT_READ_URI_PERMISSION 373 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 374 | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); 375 } 376 377 } else if (METHOD_DELETE_DOCUMENT.equals(method)) { 378 deleteDocument(documentId); 379 380 // Document no longer exists, clean up any grants 381 context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 382 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 383 | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); 384 385 } else { 386 throw new UnsupportedOperationException("Method not supported " + method); 387 } 388 } catch (FileNotFoundException e) { 389 throw new IllegalStateException("Failed call " + method, e); 390 } 391 return out; 392 } 393 394 /** 395 * Implementation is provided by the parent class. 396 * 397 * @see #openDocument(String, String, CancellationSignal) 398 */ 399 @Override 400 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 401 return openDocument(getDocumentId(uri), mode, null); 402 } 403 404 /** 405 * Implementation is provided by the parent class. 406 * 407 * @see #openDocument(String, String, CancellationSignal) 408 */ 409 @Override 410 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) 411 throws FileNotFoundException { 412 return openDocument(getDocumentId(uri), mode, signal); 413 } 414 415 /** 416 * Implementation is provided by the parent class. 417 * 418 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 419 */ 420 @Override 421 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) 422 throws FileNotFoundException { 423 if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { 424 final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); 425 return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); 426 } else { 427 return super.openTypedAssetFile(uri, mimeTypeFilter, opts); 428 } 429 } 430 431 /** 432 * Implementation is provided by the parent class. 433 * 434 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 435 */ 436 @Override 437 public final AssetFileDescriptor openTypedAssetFile( 438 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 439 throws FileNotFoundException { 440 if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { 441 final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); 442 return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); 443 } else { 444 return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); 445 } 446 } 447} 448