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