ContentResolver.java revision 718d8a2d7ff3e864a73879eb646f46c14ab74d07
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.NameNotFoundException; 20import android.content.res.AssetFileDescriptor; 21import android.content.res.Resources; 22import android.database.ContentObserver; 23import android.database.Cursor; 24import android.database.CursorWrapper; 25import android.database.IContentObserver; 26import android.net.Uri; 27import android.os.Bundle; 28import android.os.ParcelFileDescriptor; 29import android.os.RemoteException; 30import android.text.TextUtils; 31import android.accounts.Account; 32 33import java.io.File; 34import java.io.FileInputStream; 35import java.io.FileNotFoundException; 36import java.io.IOException; 37import java.io.InputStream; 38import java.io.OutputStream; 39import java.util.List; 40 41 42/** 43 * This class provides applications access to the content model. 44 */ 45public abstract class ContentResolver { 46 public final static String SYNC_EXTRAS_ACCOUNT = "account"; 47 public static final String SYNC_EXTRAS_EXPEDITED = "expedited"; 48 public static final String SYNC_EXTRAS_FORCE = "force"; 49 public static final String SYNC_EXTRAS_UPLOAD = "upload"; 50 public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override"; 51 public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions"; 52 53 public static final String SCHEME_CONTENT = "content"; 54 public static final String SCHEME_ANDROID_RESOURCE = "android.resource"; 55 public static final String SCHEME_FILE = "file"; 56 57 /** 58 * This is the Android platform's base MIME type for a content: URI 59 * containing a Cursor of a single item. Applications should use this 60 * as the base type along with their own sub-type of their content: URIs 61 * that represent a particular item. For example, hypothetical IMAP email 62 * client may have a URI 63 * <code>content://com.company.provider.imap/inbox/1</code> for a particular 64 * message in the inbox, whose MIME type would be reported as 65 * <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code> 66 * 67 * <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}. 68 */ 69 public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item"; 70 71 /** 72 * This is the Android platform's base MIME type for a content: URI 73 * containing a Cursor of zero or more items. Applications should use this 74 * as the base type along with their own sub-type of their content: URIs 75 * that represent a directory of items. For example, hypothetical IMAP email 76 * client may have a URI 77 * <code>content://com.company.provider.imap/inbox</code> for all of the 78 * messages in its inbox, whose MIME type would be reported as 79 * <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code> 80 * 81 * <p>Note how the base MIME type varies between this and 82 * {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is 83 * one single item or multiple items in the data set, while the sub-type 84 * remains the same because in either case the data structure contained 85 * in the cursor is the same. 86 */ 87 public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir"; 88 89 public ContentResolver(Context context) 90 { 91 mContext = context; 92 } 93 94 /** @hide */ 95 protected abstract IContentProvider acquireProvider(Context c, String name); 96 /** @hide */ 97 public abstract boolean releaseProvider(IContentProvider icp); 98 99 /** 100 * Return the MIME type of the given content URL. 101 * 102 * @param url A Uri identifying content (either a list or specific type), 103 * using the content:// scheme. 104 * @return A MIME type for the content, or null if the URL is invalid or the type is unknown 105 */ 106 public final String getType(Uri url) 107 { 108 IContentProvider provider = acquireProvider(url); 109 if (provider == null) { 110 return null; 111 } 112 try { 113 return provider.getType(url); 114 } catch (RemoteException e) { 115 return null; 116 } catch (java.lang.Exception e) { 117 return null; 118 } finally { 119 releaseProvider(provider); 120 } 121 } 122 123 /** 124 * Query the given URI, returning a {@link Cursor} over the result set. 125 * 126 * @param uri The URI, using the content:// scheme, for the content to 127 * retrieve. 128 * @param projection A list of which columns to return. Passing null will 129 * return all columns, which is discouraged to prevent reading data 130 * from storage that isn't going to be used. 131 * @param selection A filter declaring which rows to return, formatted as an 132 * SQL WHERE clause (excluding the WHERE itself). Passing null will 133 * return all rows for the given URI. 134 * @param selectionArgs You may include ?s in selection, which will be 135 * replaced by the values from selectionArgs, in the order that they 136 * appear in the selection. The values will be bound as Strings. 137 * @param sortOrder How to order the rows, formatted as an SQL ORDER BY 138 * clause (excluding the ORDER BY itself). Passing null will use the 139 * default sort order, which may be unordered. 140 * @return A Cursor object, which is positioned before the first entry, or null 141 * @see Cursor 142 */ 143 public final Cursor query(Uri uri, String[] projection, 144 String selection, String[] selectionArgs, String sortOrder) { 145 IContentProvider provider = acquireProvider(uri); 146 if (provider == null) { 147 return null; 148 } 149 try { 150 Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder); 151 if(qCursor == null) { 152 releaseProvider(provider); 153 return null; 154 } 155 //Wrap the cursor object into CursorWrapperInner object 156 return new CursorWrapperInner(qCursor, provider); 157 } catch (RemoteException e) { 158 releaseProvider(provider); 159 return null; 160 } catch(RuntimeException e) { 161 releaseProvider(provider); 162 throw e; 163 } 164 } 165 166 /** 167 * Open a stream on to the content associated with a content URI. If there 168 * is no data associated with the URI, FileNotFoundException is thrown. 169 * 170 * <h5>Accepts the following URI schemes:</h5> 171 * <ul> 172 * <li>content ({@link #SCHEME_CONTENT})</li> 173 * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li> 174 * <li>file ({@link #SCHEME_FILE})</li> 175 * </ul> 176 * 177 * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information 178 * on these schemes. 179 * 180 * @param uri The desired URI. 181 * @return InputStream 182 * @throws FileNotFoundException if the provided URI could not be opened. 183 * @see #openAssetFileDescriptor(Uri, String) 184 */ 185 public final InputStream openInputStream(Uri uri) 186 throws FileNotFoundException { 187 String scheme = uri.getScheme(); 188 if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { 189 // Note: left here to avoid breaking compatibility. May be removed 190 // with sufficient testing. 191 OpenResourceIdResult r = getResourceId(uri); 192 try { 193 InputStream stream = r.r.openRawResource(r.id); 194 return stream; 195 } catch (Resources.NotFoundException ex) { 196 throw new FileNotFoundException("Resource does not exist: " + uri); 197 } 198 } else if (SCHEME_FILE.equals(scheme)) { 199 // Note: left here to avoid breaking compatibility. May be removed 200 // with sufficient testing. 201 return new FileInputStream(uri.getPath()); 202 } else { 203 AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r"); 204 try { 205 return fd != null ? fd.createInputStream() : null; 206 } catch (IOException e) { 207 throw new FileNotFoundException("Unable to create stream"); 208 } 209 } 210 } 211 212 /** 213 * Synonym for {@link #openOutputStream(Uri, String) 214 * openOutputStream(uri, "w")}. 215 * @throws FileNotFoundException if the provided URI could not be opened. 216 */ 217 public final OutputStream openOutputStream(Uri uri) 218 throws FileNotFoundException { 219 return openOutputStream(uri, "w"); 220 } 221 222 /** 223 * Open a stream on to the content associated with a content URI. If there 224 * is no data associated with the URI, FileNotFoundException is thrown. 225 * 226 * <h5>Accepts the following URI schemes:</h5> 227 * <ul> 228 * <li>content ({@link #SCHEME_CONTENT})</li> 229 * <li>file ({@link #SCHEME_FILE})</li> 230 * </ul> 231 * 232 * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information 233 * on these schemes. 234 * 235 * @param uri The desired URI. 236 * @param mode May be "w", "wa", "rw", or "rwt". 237 * @return OutputStream 238 * @throws FileNotFoundException if the provided URI could not be opened. 239 * @see #openAssetFileDescriptor(Uri, String) 240 */ 241 public final OutputStream openOutputStream(Uri uri, String mode) 242 throws FileNotFoundException { 243 AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode); 244 try { 245 return fd != null ? fd.createOutputStream() : null; 246 } catch (IOException e) { 247 throw new FileNotFoundException("Unable to create stream"); 248 } 249 } 250 251 /** 252 * Open a raw file descriptor to access data under a "content:" URI. This 253 * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the 254 * underlying {@link ContentProvider#openFile} 255 * ContentProvider.openFile()} method, so will <em>not</em> work with 256 * providers that return sub-sections of files. If at all possible, 257 * you should use {@link #openAssetFileDescriptor(Uri, String)}. You 258 * will receive a FileNotFoundException exception if the provider returns a 259 * sub-section of a file. 260 * 261 * <h5>Accepts the following URI schemes:</h5> 262 * <ul> 263 * <li>content ({@link #SCHEME_CONTENT})</li> 264 * <li>file ({@link #SCHEME_FILE})</li> 265 * </ul> 266 * 267 * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information 268 * on these schemes. 269 * 270 * @param uri The desired URI to open. 271 * @param mode The file mode to use, as per {@link ContentProvider#openFile 272 * ContentProvider.openFile}. 273 * @return Returns a new ParcelFileDescriptor pointing to the file. You 274 * own this descriptor and are responsible for closing it when done. 275 * @throws FileNotFoundException Throws FileNotFoundException of no 276 * file exists under the URI or the mode is invalid. 277 * @see #openAssetFileDescriptor(Uri, String) 278 */ 279 public final ParcelFileDescriptor openFileDescriptor(Uri uri, 280 String mode) throws FileNotFoundException { 281 AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode); 282 if (afd == null) { 283 return null; 284 } 285 286 if (afd.getDeclaredLength() < 0) { 287 // This is a full file! 288 return afd.getParcelFileDescriptor(); 289 } 290 291 // Client can't handle a sub-section of a file, so close what 292 // we got and bail with an exception. 293 try { 294 afd.close(); 295 } catch (IOException e) { 296 } 297 298 throw new FileNotFoundException("Not a whole file"); 299 } 300 301 /** 302 * Open a raw file descriptor to access data under a "content:" URI. This 303 * interacts with the underlying {@link ContentProvider#openAssetFile} 304 * ContentProvider.openAssetFile()} method of the provider associated with the 305 * given URI, to retrieve any file stored there. 306 * 307 * <h5>Accepts the following URI schemes:</h5> 308 * <ul> 309 * <li>content ({@link #SCHEME_CONTENT})</li> 310 * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li> 311 * <li>file ({@link #SCHEME_FILE})</li> 312 * </ul> 313 * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5> 314 * <p> 315 * A Uri object can be used to reference a resource in an APK file. The 316 * Uri should be one of the following formats: 317 * <ul> 318 * <li><code>android.resource://package_name/id_number</code><br/> 319 * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. 320 * For example <code>com.example.myapp</code><br/> 321 * <code>id_number</code> is the int form of the ID.<br/> 322 * The easiest way to construct this form is 323 * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre> 324 * </li> 325 * <li><code>android.resource://package_name/type/name</code><br/> 326 * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. 327 * For example <code>com.example.myapp</code><br/> 328 * <code>type</code> is the string form of the resource type. For example, <code>raw</code> 329 * or <code>drawable</code>. 330 * <code>name</code> is the string form of the resource name. That is, whatever the file 331 * name was in your res directory, without the type extension. 332 * The easiest way to construct this form is 333 * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre> 334 * </li> 335 * </ul> 336 * 337 * @param uri The desired URI to open. 338 * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile 339 * ContentProvider.openAssetFile}. 340 * @return Returns a new ParcelFileDescriptor pointing to the file. You 341 * own this descriptor and are responsible for closing it when done. 342 * @throws FileNotFoundException Throws FileNotFoundException of no 343 * file exists under the URI or the mode is invalid. 344 */ 345 public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, 346 String mode) throws FileNotFoundException { 347 String scheme = uri.getScheme(); 348 if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { 349 if (!"r".equals(mode)) { 350 throw new FileNotFoundException("Can't write resources: " + uri); 351 } 352 OpenResourceIdResult r = getResourceId(uri); 353 try { 354 return r.r.openRawResourceFd(r.id); 355 } catch (Resources.NotFoundException ex) { 356 throw new FileNotFoundException("Resource does not exist: " + uri); 357 } 358 } else if (SCHEME_FILE.equals(scheme)) { 359 ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 360 new File(uri.getPath()), modeToMode(uri, mode)); 361 return new AssetFileDescriptor(pfd, 0, -1); 362 } else { 363 IContentProvider provider = acquireProvider(uri); 364 if (provider == null) { 365 throw new FileNotFoundException("No content provider: " + uri); 366 } 367 try { 368 AssetFileDescriptor fd = provider.openAssetFile(uri, mode); 369 if(fd == null) { 370 releaseProvider(provider); 371 return null; 372 } 373 ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( 374 fd.getParcelFileDescriptor(), provider); 375 return new AssetFileDescriptor(pfd, fd.getStartOffset(), 376 fd.getDeclaredLength()); 377 } catch (RemoteException e) { 378 releaseProvider(provider); 379 throw new FileNotFoundException("Dead content provider: " + uri); 380 } catch (FileNotFoundException e) { 381 releaseProvider(provider); 382 throw e; 383 } catch (RuntimeException e) { 384 releaseProvider(provider); 385 throw e; 386 } 387 } 388 } 389 390 class OpenResourceIdResult { 391 Resources r; 392 int id; 393 } 394 395 OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException { 396 String authority = uri.getAuthority(); 397 Resources r; 398 if (TextUtils.isEmpty(authority)) { 399 throw new FileNotFoundException("No authority: " + uri); 400 } else { 401 try { 402 r = mContext.getPackageManager().getResourcesForApplication(authority); 403 } catch (NameNotFoundException ex) { 404 throw new FileNotFoundException("No package found for authority: " + uri); 405 } 406 } 407 List<String> path = uri.getPathSegments(); 408 if (path == null) { 409 throw new FileNotFoundException("No path: " + uri); 410 } 411 int len = path.size(); 412 int id; 413 if (len == 1) { 414 try { 415 id = Integer.parseInt(path.get(0)); 416 } catch (NumberFormatException e) { 417 throw new FileNotFoundException("Single path segment is not a resource ID: " + uri); 418 } 419 } else if (len == 2) { 420 id = r.getIdentifier(path.get(1), path.get(0), authority); 421 } else { 422 throw new FileNotFoundException("More than two path segments: " + uri); 423 } 424 if (id == 0) { 425 throw new FileNotFoundException("No resource found for: " + uri); 426 } 427 OpenResourceIdResult res = new OpenResourceIdResult(); 428 res.r = r; 429 res.id = id; 430 return res; 431 } 432 433 /** @hide */ 434 static public int modeToMode(Uri uri, String mode) throws FileNotFoundException { 435 int modeBits; 436 if ("r".equals(mode)) { 437 modeBits = ParcelFileDescriptor.MODE_READ_ONLY; 438 } else if ("w".equals(mode) || "wt".equals(mode)) { 439 modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY 440 | ParcelFileDescriptor.MODE_CREATE 441 | ParcelFileDescriptor.MODE_TRUNCATE; 442 } else if ("wa".equals(mode)) { 443 modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY 444 | ParcelFileDescriptor.MODE_CREATE 445 | ParcelFileDescriptor.MODE_APPEND; 446 } else if ("rw".equals(mode)) { 447 modeBits = ParcelFileDescriptor.MODE_READ_WRITE 448 | ParcelFileDescriptor.MODE_CREATE; 449 } else if ("rwt".equals(mode)) { 450 modeBits = ParcelFileDescriptor.MODE_READ_WRITE 451 | ParcelFileDescriptor.MODE_CREATE 452 | ParcelFileDescriptor.MODE_TRUNCATE; 453 } else { 454 throw new FileNotFoundException("Bad mode for " + uri + ": " 455 + mode); 456 } 457 return modeBits; 458 } 459 460 /** 461 * Inserts a row into a table at the given URL. 462 * 463 * If the content provider supports transactions the insertion will be atomic. 464 * 465 * @param url The URL of the table to insert into. 466 * @param values The initial values for the newly inserted row. The key is the column name for 467 * the field. Passing an empty ContentValues will create an empty row. 468 * @return the URL of the newly created row. 469 */ 470 public final Uri insert(Uri url, ContentValues values) 471 { 472 IContentProvider provider = acquireProvider(url); 473 if (provider == null) { 474 throw new IllegalArgumentException("Unknown URL " + url); 475 } 476 try { 477 return provider.insert(url, values); 478 } catch (RemoteException e) { 479 return null; 480 } finally { 481 releaseProvider(provider); 482 } 483 } 484 485 /** 486 * Inserts multiple rows into a table at the given URL. 487 * 488 * This function make no guarantees about the atomicity of the insertions. 489 * 490 * @param url The URL of the table to insert into. 491 * @param values The initial values for the newly inserted rows. The key is the column name for 492 * the field. Passing null will create an empty row. 493 * @return the number of newly created rows. 494 */ 495 public final int bulkInsert(Uri url, ContentValues[] values) 496 { 497 IContentProvider provider = acquireProvider(url); 498 if (provider == null) { 499 throw new IllegalArgumentException("Unknown URL " + url); 500 } 501 try { 502 return provider.bulkInsert(url, values); 503 } catch (RemoteException e) { 504 return 0; 505 } finally { 506 releaseProvider(provider); 507 } 508 } 509 510 /** 511 * Deletes row(s) specified by a content URI. 512 * 513 * If the content provider supports transactions, the deletion will be atomic. 514 * 515 * @param url The URL of the row to delete. 516 * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause 517 (excluding the WHERE itself). 518 * @return The number of rows deleted. 519 */ 520 public final int delete(Uri url, String where, String[] selectionArgs) 521 { 522 IContentProvider provider = acquireProvider(url); 523 if (provider == null) { 524 throw new IllegalArgumentException("Unknown URL " + url); 525 } 526 try { 527 return provider.delete(url, where, selectionArgs); 528 } catch (RemoteException e) { 529 return -1; 530 } finally { 531 releaseProvider(provider); 532 } 533 } 534 535 /** 536 * Update row(s) in a content URI. 537 * 538 * If the content provider supports transactions the update will be atomic. 539 * 540 * @param uri The URI to modify. 541 * @param values The new field values. The key is the column name for the field. 542 A null value will remove an existing field value. 543 * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause 544 (excluding the WHERE itself). 545 * @return The number of rows updated. 546 * @throws NullPointerException if uri or values are null 547 */ 548 public final int update(Uri uri, ContentValues values, String where, 549 String[] selectionArgs) { 550 IContentProvider provider = acquireProvider(uri); 551 if (provider == null) { 552 throw new IllegalArgumentException("Unknown URI " + uri); 553 } 554 try { 555 return provider.update(uri, values, where, selectionArgs); 556 } catch (RemoteException e) { 557 return -1; 558 } finally { 559 releaseProvider(provider); 560 } 561 } 562 563 /** 564 * Returns the content provider for the given content URI.. 565 * 566 * @param uri The URI to a content provider 567 * @return The ContentProvider for the given URI, or null if no content provider is found. 568 * @hide 569 */ 570 public final IContentProvider acquireProvider(Uri uri) 571 { 572 if (!SCHEME_CONTENT.equals(uri.getScheme())) { 573 return null; 574 } 575 String auth = uri.getAuthority(); 576 if (auth != null) { 577 return acquireProvider(mContext, uri.getAuthority()); 578 } 579 return null; 580 } 581 582 /** 583 * @hide 584 */ 585 public final IContentProvider acquireProvider(String name) { 586 if(name == null) { 587 return null; 588 } 589 return acquireProvider(mContext, name); 590 } 591 592 /** 593 * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider} 594 * that services the content at uri, starting the provider if necessary. Returns 595 * null if there is no provider associated wih the uri. The caller must indicate that they are 596 * done with the provider by calling {@link ContentProviderClient#release} which will allow 597 * the system to release the provider it it determines that there is no other reason for 598 * keeping it active. 599 * @param uri specifies which provider should be acquired 600 * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider} 601 * that services the content at uri or null if there isn't one. 602 */ 603 public final ContentProviderClient acquireContentProviderClient(Uri uri) { 604 IContentProvider provider = acquireProvider(uri); 605 if (provider != null) { 606 return new ContentProviderClient(this, provider); 607 } 608 609 return null; 610 } 611 612 /** 613 * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider} 614 * with the authority of name, starting the provider if necessary. Returns 615 * null if there is no provider associated wih the uri. The caller must indicate that they are 616 * done with the provider by calling {@link ContentProviderClient#release} which will allow 617 * the system to release the provider it it determines that there is no other reason for 618 * keeping it active. 619 * @param name specifies which provider should be acquired 620 * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider} 621 * with the authority of name or null if there isn't one. 622 */ 623 public final ContentProviderClient acquireContentProviderClient(String name) { 624 IContentProvider provider = acquireProvider(name); 625 if (provider != null) { 626 return new ContentProviderClient(this, provider); 627 } 628 629 return null; 630 } 631 632 /** 633 * Register an observer class that gets callbacks when data identified by a 634 * given content URI changes. 635 * 636 * @param uri The URI to watch for changes. This can be a specific row URI, or a base URI 637 * for a whole class of content. 638 * @param notifyForDescendents If <code>true</code> changes to URIs beginning with <code>uri</code> 639 * will also cause notifications to be sent. If <code>false</code> only changes to the exact URI 640 * specified by <em>uri</em> will cause notifications to be sent. If true, than any URI values 641 * at or below the specified URI will also trigger a match. 642 * @param observer The object that receives callbacks when changes occur. 643 * @see #unregisterContentObserver 644 */ 645 public final void registerContentObserver(Uri uri, boolean notifyForDescendents, 646 ContentObserver observer) 647 { 648 try { 649 ContentServiceNative.getDefault().registerContentObserver(uri, notifyForDescendents, 650 observer.getContentObserver()); 651 } catch (RemoteException e) { 652 } 653 } 654 655 /** 656 * Unregisters a change observer. 657 * 658 * @param observer The previously registered observer that is no longer needed. 659 * @see #registerContentObserver 660 */ 661 public final void unregisterContentObserver(ContentObserver observer) { 662 try { 663 IContentObserver contentObserver = observer.releaseContentObserver(); 664 if (contentObserver != null) { 665 ContentServiceNative.getDefault().unregisterContentObserver( 666 contentObserver); 667 } 668 } catch (RemoteException e) { 669 } 670 } 671 672 /** 673 * Notify registered observers that a row was updated. 674 * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. 675 * By default, CursorAdapter objects will get this notification. 676 * 677 * @param uri 678 * @param observer The observer that originated the change, may be <code>null</null> 679 */ 680 public void notifyChange(Uri uri, ContentObserver observer) { 681 notifyChange(uri, observer, true /* sync to network */); 682 } 683 684 /** 685 * Notify registered observers that a row was updated. 686 * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. 687 * By default, CursorAdapter objects will get this notification. 688 * 689 * @param uri 690 * @param observer The observer that originated the change, may be <code>null</null> 691 * @param syncToNetwork If true, attempt to sync the change to the network. 692 */ 693 public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { 694 try { 695 ContentServiceNative.getDefault().notifyChange( 696 uri, observer == null ? null : observer.getContentObserver(), 697 observer != null && observer.deliverSelfNotifications(), syncToNetwork); 698 } catch (RemoteException e) { 699 } 700 } 701 702 /** 703 * Start an asynchronous sync operation. If you want to monitor the progress 704 * of the sync you may register a SyncObserver. Only values of the following 705 * types may be used in the extras bundle: 706 * <ul> 707 * <li>Integer</li> 708 * <li>Long</li> 709 * <li>Boolean</li> 710 * <li>Float</li> 711 * <li>Double</li> 712 * <li>String</li> 713 * </ul> 714 * 715 * @param uri the uri of the provider to sync or null to sync all providers. 716 * @param extras any extras to pass to the SyncAdapter. 717 */ 718 public void startSync(Uri uri, Bundle extras) { 719 validateSyncExtrasBundle(extras); 720 try { 721 ContentServiceNative.getDefault().startSync(uri, extras); 722 } catch (RemoteException e) { 723 } 724 } 725 726 /** 727 * Check that only values of the following types are in the Bundle: 728 * <ul> 729 * <li>Integer</li> 730 * <li>Long</li> 731 * <li>Boolean</li> 732 * <li>Float</li> 733 * <li>Double</li> 734 * <li>String</li> 735 * <li>Account</li> 736 * <li>null</li> 737 * </ul> 738 * @param extras the Bundle to check 739 */ 740 public static void validateSyncExtrasBundle(Bundle extras) { 741 try { 742 for (String key : extras.keySet()) { 743 Object value = extras.get(key); 744 if (value == null) continue; 745 if (value instanceof Long) continue; 746 if (value instanceof Integer) continue; 747 if (value instanceof Boolean) continue; 748 if (value instanceof Float) continue; 749 if (value instanceof Double) continue; 750 if (value instanceof String) continue; 751 if (value instanceof Account) continue; 752 throw new IllegalArgumentException("unexpected value type: " 753 + value.getClass().getName()); 754 } 755 } catch (IllegalArgumentException e) { 756 throw e; 757 } catch (RuntimeException exc) { 758 throw new IllegalArgumentException("error unparceling Bundle", exc); 759 } 760 } 761 762 public void cancelSync(Uri uri) { 763 try { 764 ContentServiceNative.getDefault().cancelSync(uri); 765 } catch (RemoteException e) { 766 } 767 } 768 769 private final class CursorWrapperInner extends CursorWrapper { 770 private IContentProvider mContentProvider; 771 public static final String TAG="CursorWrapperInner"; 772 private boolean mCloseFlag = false; 773 774 CursorWrapperInner(Cursor cursor, IContentProvider icp) { 775 super(cursor); 776 mContentProvider = icp; 777 } 778 779 @Override 780 public void close() { 781 super.close(); 782 ContentResolver.this.releaseProvider(mContentProvider); 783 mCloseFlag = true; 784 } 785 786 @Override 787 protected void finalize() throws Throwable { 788 try { 789 if(!mCloseFlag) { 790 ContentResolver.this.releaseProvider(mContentProvider); 791 } 792 } finally { 793 super.finalize(); 794 } 795 } 796 } 797 798 private final class ParcelFileDescriptorInner extends ParcelFileDescriptor { 799 private IContentProvider mContentProvider; 800 public static final String TAG="ParcelFileDescriptorInner"; 801 private boolean mReleaseProviderFlag = false; 802 803 ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) { 804 super(pfd); 805 mContentProvider = icp; 806 } 807 808 @Override 809 public void close() throws IOException { 810 if(!mReleaseProviderFlag) { 811 super.close(); 812 ContentResolver.this.releaseProvider(mContentProvider); 813 mReleaseProviderFlag = true; 814 } 815 } 816 817 @Override 818 protected void finalize() throws Throwable { 819 if (!mReleaseProviderFlag) { 820 close(); 821 } 822 } 823 } 824 825 private final Context mContext; 826 private static final String TAG = "ContentResolver"; 827} 828