| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.content; |
| |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.AssetFileDescriptor; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.database.CursorWrapper; |
| import android.database.IContentObserver; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.text.TextUtils; |
| import android.util.Config; |
| import android.util.Log; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.List; |
| |
| |
| /** |
| * This class provides applications access to the content model. |
| */ |
| public abstract class ContentResolver { |
| public final static String SYNC_EXTRAS_ACCOUNT = "account"; |
| public static final String SYNC_EXTRAS_EXPEDITED = "expedited"; |
| public static final String SYNC_EXTRAS_FORCE = "force"; |
| public static final String SYNC_EXTRAS_UPLOAD = "upload"; |
| public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override"; |
| public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions"; |
| |
| public static final String SCHEME_CONTENT = "content"; |
| public static final String SCHEME_ANDROID_RESOURCE = "android.resource"; |
| public static final String SCHEME_FILE = "file"; |
| |
| /** |
| * This is the Android platform's base MIME type for a content: URI |
| * containing a Cursor of a single item. Applications should use this |
| * as the base type along with their own sub-type of their content: URIs |
| * that represent a particular item. For example, hypothetical IMAP email |
| * client may have a URI |
| * <code>content://com.company.provider.imap/inbox/1</code> for a particular |
| * message in the inbox, whose MIME type would be reported as |
| * <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code> |
| * |
| * <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}. |
| */ |
| public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item"; |
| |
| /** |
| * This is the Android platform's base MIME type for a content: URI |
| * containing a Cursor of zero or more items. Applications should use this |
| * as the base type along with their own sub-type of their content: URIs |
| * that represent a directory of items. For example, hypothetical IMAP email |
| * client may have a URI |
| * <code>content://com.company.provider.imap/inbox</code> for all of the |
| * messages in its inbox, whose MIME type would be reported as |
| * <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code> |
| * |
| * <p>Note how the base MIME type varies between this and |
| * {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is |
| * one single item or multiple items in the data set, while the sub-type |
| * remains the same because in either case the data structure contained |
| * in the cursor is the same. |
| */ |
| public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir"; |
| |
| public ContentResolver(Context context) { |
| mContext = context; |
| } |
| |
| /** @hide */ |
| protected abstract IContentProvider acquireProvider(Context c, String name); |
| /** @hide */ |
| public abstract boolean releaseProvider(IContentProvider icp); |
| |
| /** |
| * Return the MIME type of the given content URL. |
| * |
| * @param url A Uri identifying content (either a list or specific type), |
| * using the content:// scheme. |
| * @return A MIME type for the content, or null if the URL is invalid or the type is unknown |
| */ |
| public final String getType(Uri url) |
| { |
| IContentProvider provider = acquireProvider(url); |
| if (provider == null) { |
| return null; |
| } |
| try { |
| return provider.getType(url); |
| } catch (RemoteException e) { |
| return null; |
| } catch (java.lang.Exception e) { |
| return null; |
| } finally { |
| releaseProvider(provider); |
| } |
| } |
| |
| /** |
| * Query the given URI, returning a {@link Cursor} over the result set. |
| * |
| * @param uri The URI, using the content:// scheme, for the content to |
| * retrieve. |
| * @param projection A list of which columns to return. Passing null will |
| * return all columns, which is discouraged to prevent reading data |
| * from storage that isn't going to be used. |
| * @param selection A filter declaring which rows to return, formatted as an |
| * SQL WHERE clause (excluding the WHERE itself). Passing null will |
| * return all rows for the given URI. |
| * @param selectionArgs You may include ?s in selection, which will be |
| * replaced by the values from selectionArgs, in the order that they |
| * appear in the selection. The values will be bound as Strings. |
| * @param sortOrder How to order the rows, formatted as an SQL ORDER BY |
| * clause (excluding the ORDER BY itself). Passing null will use the |
| * default sort order, which may be unordered. |
| * @return A Cursor object, which is positioned before the first entry, or null |
| * @see Cursor |
| */ |
| public final Cursor query(Uri uri, String[] projection, |
| String selection, String[] selectionArgs, String sortOrder) { |
| IContentProvider provider = acquireProvider(uri); |
| if (provider == null) { |
| return null; |
| } |
| try { |
| Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder); |
| if(qCursor == null) { |
| releaseProvider(provider); |
| return null; |
| } |
| //Wrap the cursor object into CursorWrapperInner object |
| return new CursorWrapperInner(qCursor, provider); |
| } catch (RemoteException e) { |
| releaseProvider(provider); |
| return null; |
| } catch(RuntimeException e) { |
| releaseProvider(provider); |
| throw e; |
| } |
| } |
| |
| /** |
| * Open a stream on to the content associated with a content URI. If there |
| * is no data associated with the URI, FileNotFoundException is thrown. |
| * |
| * <h5>Accepts the following URI schemes:</h5> |
| * <ul> |
| * <li>content ({@link #SCHEME_CONTENT})</li> |
| * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li> |
| * <li>file ({@link #SCHEME_FILE})</li> |
| * </ul> |
| * |
| * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information |
| * on these schemes. |
| * |
| * @param uri The desired URI. |
| * @return InputStream |
| * @throws FileNotFoundException if the provided URI could not be opened. |
| * @see #openAssetFileDescriptor(Uri, String) |
| */ |
| public final InputStream openInputStream(Uri uri) |
| throws FileNotFoundException { |
| String scheme = uri.getScheme(); |
| if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { |
| // Note: left here to avoid breaking compatibility. May be removed |
| // with sufficient testing. |
| OpenResourceIdResult r = getResourceId(uri); |
| try { |
| InputStream stream = r.r.openRawResource(r.id); |
| return stream; |
| } catch (Resources.NotFoundException ex) { |
| throw new FileNotFoundException("Resource does not exist: " + uri); |
| } |
| } else if (SCHEME_FILE.equals(scheme)) { |
| // Note: left here to avoid breaking compatibility. May be removed |
| // with sufficient testing. |
| return new FileInputStream(uri.getPath()); |
| } else { |
| AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r"); |
| try { |
| return fd != null ? fd.createInputStream() : null; |
| } catch (IOException e) { |
| throw new FileNotFoundException("Unable to create stream"); |
| } |
| } |
| } |
| |
| /** |
| * Synonym for {@link #openOutputStream(Uri, String) |
| * openOutputStream(uri, "w")}. |
| * @throws FileNotFoundException if the provided URI could not be opened. |
| */ |
| public final OutputStream openOutputStream(Uri uri) |
| throws FileNotFoundException { |
| return openOutputStream(uri, "w"); |
| } |
| |
| /** |
| * Open a stream on to the content associated with a content URI. If there |
| * is no data associated with the URI, FileNotFoundException is thrown. |
| * |
| * <h5>Accepts the following URI schemes:</h5> |
| * <ul> |
| * <li>content ({@link #SCHEME_CONTENT})</li> |
| * <li>file ({@link #SCHEME_FILE})</li> |
| * </ul> |
| * |
| * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information |
| * on these schemes. |
| * |
| * @param uri The desired URI. |
| * @param mode May be "w", "wa", "rw", or "rwt". |
| * @return OutputStream |
| * @throws FileNotFoundException if the provided URI could not be opened. |
| * @see #openAssetFileDescriptor(Uri, String) |
| */ |
| public final OutputStream openOutputStream(Uri uri, String mode) |
| throws FileNotFoundException { |
| AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode); |
| try { |
| return fd != null ? fd.createOutputStream() : null; |
| } catch (IOException e) { |
| throw new FileNotFoundException("Unable to create stream"); |
| } |
| } |
| |
| /** |
| * Open a raw file descriptor to access data under a "content:" URI. This |
| * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the |
| * underlying {@link ContentProvider#openFile} |
| * ContentProvider.openFile()} method, so will <em>not</em> work with |
| * providers that return sub-sections of files. If at all possible, |
| * you should use {@link #openAssetFileDescriptor(Uri, String)}. You |
| * will receive a FileNotFoundException exception if the provider returns a |
| * sub-section of a file. |
| * |
| * <h5>Accepts the following URI schemes:</h5> |
| * <ul> |
| * <li>content ({@link #SCHEME_CONTENT})</li> |
| * <li>file ({@link #SCHEME_FILE})</li> |
| * </ul> |
| * |
| * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information |
| * on these schemes. |
| * |
| * @param uri The desired URI to open. |
| * @param mode The file mode to use, as per {@link ContentProvider#openFile |
| * ContentProvider.openFile}. |
| * @return Returns a new ParcelFileDescriptor pointing to the file. You |
| * own this descriptor and are responsible for closing it when done. |
| * @throws FileNotFoundException Throws FileNotFoundException of no |
| * file exists under the URI or the mode is invalid. |
| * @see #openAssetFileDescriptor(Uri, String) |
| */ |
| public final ParcelFileDescriptor openFileDescriptor(Uri uri, |
| String mode) throws FileNotFoundException { |
| AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode); |
| if (afd == null) { |
| return null; |
| } |
| |
| if (afd.getDeclaredLength() < 0) { |
| // This is a full file! |
| return afd.getParcelFileDescriptor(); |
| } |
| |
| // Client can't handle a sub-section of a file, so close what |
| // we got and bail with an exception. |
| try { |
| afd.close(); |
| } catch (IOException e) { |
| } |
| |
| throw new FileNotFoundException("Not a whole file"); |
| } |
| |
| /** |
| * Open a raw file descriptor to access data under a "content:" URI. This |
| * interacts with the underlying {@link ContentProvider#openAssetFile} |
| * ContentProvider.openAssetFile()} method of the provider associated with the |
| * given URI, to retrieve any file stored there. |
| * |
| * <h5>Accepts the following URI schemes:</h5> |
| * <ul> |
| * <li>content ({@link #SCHEME_CONTENT})</li> |
| * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li> |
| * <li>file ({@link #SCHEME_FILE})</li> |
| * </ul> |
| * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5> |
| * <p> |
| * A Uri object can be used to reference a resource in an APK file. The |
| * Uri should be one of the following formats: |
| * <ul> |
| * <li><code>android.resource://package_name/id_number</code><br/> |
| * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. |
| * For example <code>com.example.myapp</code><br/> |
| * <code>id_number</code> is the int form of the ID.<br/> |
| * The easiest way to construct this form is |
| * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre> |
| * </li> |
| * <li><code>android.resource://package_name/type/name</code><br/> |
| * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. |
| * For example <code>com.example.myapp</code><br/> |
| * <code>type</code> is the string form of the resource type. For example, <code>raw</code> |
| * or <code>drawable</code>. |
| * <code>name</code> is the string form of the resource name. That is, whatever the file |
| * name was in your res directory, without the type extension. |
| * The easiest way to construct this form is |
| * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre> |
| * </li> |
| * </ul> |
| * |
| * @param uri The desired URI to open. |
| * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile |
| * ContentProvider.openAssetFile}. |
| * @return Returns a new ParcelFileDescriptor pointing to the file. You |
| * own this descriptor and are responsible for closing it when done. |
| * @throws FileNotFoundException Throws FileNotFoundException of no |
| * file exists under the URI or the mode is invalid. |
| */ |
| public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, |
| String mode) throws FileNotFoundException { |
| String scheme = uri.getScheme(); |
| if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { |
| if (!"r".equals(mode)) { |
| throw new FileNotFoundException("Can't write resources: " + uri); |
| } |
| OpenResourceIdResult r = getResourceId(uri); |
| try { |
| return r.r.openRawResourceFd(r.id); |
| } catch (Resources.NotFoundException ex) { |
| throw new FileNotFoundException("Resource does not exist: " + uri); |
| } |
| } else if (SCHEME_FILE.equals(scheme)) { |
| ParcelFileDescriptor pfd = ParcelFileDescriptor.open( |
| new File(uri.getPath()), modeToMode(uri, mode)); |
| return new AssetFileDescriptor(pfd, 0, -1); |
| } else { |
| IContentProvider provider = acquireProvider(uri); |
| if (provider == null) { |
| throw new FileNotFoundException("No content provider: " + uri); |
| } |
| try { |
| AssetFileDescriptor fd = provider.openAssetFile(uri, mode); |
| if(fd == null) { |
| releaseProvider(provider); |
| return null; |
| } |
| ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( |
| fd.getParcelFileDescriptor(), provider); |
| return new AssetFileDescriptor(pfd, fd.getStartOffset(), |
| fd.getDeclaredLength()); |
| } catch (RemoteException e) { |
| releaseProvider(provider); |
| throw new FileNotFoundException("Dead content provider: " + uri); |
| } catch (FileNotFoundException e) { |
| releaseProvider(provider); |
| throw e; |
| } catch (RuntimeException e) { |
| releaseProvider(provider); |
| throw e; |
| } |
| } |
| } |
| |
| class OpenResourceIdResult { |
| Resources r; |
| int id; |
| } |
| |
| OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException { |
| String authority = uri.getAuthority(); |
| Resources r; |
| if (TextUtils.isEmpty(authority)) { |
| throw new FileNotFoundException("No authority: " + uri); |
| } else { |
| try { |
| r = mContext.getPackageManager().getResourcesForApplication(authority); |
| } catch (NameNotFoundException ex) { |
| throw new FileNotFoundException("No package found for authority: " + uri); |
| } |
| } |
| List<String> path = uri.getPathSegments(); |
| if (path == null) { |
| throw new FileNotFoundException("No path: " + uri); |
| } |
| int len = path.size(); |
| int id; |
| if (len == 1) { |
| try { |
| id = Integer.parseInt(path.get(0)); |
| } catch (NumberFormatException e) { |
| throw new FileNotFoundException("Single path segment is not a resource ID: " + uri); |
| } |
| } else if (len == 2) { |
| id = r.getIdentifier(path.get(1), path.get(0), authority); |
| } else { |
| throw new FileNotFoundException("More than two path segments: " + uri); |
| } |
| if (id == 0) { |
| throw new FileNotFoundException("No resource found for: " + uri); |
| } |
| OpenResourceIdResult res = new OpenResourceIdResult(); |
| res.r = r; |
| res.id = id; |
| return res; |
| } |
| |
| /** @hide */ |
| static public int modeToMode(Uri uri, String mode) throws FileNotFoundException { |
| int modeBits; |
| if ("r".equals(mode)) { |
| modeBits = ParcelFileDescriptor.MODE_READ_ONLY; |
| } else if ("w".equals(mode) || "wt".equals(mode)) { |
| modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY |
| | ParcelFileDescriptor.MODE_CREATE |
| | ParcelFileDescriptor.MODE_TRUNCATE; |
| } else if ("wa".equals(mode)) { |
| modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY |
| | ParcelFileDescriptor.MODE_CREATE |
| | ParcelFileDescriptor.MODE_APPEND; |
| } else if ("rw".equals(mode)) { |
| modeBits = ParcelFileDescriptor.MODE_READ_WRITE |
| | ParcelFileDescriptor.MODE_CREATE; |
| } else if ("rwt".equals(mode)) { |
| modeBits = ParcelFileDescriptor.MODE_READ_WRITE |
| | ParcelFileDescriptor.MODE_CREATE |
| | ParcelFileDescriptor.MODE_TRUNCATE; |
| } else { |
| throw new FileNotFoundException("Bad mode for " + uri + ": " |
| + mode); |
| } |
| return modeBits; |
| } |
| |
| /** |
| * Inserts a row into a table at the given URL. |
| * |
| * If the content provider supports transactions the insertion will be atomic. |
| * |
| * @param url The URL of the table to insert into. |
| * @param values The initial values for the newly inserted row. The key is the column name for |
| * the field. Passing an empty ContentValues will create an empty row. |
| * @return the URL of the newly created row. |
| */ |
| public final Uri insert(Uri url, ContentValues values) |
| { |
| IContentProvider provider = acquireProvider(url); |
| if (provider == null) { |
| throw new IllegalArgumentException("Unknown URL " + url); |
| } |
| try { |
| return provider.insert(url, values); |
| } catch (RemoteException e) { |
| return null; |
| } finally { |
| releaseProvider(provider); |
| } |
| } |
| |
| /** |
| * Inserts multiple rows into a table at the given URL. |
| * |
| * This function make no guarantees about the atomicity of the insertions. |
| * |
| * @param url The URL of the table to insert into. |
| * @param values The initial values for the newly inserted rows. The key is the column name for |
| * the field. Passing null will create an empty row. |
| * @return the number of newly created rows. |
| */ |
| public final int bulkInsert(Uri url, ContentValues[] values) |
| { |
| IContentProvider provider = acquireProvider(url); |
| if (provider == null) { |
| throw new IllegalArgumentException("Unknown URL " + url); |
| } |
| try { |
| return provider.bulkInsert(url, values); |
| } catch (RemoteException e) { |
| return 0; |
| } finally { |
| releaseProvider(provider); |
| } |
| } |
| |
| /** |
| * Deletes row(s) specified by a content URI. |
| * |
| * If the content provider supports transactions, the deletion will be atomic. |
| * |
| * @param url The URL of the row to delete. |
| * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause |
| (excluding the WHERE itself). |
| * @return The number of rows deleted. |
| */ |
| public final int delete(Uri url, String where, String[] selectionArgs) |
| { |
| IContentProvider provider = acquireProvider(url); |
| if (provider == null) { |
| throw new IllegalArgumentException("Unknown URL " + url); |
| } |
| try { |
| return provider.delete(url, where, selectionArgs); |
| } catch (RemoteException e) { |
| return -1; |
| } finally { |
| releaseProvider(provider); |
| } |
| } |
| |
| /** |
| * Update row(s) in a content URI. |
| * |
| * If the content provider supports transactions the update will be atomic. |
| * |
| * @param uri The URI to modify. |
| * @param values The new field values. The key is the column name for the field. |
| A null value will remove an existing field value. |
| * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause |
| (excluding the WHERE itself). |
| * @return The number of rows updated. |
| * @throws NullPointerException if uri or values are null |
| */ |
| public final int update(Uri uri, ContentValues values, String where, |
| String[] selectionArgs) { |
| IContentProvider provider = acquireProvider(uri); |
| if (provider == null) { |
| throw new IllegalArgumentException("Unknown URI " + uri); |
| } |
| try { |
| return provider.update(uri, values, where, selectionArgs); |
| } catch (RemoteException e) { |
| return -1; |
| } finally { |
| releaseProvider(provider); |
| } |
| } |
| |
| /** |
| * Returns the content provider for the given content URI.. |
| * |
| * @param uri The URI to a content provider |
| * @return The ContentProvider for the given URI, or null if no content provider is found. |
| * @hide |
| */ |
| public final IContentProvider acquireProvider(Uri uri) |
| { |
| if (!SCHEME_CONTENT.equals(uri.getScheme())) { |
| return null; |
| } |
| String auth = uri.getAuthority(); |
| if (auth != null) { |
| return acquireProvider(mContext, uri.getAuthority()); |
| } |
| return null; |
| } |
| |
| /** |
| * @hide |
| */ |
| public final IContentProvider acquireProvider(String name) { |
| if(name == null) { |
| return null; |
| } |
| return acquireProvider(mContext, name); |
| } |
| |
| /** |
| * Register an observer class that gets callbacks when data identified by a |
| * given content URI changes. |
| * |
| * @param uri The URI to watch for changes. This can be a specific row URI, or a base URI |
| * for a whole class of content. |
| * @param notifyForDescendents If <code>true</code> changes to URIs beginning with <code>uri</code> |
| * will also cause notifications to be sent. If <code>false</code> only changes to the exact URI |
| * specified by <em>uri</em> will cause notifications to be sent. If true, than any URI values |
| * at or below the specified URI will also trigger a match. |
| * @param observer The object that receives callbacks when changes occur. |
| * @see #unregisterContentObserver |
| */ |
| public final void registerContentObserver(Uri uri, boolean notifyForDescendents, |
| ContentObserver observer) |
| { |
| try { |
| getContentService().registerContentObserver(uri, notifyForDescendents, |
| observer.getContentObserver()); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| /** |
| * Unregisters a change observer. |
| * |
| * @param observer The previously registered observer that is no longer needed. |
| * @see #registerContentObserver |
| */ |
| public final void unregisterContentObserver(ContentObserver observer) { |
| try { |
| IContentObserver contentObserver = observer.releaseContentObserver(); |
| if (contentObserver != null) { |
| getContentService().unregisterContentObserver( |
| contentObserver); |
| } |
| } catch (RemoteException e) { |
| } |
| } |
| |
| /** |
| * Notify registered observers that a row was updated. |
| * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. |
| * By default, CursorAdapter objects will get this notification. |
| * |
| * @param uri |
| * @param observer The observer that originated the change, may be <code>null</null> |
| */ |
| public void notifyChange(Uri uri, ContentObserver observer) { |
| notifyChange(uri, observer, true /* sync to network */); |
| } |
| |
| /** |
| * Notify registered observers that a row was updated. |
| * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. |
| * By default, CursorAdapter objects will get this notification. |
| * |
| * @param uri |
| * @param observer The observer that originated the change, may be <code>null</null> |
| * @param syncToNetwork If true, attempt to sync the change to the network. |
| */ |
| public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { |
| try { |
| getContentService().notifyChange( |
| uri, observer == null ? null : observer.getContentObserver(), |
| observer != null && observer.deliverSelfNotifications(), syncToNetwork); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| /** |
| * Start an asynchronous sync operation. If you want to monitor the progress |
| * of the sync you may register a SyncObserver. Only values of the following |
| * types may be used in the extras bundle: |
| * <ul> |
| * <li>Integer</li> |
| * <li>Long</li> |
| * <li>Boolean</li> |
| * <li>Float</li> |
| * <li>Double</li> |
| * <li>String</li> |
| * </ul> |
| * |
| * @param uri the uri of the provider to sync or null to sync all providers. |
| * @param extras any extras to pass to the SyncAdapter. |
| */ |
| public void startSync(Uri uri, Bundle extras) { |
| validateSyncExtrasBundle(extras); |
| try { |
| getContentService().startSync(uri, extras); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| /** |
| * Check that only values of the following types are in the Bundle: |
| * <ul> |
| * <li>Integer</li> |
| * <li>Long</li> |
| * <li>Boolean</li> |
| * <li>Float</li> |
| * <li>Double</li> |
| * <li>String</li> |
| * <li>null</li> |
| * </ul> |
| * @param extras the Bundle to check |
| */ |
| public static void validateSyncExtrasBundle(Bundle extras) { |
| try { |
| for (String key : extras.keySet()) { |
| Object value = extras.get(key); |
| if (value == null) continue; |
| if (value instanceof Long) continue; |
| if (value instanceof Integer) continue; |
| if (value instanceof Boolean) continue; |
| if (value instanceof Float) continue; |
| if (value instanceof Double) continue; |
| if (value instanceof String) continue; |
| throw new IllegalArgumentException("unexpected value type: " |
| + value.getClass().getName()); |
| } |
| } catch (IllegalArgumentException e) { |
| throw e; |
| } catch (RuntimeException exc) { |
| throw new IllegalArgumentException("error unparceling Bundle", exc); |
| } |
| } |
| |
| public void cancelSync(Uri uri) { |
| try { |
| getContentService().cancelSync(uri); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| private final class CursorWrapperInner extends CursorWrapper { |
| private IContentProvider mContentProvider; |
| public static final String TAG="CursorWrapperInner"; |
| private boolean mCloseFlag = false; |
| |
| CursorWrapperInner(Cursor cursor, IContentProvider icp) { |
| super(cursor); |
| mContentProvider = icp; |
| } |
| |
| @Override |
| public void close() { |
| super.close(); |
| ContentResolver.this.releaseProvider(mContentProvider); |
| mCloseFlag = true; |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| if(!mCloseFlag) { |
| ContentResolver.this.releaseProvider(mContentProvider); |
| } |
| } finally { |
| super.finalize(); |
| } |
| } |
| } |
| |
| private final class ParcelFileDescriptorInner extends ParcelFileDescriptor { |
| private IContentProvider mContentProvider; |
| public static final String TAG="ParcelFileDescriptorInner"; |
| private boolean mReleaseProviderFlag = false; |
| |
| ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) { |
| super(pfd); |
| mContentProvider = icp; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if(!mReleaseProviderFlag) { |
| super.close(); |
| ContentResolver.this.releaseProvider(mContentProvider); |
| mReleaseProviderFlag = true; |
| } |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| if (!mReleaseProviderFlag) { |
| close(); |
| } |
| } |
| } |
| |
| /** @hide */ |
| public static final String CONTENT_SERVICE_NAME = "content"; |
| |
| /** @hide */ |
| public static IContentService getContentService() { |
| if (sContentService != null) { |
| return sContentService; |
| } |
| IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME); |
| if (Config.LOGV) Log.v("ContentService", "default service binder = " + b); |
| sContentService = IContentService.Stub.asInterface(b); |
| if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService); |
| return sContentService; |
| } |
| |
| private static IContentService sContentService; |
| private final Context mContext; |
| private static final String TAG = "ContentResolver"; |
| } |