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