ContentResolver.java revision 718671b441c6318276e6d954a41a95db0d7e6c49
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.IBinder;
29import android.os.ParcelFileDescriptor;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.text.TextUtils;
33import android.accounts.Account;
34import android.util.Config;
35import android.util.Log;
36
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileNotFoundException;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.OutputStream;
43import java.util.List;
44import java.util.ArrayList;
45
46
47/**
48 * This class provides applications access to the content model.
49 */
50public abstract class ContentResolver {
51    /**
52     * @deprecated instead use
53     * {@link #requestSync(android.accounts.Account, String, android.os.Bundle)}
54     */
55    public static final String SYNC_EXTRAS_ACCOUNT = "account";
56    public static final String SYNC_EXTRAS_EXPEDITED = "expedited";
57    /**
58     * @deprecated instead use
59     * {@link #SYNC_EXTRAS_MANUAL}
60     */
61    public static final String SYNC_EXTRAS_FORCE = "force";
62    public static final String SYNC_EXTRAS_MANUAL = "force";
63    public static final String SYNC_EXTRAS_UPLOAD = "upload";
64    public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
65    public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
66
67    public static final String SCHEME_CONTENT = "content";
68    public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
69    public static final String SCHEME_FILE = "file";
70
71    /**
72     * This is the Android platform's base MIME type for a content: URI
73     * containing a Cursor of a single item.  Applications should use this
74     * as the base type along with their own sub-type of their content: URIs
75     * that represent a particular item.  For example, hypothetical IMAP email
76     * client may have a URI
77     * <code>content://com.company.provider.imap/inbox/1</code> for a particular
78     * message in the inbox, whose MIME type would be reported as
79     * <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code>
80     *
81     * <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}.
82     */
83    public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
84
85    /**
86     * This is the Android platform's base MIME type for a content: URI
87     * containing a Cursor of zero or more items.  Applications should use this
88     * as the base type along with their own sub-type of their content: URIs
89     * that represent a directory of items.  For example, hypothetical IMAP email
90     * client may have a URI
91     * <code>content://com.company.provider.imap/inbox</code> for all of the
92     * messages in its inbox, whose MIME type would be reported as
93     * <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code>
94     *
95     * <p>Note how the base MIME type varies between this and
96     * {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is
97     * one single item or multiple items in the data set, while the sub-type
98     * remains the same because in either case the data structure contained
99     * in the cursor is the same.
100     */
101    public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
102
103    /** @hide */
104    public static final int SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
105    /** @hide */
106    public static final int SYNC_ERROR_AUTHENTICATION = 2;
107    /** @hide */
108    public static final int SYNC_ERROR_IO = 3;
109    /** @hide */
110    public static final int SYNC_ERROR_PARSE = 4;
111    /** @hide */
112    public static final int SYNC_ERROR_CONFLICT = 5;
113    /** @hide */
114    public static final int SYNC_ERROR_TOO_MANY_DELETIONS = 6;
115    /** @hide */
116    public static final int SYNC_ERROR_TOO_MANY_RETRIES = 7;
117    /** @hide */
118    public static final int SYNC_ERROR_INTERNAL = 8;
119
120    /** @hide */
121    public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0;
122    /** @hide */
123    public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1;
124    /** @hide */
125    public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2;
126    /** @hide */
127    public static final int SYNC_OBSERVER_TYPE_STATUS = 1<<3;
128    /** @hide */
129    public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
130
131    public ContentResolver(Context context) {
132        mContext = context;
133    }
134
135    /** @hide */
136    protected abstract IContentProvider acquireProvider(Context c, String name);
137    /** @hide */
138    public abstract boolean releaseProvider(IContentProvider icp);
139
140    /**
141     * Return the MIME type of the given content URL.
142     *
143     * @param url A Uri identifying content (either a list or specific type),
144     * using the content:// scheme.
145     * @return A MIME type for the content, or null if the URL is invalid or the type is unknown
146     */
147    public final String getType(Uri url)
148    {
149        IContentProvider provider = acquireProvider(url);
150        if (provider == null) {
151            return null;
152        }
153        try {
154            return provider.getType(url);
155        } catch (RemoteException e) {
156            return null;
157        } catch (java.lang.Exception e) {
158            return null;
159        } finally {
160            releaseProvider(provider);
161        }
162    }
163
164    /**
165     * Query the given URI, returning a {@link Cursor} over the result set.
166     *
167     * @param uri The URI, using the content:// scheme, for the content to
168     *         retrieve.
169     * @param projection A list of which columns to return. Passing null will
170     *         return all columns, which is discouraged to prevent reading data
171     *         from storage that isn't going to be used.
172     * @param selection A filter declaring which rows to return, formatted as an
173     *         SQL WHERE clause (excluding the WHERE itself). Passing null will
174     *         return all rows for the given URI.
175     * @param selectionArgs You may include ?s in selection, which will be
176     *         replaced by the values from selectionArgs, in the order that they
177     *         appear in the selection. The values will be bound as Strings.
178     * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
179     *         clause (excluding the ORDER BY itself). Passing null will use the
180     *         default sort order, which may be unordered.
181     * @return A Cursor object, which is positioned before the first entry, or null
182     * @see Cursor
183     */
184    public final Cursor query(Uri uri, String[] projection,
185            String selection, String[] selectionArgs, String sortOrder) {
186        IContentProvider provider = acquireProvider(uri);
187        if (provider == null) {
188            return null;
189        }
190        try {
191            Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
192            if(qCursor == null) {
193                releaseProvider(provider);
194                return null;
195            }
196            //Wrap the cursor object into CursorWrapperInner object
197            return new CursorWrapperInner(qCursor, provider);
198        } catch (RemoteException e) {
199            releaseProvider(provider);
200            return null;
201        } catch(RuntimeException e) {
202            releaseProvider(provider);
203            throw e;
204        }
205    }
206
207    /**
208     * EntityIterator wrapper that releases the associated ContentProviderClient when the
209     * iterator is closed.
210     */
211    private class EntityIteratorWrapper implements EntityIterator {
212        private final EntityIterator mInner;
213        private final ContentProviderClient mClient;
214        private volatile boolean mClientReleased;
215
216        EntityIteratorWrapper(EntityIterator inner, ContentProviderClient client) {
217            mInner = inner;
218            mClient = client;
219            mClientReleased = false;
220        }
221
222        public boolean hasNext() throws RemoteException {
223            if (mClientReleased) {
224                throw new IllegalStateException("this iterator is already closed");
225            }
226            return mInner.hasNext();
227        }
228
229        public Entity next() throws RemoteException {
230            if (mClientReleased) {
231                throw new IllegalStateException("this iterator is already closed");
232            }
233            return mInner.next();
234        }
235
236        public void close() {
237            mClient.release();
238            mInner.close();
239            mClientReleased = true;
240        }
241
242        protected void finalize() throws Throwable {
243            if (!mClientReleased) {
244                mClient.release();
245            }
246            super.finalize();
247        }
248    }
249
250    /**
251     * Query the given URI, returning an {@link EntityIterator} over the result set.
252     *
253     * @param uri The URI, using the content:// scheme, for the content to
254     *         retrieve.
255     * @param selection A filter declaring which rows to return, formatted as an
256     *         SQL WHERE clause (excluding the WHERE itself). Passing null will
257     *         return all rows for the given URI.
258     * @param selectionArgs You may include ?s in selection, which will be
259     *         replaced by the values from selectionArgs, in the order that they
260     *         appear in the selection. The values will be bound as Strings.
261     * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
262     *         clause (excluding the ORDER BY itself). Passing null will use the
263     *         default sort order, which may be unordered.
264     * @return An EntityIterator object
265     * @throws RemoteException thrown if a RemoteException is encountered while attempting
266     *   to communicate with a remote provider.
267     * @throws IllegalArgumentException thrown if there is no provider that matches the uri
268     */
269    public final EntityIterator queryEntities(Uri uri,
270            String selection, String[] selectionArgs, String sortOrder) throws RemoteException {
271        ContentProviderClient provider = acquireContentProviderClient(uri);
272        if (provider == null) {
273            throw new IllegalArgumentException("Unknown URL " + uri);
274        }
275        try {
276            EntityIterator entityIterator =
277                    provider.queryEntities(uri, selection, selectionArgs, sortOrder);
278            return new EntityIteratorWrapper(entityIterator, provider);
279        } catch(RuntimeException e) {
280            provider.release();
281            throw e;
282        } catch(RemoteException e) {
283            provider.release();
284            throw e;
285        }
286    }
287
288    /**
289     * Open a stream on to the content associated with a content URI.  If there
290     * is no data associated with the URI, FileNotFoundException is thrown.
291     *
292     * <h5>Accepts the following URI schemes:</h5>
293     * <ul>
294     * <li>content ({@link #SCHEME_CONTENT})</li>
295     * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
296     * <li>file ({@link #SCHEME_FILE})</li>
297     * </ul>
298     *
299     * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
300     * on these schemes.
301     *
302     * @param uri The desired URI.
303     * @return InputStream
304     * @throws FileNotFoundException if the provided URI could not be opened.
305     * @see #openAssetFileDescriptor(Uri, String)
306     */
307    public final InputStream openInputStream(Uri uri)
308            throws FileNotFoundException {
309        String scheme = uri.getScheme();
310        if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
311            // Note: left here to avoid breaking compatibility.  May be removed
312            // with sufficient testing.
313            OpenResourceIdResult r = getResourceId(uri);
314            try {
315                InputStream stream = r.r.openRawResource(r.id);
316                return stream;
317            } catch (Resources.NotFoundException ex) {
318                throw new FileNotFoundException("Resource does not exist: " + uri);
319            }
320        } else if (SCHEME_FILE.equals(scheme)) {
321            // Note: left here to avoid breaking compatibility.  May be removed
322            // with sufficient testing.
323            return new FileInputStream(uri.getPath());
324        } else {
325            AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r");
326            try {
327                return fd != null ? fd.createInputStream() : null;
328            } catch (IOException e) {
329                throw new FileNotFoundException("Unable to create stream");
330            }
331        }
332    }
333
334    /**
335     * Synonym for {@link #openOutputStream(Uri, String)
336     * openOutputStream(uri, "w")}.
337     * @throws FileNotFoundException if the provided URI could not be opened.
338     */
339    public final OutputStream openOutputStream(Uri uri)
340            throws FileNotFoundException {
341        return openOutputStream(uri, "w");
342    }
343
344    /**
345     * Open a stream on to the content associated with a content URI.  If there
346     * is no data associated with the URI, FileNotFoundException is thrown.
347     *
348     * <h5>Accepts the following URI schemes:</h5>
349     * <ul>
350     * <li>content ({@link #SCHEME_CONTENT})</li>
351     * <li>file ({@link #SCHEME_FILE})</li>
352     * </ul>
353     *
354     * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
355     * on these schemes.
356     *
357     * @param uri The desired URI.
358     * @param mode May be "w", "wa", "rw", or "rwt".
359     * @return OutputStream
360     * @throws FileNotFoundException if the provided URI could not be opened.
361     * @see #openAssetFileDescriptor(Uri, String)
362     */
363    public final OutputStream openOutputStream(Uri uri, String mode)
364            throws FileNotFoundException {
365        AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode);
366        try {
367            return fd != null ? fd.createOutputStream() : null;
368        } catch (IOException e) {
369            throw new FileNotFoundException("Unable to create stream");
370        }
371    }
372
373    /**
374     * Open a raw file descriptor to access data under a "content:" URI.  This
375     * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
376     * underlying {@link ContentProvider#openFile}
377     * ContentProvider.openFile()} method, so will <em>not</em> work with
378     * providers that return sub-sections of files.  If at all possible,
379     * you should use {@link #openAssetFileDescriptor(Uri, String)}.  You
380     * will receive a FileNotFoundException exception if the provider returns a
381     * sub-section of a file.
382     *
383     * <h5>Accepts the following URI schemes:</h5>
384     * <ul>
385     * <li>content ({@link #SCHEME_CONTENT})</li>
386     * <li>file ({@link #SCHEME_FILE})</li>
387     * </ul>
388     *
389     * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
390     * on these schemes.
391     *
392     * @param uri The desired URI to open.
393     * @param mode The file mode to use, as per {@link ContentProvider#openFile
394     * ContentProvider.openFile}.
395     * @return Returns a new ParcelFileDescriptor pointing to the file.  You
396     * own this descriptor and are responsible for closing it when done.
397     * @throws FileNotFoundException Throws FileNotFoundException of no
398     * file exists under the URI or the mode is invalid.
399     * @see #openAssetFileDescriptor(Uri, String)
400     */
401    public final ParcelFileDescriptor openFileDescriptor(Uri uri,
402            String mode) throws FileNotFoundException {
403        AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode);
404        if (afd == null) {
405            return null;
406        }
407
408        if (afd.getDeclaredLength() < 0) {
409            // This is a full file!
410            return afd.getParcelFileDescriptor();
411        }
412
413        // Client can't handle a sub-section of a file, so close what
414        // we got and bail with an exception.
415        try {
416            afd.close();
417        } catch (IOException e) {
418        }
419
420        throw new FileNotFoundException("Not a whole file");
421    }
422
423    /**
424     * Open a raw file descriptor to access data under a "content:" URI.  This
425     * interacts with the underlying {@link ContentProvider#openAssetFile}
426     * ContentProvider.openAssetFile()} method of the provider associated with the
427     * given URI, to retrieve any file stored there.
428     *
429     * <h5>Accepts the following URI schemes:</h5>
430     * <ul>
431     * <li>content ({@link #SCHEME_CONTENT})</li>
432     * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
433     * <li>file ({@link #SCHEME_FILE})</li>
434     * </ul>
435     * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
436     * <p>
437     * A Uri object can be used to reference a resource in an APK file.  The
438     * Uri should be one of the following formats:
439     * <ul>
440     * <li><code>android.resource://package_name/id_number</code><br/>
441     * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
442     * For example <code>com.example.myapp</code><br/>
443     * <code>id_number</code> is the int form of the ID.<br/>
444     * The easiest way to construct this form is
445     * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
446     * </li>
447     * <li><code>android.resource://package_name/type/name</code><br/>
448     * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
449     * For example <code>com.example.myapp</code><br/>
450     * <code>type</code> is the string form of the resource type.  For example, <code>raw</code>
451     * or <code>drawable</code>.
452     * <code>name</code> is the string form of the resource name.  That is, whatever the file
453     * name was in your res directory, without the type extension.
454     * The easiest way to construct this form is
455     * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
456     * </li>
457     * </ul>
458     *
459     * @param uri The desired URI to open.
460     * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
461     * ContentProvider.openAssetFile}.
462     * @return Returns a new ParcelFileDescriptor pointing to the file.  You
463     * own this descriptor and are responsible for closing it when done.
464     * @throws FileNotFoundException Throws FileNotFoundException of no
465     * file exists under the URI or the mode is invalid.
466     */
467    public final AssetFileDescriptor openAssetFileDescriptor(Uri uri,
468            String mode) throws FileNotFoundException {
469        String scheme = uri.getScheme();
470        if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
471            if (!"r".equals(mode)) {
472                throw new FileNotFoundException("Can't write resources: " + uri);
473            }
474            OpenResourceIdResult r = getResourceId(uri);
475            try {
476                return r.r.openRawResourceFd(r.id);
477            } catch (Resources.NotFoundException ex) {
478                throw new FileNotFoundException("Resource does not exist: " + uri);
479            }
480        } else if (SCHEME_FILE.equals(scheme)) {
481            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
482                    new File(uri.getPath()), modeToMode(uri, mode));
483            return new AssetFileDescriptor(pfd, 0, -1);
484        } else {
485            IContentProvider provider = acquireProvider(uri);
486            if (provider == null) {
487                throw new FileNotFoundException("No content provider: " + uri);
488            }
489            try {
490                AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
491                if(fd == null) {
492                    releaseProvider(provider);
493                    return null;
494                }
495                ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
496                        fd.getParcelFileDescriptor(), provider);
497                return new AssetFileDescriptor(pfd, fd.getStartOffset(),
498                        fd.getDeclaredLength());
499            } catch (RemoteException e) {
500                releaseProvider(provider);
501                throw new FileNotFoundException("Dead content provider: " + uri);
502            } catch (FileNotFoundException e) {
503                releaseProvider(provider);
504                throw e;
505            } catch (RuntimeException e) {
506                releaseProvider(provider);
507                throw e;
508            }
509        }
510    }
511
512    class OpenResourceIdResult {
513        Resources r;
514        int id;
515    }
516
517    OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
518        String authority = uri.getAuthority();
519        Resources r;
520        if (TextUtils.isEmpty(authority)) {
521            throw new FileNotFoundException("No authority: " + uri);
522        } else {
523            try {
524                r = mContext.getPackageManager().getResourcesForApplication(authority);
525            } catch (NameNotFoundException ex) {
526                throw new FileNotFoundException("No package found for authority: " + uri);
527            }
528        }
529        List<String> path = uri.getPathSegments();
530        if (path == null) {
531            throw new FileNotFoundException("No path: " + uri);
532        }
533        int len = path.size();
534        int id;
535        if (len == 1) {
536            try {
537                id = Integer.parseInt(path.get(0));
538            } catch (NumberFormatException e) {
539                throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
540            }
541        } else if (len == 2) {
542            id = r.getIdentifier(path.get(1), path.get(0), authority);
543        } else {
544            throw new FileNotFoundException("More than two path segments: " + uri);
545        }
546        if (id == 0) {
547            throw new FileNotFoundException("No resource found for: " + uri);
548        }
549        OpenResourceIdResult res = new OpenResourceIdResult();
550        res.r = r;
551        res.id = id;
552        return res;
553    }
554
555    /** @hide */
556    static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
557        int modeBits;
558        if ("r".equals(mode)) {
559            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
560        } else if ("w".equals(mode) || "wt".equals(mode)) {
561            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
562                    | ParcelFileDescriptor.MODE_CREATE
563                    | ParcelFileDescriptor.MODE_TRUNCATE;
564        } else if ("wa".equals(mode)) {
565            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
566                    | ParcelFileDescriptor.MODE_CREATE
567                    | ParcelFileDescriptor.MODE_APPEND;
568        } else if ("rw".equals(mode)) {
569            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
570                    | ParcelFileDescriptor.MODE_CREATE;
571        } else if ("rwt".equals(mode)) {
572            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
573                    | ParcelFileDescriptor.MODE_CREATE
574                    | ParcelFileDescriptor.MODE_TRUNCATE;
575        } else {
576            throw new FileNotFoundException("Bad mode for " + uri + ": "
577                    + mode);
578        }
579        return modeBits;
580    }
581
582    /**
583     * Inserts a row into a table at the given URL.
584     *
585     * If the content provider supports transactions the insertion will be atomic.
586     *
587     * @param url The URL of the table to insert into.
588     * @param values The initial values for the newly inserted row. The key is the column name for
589     *               the field. Passing an empty ContentValues will create an empty row.
590     * @return the URL of the newly created row.
591     */
592    public final Uri insert(Uri url, ContentValues values)
593    {
594        IContentProvider provider = acquireProvider(url);
595        if (provider == null) {
596            throw new IllegalArgumentException("Unknown URL " + url);
597        }
598        try {
599            return provider.insert(url, values);
600        } catch (RemoteException e) {
601            return null;
602        } finally {
603            releaseProvider(provider);
604        }
605    }
606
607    /**
608     * Applies each of the {@link ContentProviderOperation} objects and returns an array
609     * of their results. Passes through OperationApplicationException, which may be thrown
610     * by the call to {@link ContentProviderOperation#apply}.
611     * If all the applications succeed then a {@link ContentProviderResult} array with the
612     * same number of elements as the operations will be returned. It is implementation-specific
613     * how many, if any, operations will have been successfully applied if a call to
614     * apply results in a {@link OperationApplicationException}.
615     * @param authority the authority of the ContentProvider to which this batch should be applied
616     * @param operations the operations to apply
617     * @return the results of the applications
618     * @throws OperationApplicationException thrown if an application fails.
619     * See {@link ContentProviderOperation#apply} for more information.
620     * @throws RemoteException thrown if a RemoteException is encountered while attempting
621     *   to communicate with a remote provider.
622     */
623    public ContentProviderResult[] applyBatch(String authority,
624            ArrayList<ContentProviderOperation> operations)
625            throws RemoteException, OperationApplicationException {
626        ContentProviderClient provider = acquireContentProviderClient(authority);
627        if (provider == null) {
628            throw new IllegalArgumentException("Unknown authority " + authority);
629        }
630        try {
631            return provider.applyBatch(operations);
632        } finally {
633            provider.release();
634        }
635    }
636
637    /**
638     * Inserts multiple rows into a table at the given URL.
639     *
640     * This function make no guarantees about the atomicity of the insertions.
641     *
642     * @param url The URL of the table to insert into.
643     * @param values The initial values for the newly inserted rows. The key is the column name for
644     *               the field. Passing null will create an empty row.
645     * @return the number of newly created rows.
646     */
647    public final int bulkInsert(Uri url, ContentValues[] values)
648    {
649        IContentProvider provider = acquireProvider(url);
650        if (provider == null) {
651            throw new IllegalArgumentException("Unknown URL " + url);
652        }
653        try {
654            return provider.bulkInsert(url, values);
655        } catch (RemoteException e) {
656            return 0;
657        } finally {
658            releaseProvider(provider);
659        }
660    }
661
662    /**
663     * Deletes row(s) specified by a content URI.
664     *
665     * If the content provider supports transactions, the deletion will be atomic.
666     *
667     * @param url The URL of the row to delete.
668     * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause
669                    (excluding the WHERE itself).
670     * @return The number of rows deleted.
671     */
672    public final int delete(Uri url, String where, String[] selectionArgs)
673    {
674        IContentProvider provider = acquireProvider(url);
675        if (provider == null) {
676            throw new IllegalArgumentException("Unknown URL " + url);
677        }
678        try {
679            return provider.delete(url, where, selectionArgs);
680        } catch (RemoteException e) {
681            return -1;
682        } finally {
683            releaseProvider(provider);
684        }
685    }
686
687    /**
688     * Update row(s) in a content URI.
689     *
690     * If the content provider supports transactions the update will be atomic.
691     *
692     * @param uri The URI to modify.
693     * @param values The new field values. The key is the column name for the field.
694                     A null value will remove an existing field value.
695     * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause
696                    (excluding the WHERE itself).
697     * @return The number of rows updated.
698     * @throws NullPointerException if uri or values are null
699     */
700    public final int update(Uri uri, ContentValues values, String where,
701            String[] selectionArgs) {
702        IContentProvider provider = acquireProvider(uri);
703        if (provider == null) {
704            throw new IllegalArgumentException("Unknown URI " + uri);
705        }
706        try {
707            return provider.update(uri, values, where, selectionArgs);
708        } catch (RemoteException e) {
709            return -1;
710        } finally {
711            releaseProvider(provider);
712        }
713    }
714
715    /**
716     * Returns the content provider for the given content URI..
717     *
718     * @param uri The URI to a content provider
719     * @return The ContentProvider for the given URI, or null if no content provider is found.
720     * @hide
721     */
722    public final IContentProvider acquireProvider(Uri uri)
723    {
724        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
725            return null;
726        }
727        String auth = uri.getAuthority();
728        if (auth != null) {
729            return acquireProvider(mContext, uri.getAuthority());
730        }
731        return null;
732    }
733
734    /**
735     * @hide
736     */
737    public final IContentProvider acquireProvider(String name) {
738        if(name == null) {
739            return null;
740        }
741        return acquireProvider(mContext, name);
742    }
743
744    /**
745     * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
746     * that services the content at uri, starting the provider if necessary. Returns
747     * null if there is no provider associated wih the uri. The caller must indicate that they are
748     * done with the provider by calling {@link ContentProviderClient#release} which will allow
749     * the system to release the provider it it determines that there is no other reason for
750     * keeping it active.
751     * @param uri specifies which provider should be acquired
752     * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
753     * that services the content at uri or null if there isn't one.
754     */
755    public final ContentProviderClient acquireContentProviderClient(Uri uri) {
756        IContentProvider provider = acquireProvider(uri);
757        if (provider != null) {
758            return new ContentProviderClient(this, provider);
759        }
760
761        return null;
762    }
763
764    /**
765     * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
766     * with the authority of name, starting the provider if necessary. Returns
767     * null if there is no provider associated wih the uri. The caller must indicate that they are
768     * done with the provider by calling {@link ContentProviderClient#release} which will allow
769     * the system to release the provider it it determines that there is no other reason for
770     * keeping it active.
771     * @param name specifies which provider should be acquired
772     * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
773     * with the authority of name or null if there isn't one.
774     */
775    public final ContentProviderClient acquireContentProviderClient(String name) {
776        IContentProvider provider = acquireProvider(name);
777        if (provider != null) {
778            return new ContentProviderClient(this, provider);
779        }
780
781        return null;
782    }
783
784    /**
785     * Register an observer class that gets callbacks when data identified by a
786     * given content URI changes.
787     *
788     * @param uri The URI to watch for changes. This can be a specific row URI, or a base URI
789     * for a whole class of content.
790     * @param notifyForDescendents If <code>true</code> changes to URIs beginning with <code>uri</code>
791     * will also cause notifications to be sent. If <code>false</code> only changes to the exact URI
792     * specified by <em>uri</em> will cause notifications to be sent. If true, than any URI values
793     * at or below the specified URI will also trigger a match.
794     * @param observer The object that receives callbacks when changes occur.
795     * @see #unregisterContentObserver
796     */
797    public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
798            ContentObserver observer)
799    {
800        try {
801            getContentService().registerContentObserver(uri, notifyForDescendents,
802                    observer.getContentObserver());
803        } catch (RemoteException e) {
804        }
805    }
806
807    /**
808     * Unregisters a change observer.
809     *
810     * @param observer The previously registered observer that is no longer needed.
811     * @see #registerContentObserver
812     */
813    public final void unregisterContentObserver(ContentObserver observer) {
814        try {
815            IContentObserver contentObserver = observer.releaseContentObserver();
816            if (contentObserver != null) {
817                getContentService().unregisterContentObserver(
818                        contentObserver);
819            }
820        } catch (RemoteException e) {
821        }
822    }
823
824    /**
825     * Notify registered observers that a row was updated.
826     * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
827     * By default, CursorAdapter objects will get this notification.
828     *
829     * @param uri
830     * @param observer The observer that originated the change, may be <code>null</null>
831     */
832    public void notifyChange(Uri uri, ContentObserver observer) {
833        notifyChange(uri, observer, true /* sync to network */);
834    }
835
836    /**
837     * Notify registered observers that a row was updated.
838     * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
839     * By default, CursorAdapter objects will get this notification.
840     *
841     * @param uri
842     * @param observer The observer that originated the change, may be <code>null</null>
843     * @param syncToNetwork If true, attempt to sync the change to the network.
844     */
845    public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
846        try {
847            getContentService().notifyChange(
848                    uri, observer == null ? null : observer.getContentObserver(),
849                    observer != null && observer.deliverSelfNotifications(), syncToNetwork);
850        } catch (RemoteException e) {
851        }
852    }
853
854    /**
855     * Start an asynchronous sync operation. If you want to monitor the progress
856     * of the sync you may register a SyncObserver. Only values of the following
857     * types may be used in the extras bundle:
858     * <ul>
859     * <li>Integer</li>
860     * <li>Long</li>
861     * <li>Boolean</li>
862     * <li>Float</li>
863     * <li>Double</li>
864     * <li>String</li>
865     * </ul>
866     *
867     * @param uri the uri of the provider to sync or null to sync all providers.
868     * @param extras any extras to pass to the SyncAdapter.
869     * @deprecated instead use
870     * {@link #requestSync(android.accounts.Account, String, android.os.Bundle)}
871     */
872    public void startSync(Uri uri, Bundle extras) {
873        Account account = null;
874        if (extras != null) {
875            String accountName = extras.getString(SYNC_EXTRAS_ACCOUNT);
876            if (!TextUtils.isEmpty(accountName)) {
877                account = new Account(accountName, "com.google.GAIA");
878            }
879            extras.remove(SYNC_EXTRAS_ACCOUNT);
880        }
881        requestSync(account, uri != null ? uri.getAuthority() : null, extras);
882    }
883
884    /**
885     * Start an asynchronous sync operation. If you want to monitor the progress
886     * of the sync you may register a SyncObserver. Only values of the following
887     * types may be used in the extras bundle:
888     * <ul>
889     * <li>Integer</li>
890     * <li>Long</li>
891     * <li>Boolean</li>
892     * <li>Float</li>
893     * <li>Double</li>
894     * <li>String</li>
895     * </ul>
896     *
897     * @param account which account should be synced
898     * @param authority which authority should be synced
899     * @param extras any extras to pass to the SyncAdapter.
900     */
901    public static void requestSync(Account account, String authority, Bundle extras) {
902        validateSyncExtrasBundle(extras);
903        try {
904            getContentService().requestSync(account, authority, extras);
905        } catch (RemoteException e) {
906        }
907    }
908
909    /**
910     * Check that only values of the following types are in the Bundle:
911     * <ul>
912     * <li>Integer</li>
913     * <li>Long</li>
914     * <li>Boolean</li>
915     * <li>Float</li>
916     * <li>Double</li>
917     * <li>String</li>
918     * <li>Account</li>
919     * <li>null</li>
920     * </ul>
921     * @param extras the Bundle to check
922     */
923    public static void validateSyncExtrasBundle(Bundle extras) {
924        try {
925            for (String key : extras.keySet()) {
926                Object value = extras.get(key);
927                if (value == null) continue;
928                if (value instanceof Long) continue;
929                if (value instanceof Integer) continue;
930                if (value instanceof Boolean) continue;
931                if (value instanceof Float) continue;
932                if (value instanceof Double) continue;
933                if (value instanceof String) continue;
934                if (value instanceof Account) continue;
935                throw new IllegalArgumentException("unexpected value type: "
936                        + value.getClass().getName());
937            }
938        } catch (IllegalArgumentException e) {
939            throw e;
940        } catch (RuntimeException exc) {
941            throw new IllegalArgumentException("error unparceling Bundle", exc);
942        }
943    }
944
945    /**
946     * Cancel any active or pending syncs that match the Uri. If the uri is null then
947     * all syncs will be canceled.
948     *
949     * @param uri the uri of the provider to sync or null to sync all providers.
950     * @deprecated instead use {@link #cancelSync(android.accounts.Account, String)}
951     */
952    public void cancelSync(Uri uri) {
953        cancelSync(null /* all accounts */, uri != null ? uri.getAuthority() : null);
954    }
955
956    /**
957     * Cancel any active or pending syncs that match account and authority. The account and
958     * authority can each independently be set to null, which means that syncs with any account
959     * or authority, respectively, will match.
960     *
961     * @param account filters the syncs that match by this account
962     * @param authority filters the syncs that match by this authority
963     */
964    public static void cancelSync(Account account, String authority) {
965        try {
966            getContentService().cancelSync(account, authority);
967        } catch (RemoteException e) {
968        }
969    }
970
971    /**
972     * Get information about the SyncAdapters that are known to the system.
973     * @return an array of SyncAdapters that have registered with the system
974     */
975    public static SyncAdapterType[] getSyncAdapterTypes() {
976        try {
977            return getContentService().getSyncAdapterTypes();
978        } catch (RemoteException e) {
979            throw new RuntimeException("the ContentService should always be reachable", e);
980        }
981    }
982
983    /**
984     * Check if the provider should be synced when a network tickle is received
985     *
986     * @param account the account whose setting we are querying
987     * @param authority the provider whose setting we are querying
988     * @return true if the provider should be synced when a network tickle is received
989     */
990    public static boolean getSyncAutomatically(Account account, String authority) {
991        try {
992            return getContentService().getSyncAutomatically(account, authority);
993        } catch (RemoteException e) {
994            throw new RuntimeException("the ContentService should always be reachable", e);
995        }
996    }
997
998    /**
999     * Set whether or not the provider is synced when it receives a network tickle.
1000     *
1001     * @param account the account whose setting we are querying
1002     * @param authority the provider whose behavior is being controlled
1003     * @param sync true if the provider should be synced when tickles are received for it
1004     */
1005    public static void setSyncAutomatically(Account account, String authority, boolean sync) {
1006        try {
1007            getContentService().setSyncAutomatically(account, authority, sync);
1008        } catch (RemoteException e) {
1009            // exception ignored; if this is thrown then it means the runtime is in the midst of
1010            // being restarted
1011        }
1012    }
1013
1014    /**
1015     * Check if this account/provider is syncable.
1016     * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
1017     */
1018    public int getIsSyncable(Account account, String authority) {
1019        try {
1020            return getContentService().getIsSyncable(account, authority);
1021        } catch (RemoteException e) {
1022            throw new RuntimeException("the ContentService should always be reachable", e);
1023        }
1024    }
1025
1026    /**
1027     * Set whether this account/provider is syncable.
1028     * @param syncable >0 denotes syncable, 0 means not syncable, <0 means unknown
1029     */
1030    public void setIsSyncable(Account account, String authority, int syncable) {
1031        try {
1032            getContentService().setIsSyncable(account, authority, syncable);
1033        } catch (RemoteException e) {
1034            // exception ignored; if this is thrown then it means the runtime is in the midst of
1035            // being restarted
1036        }
1037    }
1038
1039    /**
1040     * Gets the master auto-sync setting that applies to all the providers and accounts.
1041     * If this is false then the per-provider auto-sync setting is ignored.
1042     *
1043     * @return the master auto-sync setting that applies to all the providers and accounts
1044     */
1045    public static boolean getMasterSyncAutomatically() {
1046        try {
1047            return getContentService().getMasterSyncAutomatically();
1048        } catch (RemoteException e) {
1049            throw new RuntimeException("the ContentService should always be reachable", e);
1050        }
1051    }
1052
1053    /**
1054     * Sets the master auto-sync setting that applies to all the providers and accounts.
1055     * If this is false then the per-provider auto-sync setting is ignored.
1056     *
1057     * @param sync the master auto-sync setting that applies to all the providers and accounts
1058     */
1059    public static void setMasterSyncAutomatically(boolean sync) {
1060        try {
1061            getContentService().setMasterSyncAutomatically(sync);
1062        } catch (RemoteException e) {
1063            // exception ignored; if this is thrown then it means the runtime is in the midst of
1064            // being restarted
1065        }
1066    }
1067
1068    /**
1069     * Returns true if there is currently a sync operation for the given
1070     * account or authority in the pending list, or actively being processed.
1071     * @param account the account whose setting we are querying
1072     * @param authority the provider whose behavior is being queried
1073     * @return true if a sync is active for the given account or authority.
1074     */
1075    public static boolean isSyncActive(Account account, String authority) {
1076        try {
1077            return getContentService().isSyncActive(account, authority);
1078        } catch (RemoteException e) {
1079            throw new RuntimeException("the ContentService should always be reachable", e);
1080        }
1081    }
1082
1083    /**
1084     * If a sync is active returns the information about it, otherwise returns false.
1085     * @return the ActiveSyncInfo for the currently active sync or null if one is not active.
1086     * @hide
1087     */
1088    public static ActiveSyncInfo getActiveSync() {
1089        try {
1090            return getContentService().getActiveSync();
1091        } catch (RemoteException e) {
1092            throw new RuntimeException("the ContentService should always be reachable", e);
1093        }
1094    }
1095
1096    /**
1097     * Returns the status that matches the authority. If there are multiples accounts for
1098     * the authority, the one with the latest "lastSuccessTime" status is returned.
1099     * @param account the account whose setting we are querying
1100     * @param authority the provider whose behavior is being queried
1101     * @return the SyncStatusInfo for the authority, or null if none exists
1102     * @hide
1103     */
1104    public static SyncStatusInfo getSyncStatus(Account account, String authority) {
1105        try {
1106            return getContentService().getSyncStatus(account, authority);
1107        } catch (RemoteException e) {
1108            throw new RuntimeException("the ContentService should always be reachable", e);
1109        }
1110    }
1111
1112    /**
1113     * Return true if the pending status is true of any matching authorities.
1114     * @param account the account whose setting we are querying
1115     * @param authority the provider whose behavior is being queried
1116     * @return true if there is a pending sync with the matching account and authority
1117     */
1118    public static boolean isSyncPending(Account account, String authority) {
1119        try {
1120            return getContentService().isSyncPending(account, authority);
1121        } catch (RemoteException e) {
1122            throw new RuntimeException("the ContentService should always be reachable", e);
1123        }
1124    }
1125
1126    public static Object addStatusChangeListener(int mask, final SyncStatusObserver callback) {
1127        try {
1128            ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() {
1129                public void onStatusChanged(int which) throws RemoteException {
1130                    callback.onStatusChanged(which);
1131                }
1132            };
1133            getContentService().addStatusChangeListener(mask, observer);
1134            return observer;
1135        } catch (RemoteException e) {
1136            throw new RuntimeException("the ContentService should always be reachable", e);
1137        }
1138    }
1139
1140    public static void removeStatusChangeListener(Object handle) {
1141        try {
1142            getContentService().removeStatusChangeListener((ISyncStatusObserver.Stub) handle);
1143        } catch (RemoteException e) {
1144            // exception ignored; if this is thrown then it means the runtime is in the midst of
1145            // being restarted
1146        }
1147    }
1148
1149
1150    private final class CursorWrapperInner extends CursorWrapper {
1151        private IContentProvider mContentProvider;
1152        public static final String TAG="CursorWrapperInner";
1153        private boolean mCloseFlag = false;
1154
1155        CursorWrapperInner(Cursor cursor, IContentProvider icp) {
1156            super(cursor);
1157            mContentProvider = icp;
1158        }
1159
1160        @Override
1161        public void close() {
1162            super.close();
1163            ContentResolver.this.releaseProvider(mContentProvider);
1164            mCloseFlag = true;
1165        }
1166
1167        @Override
1168        protected void finalize() throws Throwable {
1169            try {
1170                if(!mCloseFlag) {
1171                    ContentResolver.this.releaseProvider(mContentProvider);
1172                }
1173            } finally {
1174                super.finalize();
1175            }
1176        }
1177    }
1178
1179    private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
1180        private IContentProvider mContentProvider;
1181        public static final String TAG="ParcelFileDescriptorInner";
1182        private boolean mReleaseProviderFlag = false;
1183
1184        ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) {
1185            super(pfd);
1186            mContentProvider = icp;
1187        }
1188
1189        @Override
1190        public void close() throws IOException {
1191            if(!mReleaseProviderFlag) {
1192                super.close();
1193                ContentResolver.this.releaseProvider(mContentProvider);
1194                mReleaseProviderFlag = true;
1195            }
1196        }
1197
1198        @Override
1199        protected void finalize() throws Throwable {
1200            if (!mReleaseProviderFlag) {
1201                close();
1202            }
1203        }
1204    }
1205
1206    /** @hide */
1207    public static final String CONTENT_SERVICE_NAME = "content";
1208
1209    /** @hide */
1210    public static IContentService getContentService() {
1211        if (sContentService != null) {
1212            return sContentService;
1213        }
1214        IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
1215        if (Config.LOGV) Log.v("ContentService", "default service binder = " + b);
1216        sContentService = IContentService.Stub.asInterface(b);
1217        if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService);
1218        return sContentService;
1219    }
1220
1221    private static IContentService sContentService;
1222    private final Context mContext;
1223    private static final String TAG = "ContentResolver";
1224}
1225