ContentProvider.java revision 328c0e7986aa6bb7752ec6de3da9c999920bb55f
1/* 2 * Copyright (C) 2006 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.content; 18 19import android.content.pm.PackageManager; 20import android.content.pm.PathPermission; 21import android.content.pm.ProviderInfo; 22import android.content.res.AssetFileDescriptor; 23import android.content.res.Configuration; 24import android.database.Cursor; 25import android.database.CursorToBulkCursorAdaptor; 26import android.database.CursorWindow; 27import android.database.IBulkCursor; 28import android.database.IContentObserver; 29import android.database.SQLException; 30import android.net.Uri; 31import android.os.Binder; 32import android.os.ParcelFileDescriptor; 33import android.os.Process; 34 35import java.io.File; 36import java.io.FileNotFoundException; 37import java.util.ArrayList; 38 39/** 40 * Content providers are one of the primary building blocks of Android applications, providing 41 * content to applications. They encapsulate data and provide it to applications through the single 42 * {@link ContentResolver} interface. A content provider is only required if you need to share 43 * data between multiple applications. For example, the contacts data is used by multiple 44 * applications and must be stored in a content provider. If you don't need to share data amongst 45 * multiple applications you can use a database directly via 46 * {@link android.database.sqlite.SQLiteDatabase}. 47 * 48 * <p>For more information, read <a href="{@docRoot}guide/topics/providers/content-providers.html">Content 49 * Providers</a>.</p> 50 * 51 * <p>When a request is made via 52 * a {@link ContentResolver} the system inspects the authority of the given URI and passes the 53 * request to the content provider registered with the authority. The content provider can interpret 54 * the rest of the URI however it wants. The {@link UriMatcher} class is helpful for parsing 55 * URIs.</p> 56 * 57 * <p>The primary methods that need to be implemented are: 58 * <ul> 59 * <li>{@link #query} which returns data to the caller</li> 60 * <li>{@link #insert} which inserts new data into the content provider</li> 61 * <li>{@link #update} which updates existing data in the content provider</li> 62 * <li>{@link #delete} which deletes data from the content provider</li> 63 * <li>{@link #getType} which returns the MIME type of data in the content provider</li> 64 * </ul></p> 65 * 66 * <p>This class takes care of cross process calls so subclasses don't have to worry about which 67 * process a request is coming from.</p> 68 */ 69public abstract class ContentProvider implements ComponentCallbacks { 70 /* 71 * Note: if you add methods to ContentProvider, you must add similar methods to 72 * MockContentProvider. 73 */ 74 75 private Context mContext = null; 76 private int mMyUid; 77 private String mReadPermission; 78 private String mWritePermission; 79 private PathPermission[] mPathPermissions; 80 81 private Transport mTransport = new Transport(); 82 83 public ContentProvider() { 84 } 85 86 /** 87 * Constructor just for mocking. 88 * 89 * @param context A Context object which should be some mock instance (like the 90 * instance of {@link android.test.mock.MockContext}). 91 * @param readPermission The read permision you want this instance should have in the 92 * test, which is available via {@link #getReadPermission()}. 93 * @param writePermission The write permission you want this instance should have 94 * in the test, which is available via {@link #getWritePermission()}. 95 * @param pathPermissions The PathPermissions you want this instance should have 96 * in the test, which is available via {@link #getPathPermissions()}. 97 * @hide 98 */ 99 public ContentProvider( 100 Context context, 101 String readPermission, 102 String writePermission, 103 PathPermission[] pathPermissions) { 104 mContext = context; 105 mReadPermission = readPermission; 106 mWritePermission = writePermission; 107 mPathPermissions = pathPermissions; 108 } 109 110 /** 111 * Given an IContentProvider, try to coerce it back to the real 112 * ContentProvider object if it is running in the local process. This can 113 * be used if you know you are running in the same process as a provider, 114 * and want to get direct access to its implementation details. Most 115 * clients should not nor have a reason to use it. 116 * 117 * @param abstractInterface The ContentProvider interface that is to be 118 * coerced. 119 * @return If the IContentProvider is non-null and local, returns its actual 120 * ContentProvider instance. Otherwise returns null. 121 * @hide 122 */ 123 public static ContentProvider coerceToLocalContentProvider( 124 IContentProvider abstractInterface) { 125 if (abstractInterface instanceof Transport) { 126 return ((Transport)abstractInterface).getContentProvider(); 127 } 128 return null; 129 } 130 131 /** 132 * Binder object that deals with remoting. 133 * 134 * @hide 135 */ 136 class Transport extends ContentProviderNative { 137 ContentProvider getContentProvider() { 138 return ContentProvider.this; 139 } 140 141 /** 142 * Remote version of a query, which returns an IBulkCursor. The bulk 143 * cursor should be wrapped with BulkCursorToCursorAdaptor before use. 144 */ 145 public IBulkCursor bulkQuery(Uri uri, String[] projection, 146 String selection, String[] selectionArgs, String sortOrder, 147 IContentObserver observer, CursorWindow window) { 148 enforceReadPermission(uri); 149 Cursor cursor = ContentProvider.this.query(uri, projection, 150 selection, selectionArgs, sortOrder); 151 if (cursor == null) { 152 return null; 153 } 154 return new CursorToBulkCursorAdaptor(cursor, observer, 155 ContentProvider.this.getClass().getName(), 156 hasWritePermission(uri), window); 157 } 158 159 public Cursor query(Uri uri, String[] projection, 160 String selection, String[] selectionArgs, String sortOrder) { 161 enforceReadPermission(uri); 162 return ContentProvider.this.query(uri, projection, selection, 163 selectionArgs, sortOrder); 164 } 165 166 public String getType(Uri uri) { 167 return ContentProvider.this.getType(uri); 168 } 169 170 171 public Uri insert(Uri uri, ContentValues initialValues) { 172 enforceWritePermission(uri); 173 return ContentProvider.this.insert(uri, initialValues); 174 } 175 176 public int bulkInsert(Uri uri, ContentValues[] initialValues) { 177 enforceWritePermission(uri); 178 return ContentProvider.this.bulkInsert(uri, initialValues); 179 } 180 181 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 182 throws OperationApplicationException { 183 for (ContentProviderOperation operation : operations) { 184 if (operation.isReadOperation()) { 185 enforceReadPermission(operation.getUri()); 186 } 187 188 if (operation.isWriteOperation()) { 189 enforceWritePermission(operation.getUri()); 190 } 191 } 192 return ContentProvider.this.applyBatch(operations); 193 } 194 195 public int delete(Uri uri, String selection, String[] selectionArgs) { 196 enforceWritePermission(uri); 197 return ContentProvider.this.delete(uri, selection, selectionArgs); 198 } 199 200 public int update(Uri uri, ContentValues values, String selection, 201 String[] selectionArgs) { 202 enforceWritePermission(uri); 203 return ContentProvider.this.update(uri, values, selection, selectionArgs); 204 } 205 206 public ParcelFileDescriptor openFile(Uri uri, String mode) 207 throws FileNotFoundException { 208 if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); 209 else enforceReadPermission(uri); 210 return ContentProvider.this.openFile(uri, mode); 211 } 212 213 public AssetFileDescriptor openAssetFile(Uri uri, String mode) 214 throws FileNotFoundException { 215 if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); 216 else enforceReadPermission(uri); 217 return ContentProvider.this.openAssetFile(uri, mode); 218 } 219 220 private void enforceReadPermission(Uri uri) { 221 final int uid = Binder.getCallingUid(); 222 if (uid == mMyUid) { 223 return; 224 } 225 226 final Context context = getContext(); 227 final String rperm = getReadPermission(); 228 final int pid = Binder.getCallingPid(); 229 if (rperm == null 230 || context.checkPermission(rperm, pid, uid) 231 == PackageManager.PERMISSION_GRANTED) { 232 return; 233 } 234 235 PathPermission[] pps = getPathPermissions(); 236 if (pps != null) { 237 final String path = uri.getPath(); 238 int i = pps.length; 239 while (i > 0) { 240 i--; 241 final PathPermission pp = pps[i]; 242 final String pprperm = pp.getReadPermission(); 243 if (pprperm != null && pp.match(path)) { 244 if (context.checkPermission(pprperm, pid, uid) 245 == PackageManager.PERMISSION_GRANTED) { 246 return; 247 } 248 } 249 } 250 } 251 252 if (context.checkUriPermission(uri, pid, uid, 253 Intent.FLAG_GRANT_READ_URI_PERMISSION) 254 == PackageManager.PERMISSION_GRANTED) { 255 return; 256 } 257 258 String msg = "Permission Denial: reading " 259 + ContentProvider.this.getClass().getName() 260 + " uri " + uri + " from pid=" + Binder.getCallingPid() 261 + ", uid=" + Binder.getCallingUid() 262 + " requires " + rperm; 263 throw new SecurityException(msg); 264 } 265 266 private boolean hasWritePermission(Uri uri) { 267 final int uid = Binder.getCallingUid(); 268 if (uid == mMyUid) { 269 return true; 270 } 271 272 final Context context = getContext(); 273 final String wperm = getWritePermission(); 274 final int pid = Binder.getCallingPid(); 275 if (wperm == null 276 || context.checkPermission(wperm, pid, uid) 277 == PackageManager.PERMISSION_GRANTED) { 278 return true; 279 } 280 281 PathPermission[] pps = getPathPermissions(); 282 if (pps != null) { 283 final String path = uri.getPath(); 284 int i = pps.length; 285 while (i > 0) { 286 i--; 287 final PathPermission pp = pps[i]; 288 final String ppwperm = pp.getWritePermission(); 289 if (ppwperm != null && pp.match(path)) { 290 if (context.checkPermission(ppwperm, pid, uid) 291 == PackageManager.PERMISSION_GRANTED) { 292 return true; 293 } 294 } 295 } 296 } 297 298 if (context.checkUriPermission(uri, pid, uid, 299 Intent.FLAG_GRANT_WRITE_URI_PERMISSION) 300 == PackageManager.PERMISSION_GRANTED) { 301 return true; 302 } 303 304 return false; 305 } 306 307 private void enforceWritePermission(Uri uri) { 308 if (hasWritePermission(uri)) { 309 return; 310 } 311 312 String msg = "Permission Denial: writing " 313 + ContentProvider.this.getClass().getName() 314 + " uri " + uri + " from pid=" + Binder.getCallingPid() 315 + ", uid=" + Binder.getCallingUid() 316 + " requires " + getWritePermission(); 317 throw new SecurityException(msg); 318 } 319 } 320 321 322 /** 323 * Retrieve the Context this provider is running in. Only available once 324 * onCreate(Map icicle) has been called -- this will be null in the 325 * constructor. 326 */ 327 public final Context getContext() { 328 return mContext; 329 } 330 331 /** 332 * Change the permission required to read data from the content 333 * provider. This is normally set for you from its manifest information 334 * when the provider is first created. 335 * 336 * @param permission Name of the permission required for read-only access. 337 */ 338 protected final void setReadPermission(String permission) { 339 mReadPermission = permission; 340 } 341 342 /** 343 * Return the name of the permission required for read-only access to 344 * this content provider. This method can be called from multiple 345 * threads, as described in 346 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 347 * Processes and Threads</a>. 348 */ 349 public final String getReadPermission() { 350 return mReadPermission; 351 } 352 353 /** 354 * Change the permission required to read and write data in the content 355 * provider. This is normally set for you from its manifest information 356 * when the provider is first created. 357 * 358 * @param permission Name of the permission required for read/write access. 359 */ 360 protected final void setWritePermission(String permission) { 361 mWritePermission = permission; 362 } 363 364 /** 365 * Return the name of the permission required for read/write access to 366 * this content provider. This method can be called from multiple 367 * threads, as described in 368 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 369 * Processes and Threads</a>. 370 */ 371 public final String getWritePermission() { 372 return mWritePermission; 373 } 374 375 /** 376 * Change the path-based permission required to read and/or write data in 377 * the content provider. This is normally set for you from its manifest 378 * information when the provider is first created. 379 * 380 * @param permissions Array of path permission descriptions. 381 */ 382 protected final void setPathPermissions(PathPermission[] permissions) { 383 mPathPermissions = permissions; 384 } 385 386 /** 387 * Return the path-based permissions required for read and/or write access to 388 * this content provider. This method can be called from multiple 389 * threads, as described in 390 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 391 * Processes and Threads</a>. 392 */ 393 public final PathPermission[] getPathPermissions() { 394 return mPathPermissions; 395 } 396 397 /** 398 * Called when the provider is being started. 399 * 400 * @return true if the provider was successfully loaded, false otherwise 401 */ 402 public abstract boolean onCreate(); 403 404 public void onConfigurationChanged(Configuration newConfig) { 405 } 406 407 public void onLowMemory() { 408 } 409 410 /** 411 * Receives a query request from a client in a local process, and 412 * returns a Cursor. This is called internally by the {@link ContentResolver}. 413 * This method can be called from multiple 414 * threads, as described in 415 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 416 * Processes and Threads</a>. 417 * <p> 418 * Example client call:<p> 419 * <pre>// Request a specific record. 420 * Cursor managedCursor = managedQuery( 421 ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2), 422 projection, // Which columns to return. 423 null, // WHERE clause. 424 null, // WHERE clause value substitution 425 People.NAME + " ASC"); // Sort order.</pre> 426 * Example implementation:<p> 427 * <pre>// SQLiteQueryBuilder is a helper class that creates the 428 // proper SQL syntax for us. 429 SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder(); 430 431 // Set the table we're querying. 432 qBuilder.setTables(DATABASE_TABLE_NAME); 433 434 // If the query ends in a specific record number, we're 435 // being asked for a specific record, so set the 436 // WHERE clause in our query. 437 if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){ 438 qBuilder.appendWhere("_id=" + uri.getPathLeafId()); 439 } 440 441 // Make the query. 442 Cursor c = qBuilder.query(mDb, 443 projection, 444 selection, 445 selectionArgs, 446 groupBy, 447 having, 448 sortOrder); 449 c.setNotificationUri(getContext().getContentResolver(), uri); 450 return c;</pre> 451 * 452 * @param uri The URI to query. This will be the full URI sent by the client; 453 * if the client is requesting a specific record, the URI will end in a record number 454 * that the implementation should parse and add to a WHERE or HAVING clause, specifying 455 * that _id value. 456 * @param projection The list of columns to put into the cursor. If 457 * null all columns are included. 458 * @param selection A selection criteria to apply when filtering rows. 459 * If null then all rows are included. 460 * @param selectionArgs You may include ?s in selection, which will be replaced by 461 * the values from selectionArgs, in order that they appear in the selection. 462 * The values will be bound as Strings. 463 * @param sortOrder How the rows in the cursor should be sorted. 464 * If null then the provider is free to define the sort order. 465 * @return a Cursor or null. 466 */ 467 public abstract Cursor query(Uri uri, String[] projection, 468 String selection, String[] selectionArgs, String sortOrder); 469 470 /** 471 * Return the MIME type of the data at the given URI. This should start with 472 * <code>vnd.android.cursor.item</code> for a single record, 473 * or <code>vnd.android.cursor.dir/</code> for multiple items. 474 * This method can be called from multiple 475 * threads, as described in 476 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 477 * Processes and Threads</a>. 478 * 479 * @param uri the URI to query. 480 * @return a MIME type string, or null if there is no type. 481 */ 482 public abstract String getType(Uri uri); 483 484 /** 485 * Implement this to insert a new row. 486 * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()} 487 * after inserting. 488 * This method can be called from multiple 489 * threads, as described in 490 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 491 * Processes and Threads</a>. 492 * @param uri The content:// URI of the insertion request. 493 * @param values A set of column_name/value pairs to add to the database. 494 * @return The URI for the newly inserted item. 495 */ 496 public abstract Uri insert(Uri uri, ContentValues values); 497 498 /** 499 * Implement this to insert a set of new rows, or the default implementation will 500 * iterate over the values and call {@link #insert} on each of them. 501 * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()} 502 * after inserting. 503 * This method can be called from multiple 504 * threads, as described in 505 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 506 * Processes and Threads</a>. 507 * 508 * @param uri The content:// URI of the insertion request. 509 * @param values An array of sets of column_name/value pairs to add to the database. 510 * @return The number of values that were inserted. 511 */ 512 public int bulkInsert(Uri uri, ContentValues[] values) { 513 int numValues = values.length; 514 for (int i = 0; i < numValues; i++) { 515 insert(uri, values[i]); 516 } 517 return numValues; 518 } 519 520 /** 521 * A request to delete one or more rows. The selection clause is applied when performing 522 * the deletion, allowing the operation to affect multiple rows in a 523 * directory. 524 * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()} 525 * after deleting. 526 * This method can be called from multiple 527 * threads, as described in 528 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 529 * Processes and Threads</a>. 530 * 531 * <p>The implementation is responsible for parsing out a row ID at the end 532 * of the URI, if a specific row is being deleted. That is, the client would 533 * pass in <code>content://contacts/people/22</code> and the implementation is 534 * responsible for parsing the record number (22) when creating a SQL statement. 535 * 536 * @param uri The full URI to query, including a row ID (if a specific record is requested). 537 * @param selection An optional restriction to apply to rows when deleting. 538 * @return The number of rows affected. 539 * @throws SQLException 540 */ 541 public abstract int delete(Uri uri, String selection, String[] selectionArgs); 542 543 /** 544 * Update a content URI. All rows matching the optionally provided selection 545 * will have their columns listed as the keys in the values map with the 546 * values of those keys. 547 * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()} 548 * after updating. 549 * This method can be called from multiple 550 * threads, as described in 551 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 552 * Processes and Threads</a>. 553 * 554 * @param uri The URI to query. This can potentially have a record ID if this 555 * is an update request for a specific record. 556 * @param values A Bundle mapping from column names to new column values (NULL is a 557 * valid value). 558 * @param selection An optional filter to match rows to update. 559 * @return the number of rows affected. 560 */ 561 public abstract int update(Uri uri, ContentValues values, String selection, 562 String[] selectionArgs); 563 564 /** 565 * Open a file blob associated with a content URI. 566 * This method can be called from multiple 567 * threads, as described in 568 * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: 569 * Processes and Threads</a>. 570 * 571 * <p>Returns a 572 * ParcelFileDescriptor, from which you can obtain a 573 * {@link java.io.FileDescriptor} for use with 574 * {@link java.io.FileInputStream}, {@link java.io.FileOutputStream}, etc. 575 * This can be used to store large data (such as an image) associated with 576 * a particular piece of content. 577 * 578 * <p>The returned ParcelFileDescriptor is owned by the caller, so it is 579 * their responsibility to close it when done. That is, the implementation 580 * of this method should create a new ParcelFileDescriptor for each call. 581 * 582 * @param uri The URI whose file is to be opened. 583 * @param mode Access mode for the file. May be "r" for read-only access, 584 * "rw" for read and write access, or "rwt" for read and write access 585 * that truncates any existing file. 586 * 587 * @return Returns a new ParcelFileDescriptor which you can use to access 588 * the file. 589 * 590 * @throws FileNotFoundException Throws FileNotFoundException if there is 591 * no file associated with the given URI or the mode is invalid. 592 * @throws SecurityException Throws SecurityException if the caller does 593 * not have permission to access the file. 594 * 595 * @see #openAssetFile(Uri, String) 596 * @see #openFileHelper(Uri, String) 597 */ 598 public ParcelFileDescriptor openFile(Uri uri, String mode) 599 throws FileNotFoundException { 600 throw new FileNotFoundException("No files supported by provider at " 601 + uri); 602 } 603 604 /** 605 * This is like {@link #openFile}, but can be implemented by providers 606 * that need to be able to return sub-sections of files, often assets 607 * inside of their .apk. Note that when implementing this your clients 608 * must be able to deal with such files, either directly with 609 * {@link ContentResolver#openAssetFileDescriptor 610 * ContentResolver.openAssetFileDescriptor}, or by using the higher-level 611 * {@link ContentResolver#openInputStream ContentResolver.openInputStream} 612 * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream} 613 * methods. 614 * 615 * <p><em>Note: if you are implementing this to return a full file, you 616 * should create the AssetFileDescriptor with 617 * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with 618 * applications that can not handle sub-sections of files.</em></p> 619 * 620 * @param uri The URI whose file is to be opened. 621 * @param mode Access mode for the file. May be "r" for read-only access, 622 * "w" for write-only access (erasing whatever data is currently in 623 * the file), "wa" for write-only access to append to any existing data, 624 * "rw" for read and write access on any existing data, and "rwt" for read 625 * and write access that truncates any existing file. 626 * 627 * @return Returns a new AssetFileDescriptor which you can use to access 628 * the file. 629 * 630 * @throws FileNotFoundException Throws FileNotFoundException if there is 631 * no file associated with the given URI or the mode is invalid. 632 * @throws SecurityException Throws SecurityException if the caller does 633 * not have permission to access the file. 634 * 635 * @see #openFile(Uri, String) 636 * @see #openFileHelper(Uri, String) 637 */ 638 public AssetFileDescriptor openAssetFile(Uri uri, String mode) 639 throws FileNotFoundException { 640 ParcelFileDescriptor fd = openFile(uri, mode); 641 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 642 } 643 644 /** 645 * Convenience for subclasses that wish to implement {@link #openFile} 646 * by looking up a column named "_data" at the given URI. 647 * 648 * @param uri The URI to be opened. 649 * @param mode The file mode. May be "r" for read-only access, 650 * "w" for write-only access (erasing whatever data is currently in 651 * the file), "wa" for write-only access to append to any existing data, 652 * "rw" for read and write access on any existing data, and "rwt" for read 653 * and write access that truncates any existing file. 654 * 655 * @return Returns a new ParcelFileDescriptor that can be used by the 656 * client to access the file. 657 */ 658 protected final ParcelFileDescriptor openFileHelper(Uri uri, 659 String mode) throws FileNotFoundException { 660 Cursor c = query(uri, new String[]{"_data"}, null, null, null); 661 int count = (c != null) ? c.getCount() : 0; 662 if (count != 1) { 663 // If there is not exactly one result, throw an appropriate 664 // exception. 665 if (c != null) { 666 c.close(); 667 } 668 if (count == 0) { 669 throw new FileNotFoundException("No entry for " + uri); 670 } 671 throw new FileNotFoundException("Multiple items at " + uri); 672 } 673 674 c.moveToFirst(); 675 int i = c.getColumnIndex("_data"); 676 String path = (i >= 0 ? c.getString(i) : null); 677 c.close(); 678 if (path == null) { 679 throw new FileNotFoundException("Column _data not found."); 680 } 681 682 int modeBits = ContentResolver.modeToMode(uri, mode); 683 return ParcelFileDescriptor.open(new File(path), modeBits); 684 } 685 686 /** 687 * Returns true if this instance is a temporary content provider. 688 * @return true if this instance is a temporary content provider 689 */ 690 protected boolean isTemporary() { 691 return false; 692 } 693 694 /** 695 * Returns the Binder object for this provider. 696 * 697 * @return the Binder object for this provider 698 * @hide 699 */ 700 public IContentProvider getIContentProvider() { 701 return mTransport; 702 } 703 704 /** 705 * After being instantiated, this is called to tell the content provider 706 * about itself. 707 * 708 * @param context The context this provider is running in 709 * @param info Registered information about this content provider 710 */ 711 public void attachInfo(Context context, ProviderInfo info) { 712 713 /* 714 * Only allow it to be set once, so after the content service gives 715 * this to us clients can't change it. 716 */ 717 if (mContext == null) { 718 mContext = context; 719 mMyUid = Process.myUid(); 720 if (info != null) { 721 setReadPermission(info.readPermission); 722 setWritePermission(info.writePermission); 723 setPathPermissions(info.pathPermissions); 724 } 725 ContentProvider.this.onCreate(); 726 } 727 } 728 729 /** 730 * Applies each of the {@link ContentProviderOperation} objects and returns an array 731 * of their results. Passes through OperationApplicationException, which may be thrown 732 * by the call to {@link ContentProviderOperation#apply}. 733 * If all the applications succeed then a {@link ContentProviderResult} array with the 734 * same number of elements as the operations will be returned. It is implementation-specific 735 * how many, if any, operations will have been successfully applied if a call to 736 * apply results in a {@link OperationApplicationException}. 737 * @param operations the operations to apply 738 * @return the results of the applications 739 * @throws OperationApplicationException thrown if an application fails. 740 * See {@link ContentProviderOperation#apply} for more information. 741 */ 742 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 743 throws OperationApplicationException { 744 final int numOperations = operations.size(); 745 final ContentProviderResult[] results = new ContentProviderResult[numOperations]; 746 for (int i = 0; i < numOperations; i++) { 747 results[i] = operations.get(i).apply(this, results, i); 748 } 749 return results; 750 } 751}