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