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