DocumentsProvider.java revision e37ea6123d1aa3cd3e8804988886b1f6046d79d6
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_ROOT = 1; 79 private static final int MATCH_RECENT = 2; 80 private static final int MATCH_DOCUMENT = 3; 81 private static final int MATCH_CHILDREN = 4; 82 private static final int MATCH_SEARCH = 5; 83 84 private String mAuthority; 85 86 private UriMatcher mMatcher; 87 88 /** 89 * Implementation is provided by the parent class. 90 */ 91 @Override 92 public void attachInfo(Context context, ProviderInfo info) { 93 mAuthority = info.authority; 94 95 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); 96 mMatcher.addURI(mAuthority, "root", MATCH_ROOT); 97 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); 98 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); 99 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); 100 mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH); 101 102 // Sanity check our setup 103 if (!info.exported) { 104 throw new SecurityException("Provider must be exported"); 105 } 106 if (!info.grantUriPermissions) { 107 throw new SecurityException("Provider must grantUriPermissions"); 108 } 109 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission) 110 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) { 111 throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS"); 112 } 113 114 super.attachInfo(context, info); 115 } 116 117 /** 118 * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}. 119 * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to 120 * represent the document, which must not change once returned. 121 * 122 * @param documentId the parent directory to create the new document under. 123 * @param mimeType the MIME type associated with the new document. 124 * @param displayName the display name of the new document. 125 */ 126 @SuppressWarnings("unused") 127 public String createDocument(String documentId, String mimeType, String displayName) 128 throws FileNotFoundException { 129 throw new UnsupportedOperationException("Create not supported"); 130 } 131 132 /** 133 * Delete the given document. Upon returning, any Uri permission grants for 134 * the given document will be revoked. If additional documents were deleted 135 * as a side effect of this call, such as documents inside a directory, the 136 * implementor is responsible for revoking those permissions. 137 * 138 * @param documentId the document to delete. 139 */ 140 @SuppressWarnings("unused") 141 public void deleteDocument(String documentId) throws FileNotFoundException { 142 throw new UnsupportedOperationException("Delete not supported"); 143 } 144 145 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; 146 147 @SuppressWarnings("unused") 148 public Cursor queryRecentDocuments(String rootId, String[] projection) 149 throws FileNotFoundException { 150 throw new UnsupportedOperationException("Recent not supported"); 151 } 152 153 /** 154 * Return metadata for the given document. A provider should avoid making 155 * network requests to keep this request fast. 156 * 157 * @param documentId the document to return. 158 */ 159 public abstract Cursor queryDocument(String documentId, String[] projection) 160 throws FileNotFoundException; 161 162 /** 163 * Return the children of the given document which is a directory. 164 * 165 * @param parentDocumentId the directory to return children for. 166 */ 167 public abstract Cursor queryChildDocuments( 168 String parentDocumentId, String[] projection, String sortOrder) 169 throws FileNotFoundException; 170 171 /** 172 * Return documents that that match the given query, starting the search at 173 * the given directory. 174 * 175 * @param parentDocumentId the directory to start search at. 176 */ 177 @SuppressWarnings("unused") 178 public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection) 179 throws FileNotFoundException { 180 throw new UnsupportedOperationException("Search not supported"); 181 } 182 183 /** 184 * Return MIME type for the given document. Must match the value of 185 * {@link Document#COLUMN_MIME_TYPE} for this document. 186 */ 187 public String getDocumentType(String documentId) throws FileNotFoundException { 188 final Cursor cursor = queryDocument(documentId, null); 189 try { 190 if (cursor.moveToFirst()) { 191 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 192 } else { 193 return null; 194 } 195 } finally { 196 IoUtils.closeQuietly(cursor); 197 } 198 } 199 200 /** 201 * Open and return the requested document. A provider should return a 202 * reliable {@link ParcelFileDescriptor} to detect when the remote caller 203 * has finished reading or writing the document. A provider may return a 204 * pipe or socket pair if the mode is exclusively 205 * {@link ParcelFileDescriptor#MODE_READ_ONLY} or 206 * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, but complex modes like 207 * {@link ParcelFileDescriptor#MODE_READ_WRITE} require a normal file on 208 * disk. If a provider blocks while downloading content, it should 209 * periodically check {@link CancellationSignal#isCanceled()} to abort 210 * abandoned open requests. 211 * 212 * @param docId the document to return. 213 * @param mode the mode to open with, such as 'r', 'w', or 'rw'. 214 * @param signal used by the caller to signal if the request should be 215 * cancelled. 216 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, 217 * OnCloseListener) 218 * @see ParcelFileDescriptor#createReliablePipe() 219 * @see ParcelFileDescriptor#createReliableSocketPair() 220 */ 221 public abstract ParcelFileDescriptor openDocument( 222 String docId, String mode, CancellationSignal signal) throws FileNotFoundException; 223 224 /** 225 * Open and return a thumbnail of the requested document. A provider should 226 * return a thumbnail closely matching the hinted size, attempting to serve 227 * from a local cache if possible. A provider should never return images 228 * more than double the hinted size. If a provider performs expensive 229 * operations to download or generate a thumbnail, it should periodically 230 * check {@link CancellationSignal#isCanceled()} to abort abandoned 231 * thumbnail requests. 232 * 233 * @param docId the document to return. 234 * @param sizeHint hint of the optimal thumbnail dimensions. 235 * @param signal used by the caller to signal if the request should be 236 * cancelled. 237 * @see Document#FLAG_SUPPORTS_THUMBNAIL 238 */ 239 @SuppressWarnings("unused") 240 public AssetFileDescriptor openDocumentThumbnail( 241 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { 242 throw new UnsupportedOperationException("Thumbnails not supported"); 243 } 244 245 /** 246 * Implementation is provided by the parent class. Cannot be overriden. 247 * 248 * @see #queryRoots(String[]) 249 * @see #queryRecentDocuments(String, String[]) 250 * @see #queryDocument(String, String[]) 251 * @see #queryChildDocuments(String, String[], String) 252 * @see #querySearchDocuments(String, String, String[]) 253 */ 254 @Override 255 public final Cursor query(Uri uri, String[] projection, String selection, 256 String[] selectionArgs, String sortOrder) { 257 try { 258 switch (mMatcher.match(uri)) { 259 case MATCH_ROOT: 260 return queryRoots(projection); 261 case MATCH_RECENT: 262 return queryRecentDocuments(getRootId(uri), projection); 263 case MATCH_DOCUMENT: 264 return queryDocument(getDocumentId(uri), projection); 265 case MATCH_CHILDREN: 266 return queryChildDocuments(getDocumentId(uri), projection, sortOrder); 267 case MATCH_SEARCH: 268 return querySearchDocuments( 269 getDocumentId(uri), getSearchDocumentsQuery(uri), projection); 270 default: 271 throw new UnsupportedOperationException("Unsupported Uri " + uri); 272 } 273 } catch (FileNotFoundException e) { 274 Log.w(TAG, "Failed during query", e); 275 return null; 276 } 277 } 278 279 /** 280 * Implementation is provided by the parent class. Cannot be overriden. 281 * 282 * @see #getDocumentType(String) 283 */ 284 @Override 285 public final String getType(Uri uri) { 286 try { 287 switch (mMatcher.match(uri)) { 288 case MATCH_DOCUMENT: 289 return getDocumentType(getDocumentId(uri)); 290 default: 291 return null; 292 } 293 } catch (FileNotFoundException e) { 294 Log.w(TAG, "Failed during getType", e); 295 return null; 296 } 297 } 298 299 /** 300 * Implementation is provided by the parent class. Throws by default, and 301 * cannot be overriden. 302 * 303 * @see #createDocument(String, String, String) 304 */ 305 @Override 306 public final Uri insert(Uri uri, ContentValues values) { 307 throw new UnsupportedOperationException("Insert not supported"); 308 } 309 310 /** 311 * Implementation is provided by the parent class. Throws by default, and 312 * cannot be overriden. 313 * 314 * @see #deleteDocument(String) 315 */ 316 @Override 317 public final int delete(Uri uri, String selection, String[] selectionArgs) { 318 throw new UnsupportedOperationException("Delete not supported"); 319 } 320 321 /** 322 * Implementation is provided by the parent class. Throws by default, and 323 * cannot be overriden. 324 */ 325 @Override 326 public final int update( 327 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 328 throw new UnsupportedOperationException("Update not supported"); 329 } 330 331 /** {@hide} */ 332 @Override 333 public final Bundle callFromPackage( 334 String callingPackage, String method, String arg, Bundle extras) { 335 final Context context = getContext(); 336 337 if (!method.startsWith("android:")) { 338 // Let non-platform methods pass through 339 return super.callFromPackage(callingPackage, method, arg, extras); 340 } 341 342 final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); 343 final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); 344 345 // Require that caller can manage given document 346 final boolean callerHasManage = 347 context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) 348 == PackageManager.PERMISSION_GRANTED; 349 if (!callerHasManage) { 350 getContext().enforceCallingOrSelfUriPermission( 351 documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); 352 } 353 354 final Bundle out = new Bundle(); 355 try { 356 if (METHOD_CREATE_DOCUMENT.equals(method)) { 357 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); 358 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 359 360 final String newDocumentId = createDocument(documentId, mimeType, displayName); 361 out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); 362 363 // Extend permission grant towards caller if needed 364 if (!callerHasManage) { 365 final Uri newDocumentUri = DocumentsContract.buildDocumentUri( 366 mAuthority, newDocumentId); 367 context.grantUriPermission(callingPackage, newDocumentUri, 368 Intent.FLAG_GRANT_READ_URI_PERMISSION 369 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 370 | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); 371 } 372 373 } else if (METHOD_DELETE_DOCUMENT.equals(method)) { 374 deleteDocument(documentId); 375 376 // Document no longer exists, clean up any grants 377 context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 378 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 379 | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); 380 381 } else { 382 throw new UnsupportedOperationException("Method not supported " + method); 383 } 384 } catch (FileNotFoundException e) { 385 throw new IllegalStateException("Failed call " + method, e); 386 } 387 return out; 388 } 389 390 /** 391 * Implementation is provided by the parent class. 392 * 393 * @see #openDocument(String, String, CancellationSignal) 394 */ 395 @Override 396 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 397 return openDocument(getDocumentId(uri), mode, null); 398 } 399 400 /** 401 * Implementation is provided by the parent class. 402 * 403 * @see #openDocument(String, String, CancellationSignal) 404 */ 405 @Override 406 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) 407 throws FileNotFoundException { 408 return openDocument(getDocumentId(uri), mode, signal); 409 } 410 411 /** 412 * Implementation is provided by the parent class. 413 * 414 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 415 */ 416 @Override 417 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) 418 throws FileNotFoundException { 419 if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { 420 final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); 421 return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); 422 } else { 423 return super.openTypedAssetFile(uri, mimeTypeFilter, opts); 424 } 425 } 426 427 /** 428 * Implementation is provided by the parent class. 429 * 430 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 431 */ 432 @Override 433 public final AssetFileDescriptor openTypedAssetFile( 434 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 435 throws FileNotFoundException { 436 if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { 437 final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); 438 return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); 439 } else { 440 return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); 441 } 442 } 443} 444