summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.xml465
-rw-r--r--core/java/android/accounts/AccountAuthenticatorCache.java166
-rw-r--r--core/java/android/accounts/AccountManagerService.java12
-rw-r--r--core/java/android/accounts/AuthenticatorBindHelper.java11
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java3
-rw-r--r--core/java/android/content/ContentProvider.java23
-rw-r--r--core/java/android/content/ContentProviderClient.java96
-rw-r--r--core/java/android/content/ContentProviderNative.java26
-rw-r--r--core/java/android/content/ContentResolver.java40
-rw-r--r--core/java/android/content/IContentProvider.java2
-rw-r--r--core/java/android/content/SyncAdapter.java4
-rw-r--r--core/java/android/content/SyncAdapterType.java57
-rw-r--r--core/java/android/content/SyncAdaptersCache.java77
-rw-r--r--core/java/android/content/SyncManager.java276
-rw-r--r--core/java/android/content/SyncableContentProvider.java10
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl1
-rw-r--r--core/java/android/content/pm/ProviderInfo.java6
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java233
-rw-r--r--core/res/res/values/attrs.xml13
-rw-r--r--core/res/res/values/public.xml3
-rw-r--r--services/java/com/android/server/PackageManagerService.java3
-rw-r--r--test-runner/android/test/mock/MockContentProvider.java5
22 files changed, 998 insertions, 534 deletions
diff --git a/api/current.xml b/api/current.xml
index d99e42a..9c7485e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2528,6 +2528,17 @@
visibility="public"
>
</field>
+<field name="contentAuthority"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843373"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="cropToPadding"
type="int"
transient="false"
@@ -12848,123 +12859,6 @@
</parameter>
</method>
</class>
-<class name="AccountAuthenticatorCache"
- extends="java.lang.Object"
- abstract="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<constructor name="AccountAuthenticatorCache"
- type="android.accounts.AccountAuthenticatorCache"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-</constructor>
-<method name="close"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="dump"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="fd" type="java.io.FileDescriptor">
-</parameter>
-<parameter name="fout" type="java.io.PrintWriter">
-</parameter>
-<parameter name="args" type="java.lang.String[]">
-</parameter>
-</method>
-<method name="getAllAuthenticators"
- return="java.util.Collection&lt;android.accounts.AccountAuthenticatorCache.AuthenticatorInfo&gt;"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getAuthenticatorInfo"
- return="android.accounts.AccountAuthenticatorCache.AuthenticatorInfo"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="type" type="java.lang.String">
-</parameter>
-</method>
-<method name="parseAuthenticatorInfo"
- return="android.accounts.AccountAuthenticatorCache.AuthenticatorInfo"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="service" type="android.content.pm.ResolveInfo">
-</parameter>
-<exception name="IOException" type="java.io.IOException">
-</exception>
-<exception name="XmlPullParserException" type="org.xmlpull.v1.XmlPullParserException">
-</exception>
-</method>
-</class>
-<class name="AccountAuthenticatorCache.AuthenticatorInfo"
- extends="java.lang.Object"
- abstract="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<field name="mComponentName"
- type="android.content.ComponentName"
- transient="false"
- volatile="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="mType"
- type="java.lang.String"
- transient="false"
- volatile="false"
- static="false"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-</class>
<class name="AccountAuthenticatorResponse"
extends="java.lang.Object"
abstract="false"
@@ -13784,93 +13678,6 @@
</parameter>
</method>
</class>
-<class name="AuthenticatorBindHelper"
- extends="java.lang.Object"
- abstract="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<constructor name="AuthenticatorBindHelper"
- type="android.accounts.AuthenticatorBindHelper"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="authenticatorCache" type="android.accounts.AccountAuthenticatorCache">
-</parameter>
-<parameter name="handler" type="android.os.Handler">
-</parameter>
-<parameter name="messageWhatConnected" type="int">
-</parameter>
-<parameter name="messageWhatDisconnected" type="int">
-</parameter>
-</constructor>
-<method name="bind"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="authenticatorType" type="java.lang.String">
-</parameter>
-<parameter name="callback" type="android.accounts.AuthenticatorBindHelper.Callback">
-</parameter>
-</method>
-<method name="unbind"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="callbackToUnbind" type="android.accounts.AuthenticatorBindHelper.Callback">
-</parameter>
-</method>
-</class>
-<interface name="AuthenticatorBindHelper.Callback"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="onConnected"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="service" type="android.os.IBinder">
-</parameter>
-</method>
-<method name="onDisconnected"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-</interface>
<class name="AuthenticatorException"
extends="java.lang.Exception"
abstract="false"
@@ -25041,6 +24848,187 @@
</parameter>
</method>
</class>
+<class name="ContentProviderClient"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="bulkInsert"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="initialValues" type="android.content.ContentValues[]">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="coerceToContentProvider"
+ return="android.content.ContentProvider"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="delete"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="getType"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="insert"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="initialValues" type="android.content.ContentValues">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="openAssetFile"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mode" type="java.lang.String">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="openFile"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mode" type="java.lang.String">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="query"
+ return="android.database.Cursor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="projection" type="java.lang.String[]">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+<parameter name="sortOrder" type="java.lang.String">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="release"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="update"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="values" type="android.content.ContentValues">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+</class>
<class name="ContentQueryMap"
extends="java.util.Observable"
abstract="false"
@@ -25143,6 +25131,32 @@
<parameter name="context" type="android.content.Context">
</parameter>
</constructor>
+<method name="acquireContentProviderClient"
+ return="android.content.ContentProviderClient"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="acquireContentProviderClient"
+ return="android.content.ContentProviderClient"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
<method name="bulkInsert"
return="int"
abstract="false"
@@ -33095,6 +33109,47 @@
</parameter>
</method>
</interface>
+<class name="SyncAdapterType"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SyncAdapterType"
+ type="android.content.SyncAdapterType"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="accountType" type="java.lang.String">
+</parameter>
+</constructor>
+<field name="accountType"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="authority"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="UriMatcher"
extends="java.lang.Object"
abstract="false"
@@ -36787,7 +36842,7 @@
volatile="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
index 6a14ff8..83aae3a 100644
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ b/core/java/android/accounts/AccountAuthenticatorCache.java
@@ -19,6 +19,7 @@ package android.accounts;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.RegisteredServicesCache;
import android.content.res.XmlResourceParser;
import android.content.res.TypedArray;
import android.content.BroadcastReceiver;
@@ -46,172 +47,27 @@ import org.xmlpull.v1.XmlPullParser;
* A cache of services that export the {@link IAccountAuthenticator} interface. This cache
* is built by interrogating the {@link PackageManager} and is updated as packages are added,
* removed and changed. The authenticators are referred to by their account type and
- * are made available via the {@link #getAuthenticatorInfo(String type)} method.
+ * are made available via the {@link RegisteredServicesCache#getServiceInfo} method.
+ * @hide
*/
-public class AccountAuthenticatorCache {
+/* package private */ class AccountAuthenticatorCache extends RegisteredServicesCache<String> {
private static final String TAG = "Account";
private static final String SERVICE_INTERFACE = "android.accounts.AccountAuthenticator";
private static final String SERVICE_META_DATA = "android.accounts.AccountAuthenticator";
-
- private volatile Map<String, AuthenticatorInfo> mAuthenticators;
-
- private final Context mContext;
- private BroadcastReceiver mReceiver;
+ private static final String ATTRIBUTES_NAME = "account-authenticator";
public AccountAuthenticatorCache(Context context) {
- mContext = context;
- mReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- buildAuthenticatorList();
- }
- };
- }
-
- protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
- getAllAuthenticators();
- Map<String, AuthenticatorInfo> authenticators = mAuthenticators;
- fout.println("AccountAuthenticatorCache: " + authenticators.size() + " authenticators");
- for (AuthenticatorInfo info : authenticators.values()) {
- fout.println(" " + info);
- }
- }
-
- private void monitorPackageChanges() {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- mContext.registerReceiver(mReceiver, intentFilter);
- }
-
- /**
- * Value type that describes an AccountAuthenticator. The information within can be used
- * to bind to its {@link IAccountAuthenticator} interface.
- */
- public class AuthenticatorInfo {
- public final String mType;
- public final ComponentName mComponentName;
-
- private AuthenticatorInfo(String type, ComponentName componentName) {
- mType = type;
- mComponentName = componentName;
- }
-
- public String toString() {
- return "AuthenticatorInfo: " + mType + ", " + mComponentName;
- }
- }
-
- /**
- * Accessor for the registered authenticators.
- * @param type the account type of the authenticator
- * @return the AuthenticatorInfo that matches the account type or null if none is present
- */
- public AuthenticatorInfo getAuthenticatorInfo(String type) {
- if (mAuthenticators == null) {
- monitorPackageChanges();
- buildAuthenticatorList();
- }
- return mAuthenticators.get(type);
- }
-
- /**
- * @return a collection of {@link AuthenticatorInfo} objects for all
- * registered authenticators.
- */
- public Collection<AuthenticatorInfo> getAllAuthenticators() {
- if (mAuthenticators == null) {
- monitorPackageChanges();
- buildAuthenticatorList();
- }
- return Collections.unmodifiableCollection(mAuthenticators.values());
- }
-
- /**
- * Stops the monitoring of package additions, removals and changes.
- */
- public void close() {
- if (mReceiver != null) {
- mContext.unregisterReceiver(mReceiver);
- mReceiver = null;
- }
- }
-
- protected void finalize() throws Throwable {
- if (mReceiver != null) {
- Log.e(TAG, "AccountAuthenticatorCache finalized without being closed");
- }
- close();
- super.finalize();
+ super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME);
}
- private void buildAuthenticatorList() {
- Map<String, AuthenticatorInfo> authenticators = Maps.newHashMap();
- PackageManager pm = mContext.getPackageManager();
-
- List<ResolveInfo> services =
- pm.queryIntentServices(new Intent(SERVICE_INTERFACE), PackageManager.GET_META_DATA);
-
- for (ResolveInfo resolveInfo : services) {
- try {
- AuthenticatorInfo info = parseAuthenticatorInfo(resolveInfo);
- if (info != null) {
- authenticators.put(info.mType, info);
- } else {
- Log.w(TAG, "Unable to load input method " + resolveInfo.toString());
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
- } catch (IOException e) {
- Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
- }
- }
-
- mAuthenticators = authenticators;
- }
-
- public AuthenticatorInfo parseAuthenticatorInfo(ResolveInfo service)
- throws XmlPullParserException, IOException {
- ServiceInfo si = service.serviceInfo;
- ComponentName componentName = new ComponentName(si.packageName, si.name);
-
- PackageManager pm = mContext.getPackageManager();
- String authenticatorType = null;
-
- XmlResourceParser parser = null;
+ public String parseServiceAttributes(AttributeSet attrs) {
+ TypedArray sa = mContext.getResources().obtainAttributes(attrs,
+ com.android.internal.R.styleable.AccountAuthenticator);
try {
- parser = si.loadXmlMetaData(pm, SERVICE_META_DATA);
- if (parser == null) {
- throw new XmlPullParserException("No " + SERVICE_META_DATA + " meta-data");
- }
-
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- }
-
- String nodeName = parser.getName();
- if (!"account-authenticator".equals(nodeName)) {
- throw new XmlPullParserException(
- "Meta-data does not start with account-authenticator tag");
- }
-
- TypedArray sa = mContext.getResources().obtainAttributes(attrs,
- com.android.internal.R.styleable.AccountAuthenticator);
- authenticatorType = sa.getString(
- com.android.internal.R.styleable.AccountAuthenticator_accountType);
- sa.recycle();
+ return sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
} finally {
- if (parser != null) parser.close();
- }
-
- if (authenticatorType == null) {
- return null;
+ sa.recycle();
}
-
- return new AuthenticatorInfo(authenticatorType, componentName);
}
}
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index ef0875c..545241f 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -44,13 +44,10 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.R;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
/**
* A system service that provides account, password, and authtoken management for all
@@ -221,12 +218,13 @@ public class AccountManagerService extends IAccountManager.Stub {
public String[] getAuthenticatorTypes() {
long identityToken = clearCallingIdentity();
try {
- Collection<AccountAuthenticatorCache.AuthenticatorInfo> authenticatorCollection =
- mAuthenticatorCache.getAllAuthenticators();
+ Collection<AccountAuthenticatorCache.ServiceInfo<String>> authenticatorCollection =
+ mAuthenticatorCache.getAllServices();
String[] types = new String[authenticatorCollection.size()];
int i = 0;
- for (AccountAuthenticatorCache.AuthenticatorInfo authenticator : authenticatorCollection) {
- types[i] = authenticator.mType;
+ for (AccountAuthenticatorCache.ServiceInfo<String> authenticator
+ : authenticatorCollection) {
+ types[i] = authenticator.type;
i++;
}
return types;
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
index 6c28485..9d2ccf6 100644
--- a/core/java/android/accounts/AuthenticatorBindHelper.java
+++ b/core/java/android/accounts/AuthenticatorBindHelper.java
@@ -38,6 +38,7 @@ import com.google.android.collect.Maps;
* to the same authenticator, with each bind call guaranteed to return either
* {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
* itself succeeds, even if the authenticator is already bound internally.
+ * @hide
*/
public class AuthenticatorBindHelper {
private static final String TAG = "Accounts";
@@ -94,8 +95,8 @@ public class AuthenticatorBindHelper {
// otherwise find the component name for the authenticator and initiate a bind
// if no authenticator or the bind fails then return false, otherwise return true
- AccountAuthenticatorCache.AuthenticatorInfo authenticatorInfo =
- mAuthenticatorCache.getAuthenticatorInfo(authenticatorType);
+ AccountAuthenticatorCache.ServiceInfo authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(authenticatorType);
if (authenticatorInfo == null) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "there is no authenticator for " + authenticatorType
@@ -108,13 +109,13 @@ public class AuthenticatorBindHelper {
Intent intent = new Intent();
intent.setAction("android.accounts.AccountAuthenticator");
- intent.setComponent(authenticatorInfo.mComponentName);
+ intent.setComponent(authenticatorInfo.componentName);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.mComponentName);
+ Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
}
if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.mComponentName + " failed");
+ Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
}
return false;
}
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index edef332..1452985 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -160,8 +160,7 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
// work with.
mDb = mOpenHelper.getWritableDatabase();
onAccountsChanged(accounts);
- TempProviderSyncAdapter syncAdapter =
- (TempProviderSyncAdapter)getSyncAdapter();
+ TempProviderSyncAdapter syncAdapter = getTempProviderSyncAdapter();
if (syncAdapter != null) {
syncAdapter.onAccountsChanged(accounts);
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5cc5730..3a080a0 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -170,12 +170,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.openAssetFile(uri, mode);
}
- public ISyncAdapter getSyncAdapter() {
- checkWritePermission(null);
- SyncAdapter sa = ContentProvider.this.getSyncAdapter();
- return sa != null ? sa.getISyncAdapter() : null;
- }
-
private void checkReadPermission(Uri uri) {
final String rperm = getReadPermission();
final int pid = Binder.getCallingPid();
@@ -551,23 +545,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
}
/**
- * Get the sync adapter that is to be used by this content provider.
- * This is intended for use by the sync system. If null then this
- * content provider is considered not syncable.
- * This method can be called from multiple
- * threads, as described in
- * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
- * Processes and Threads</a>.
- *
- * @return the SyncAdapter that is to be used by this ContentProvider, or null
- * if this ContentProvider is not syncable
- * @hide
- */
- public SyncAdapter getSyncAdapter() {
- return null;
- }
-
- /**
* Returns true if this instance is a temporary content provider.
* @return true if this instance is a temporary content provider
*/
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
new file mode 100644
index 0000000..9902807
--- /dev/null
+++ b/core/java/android/content/ContentProviderClient.java
@@ -0,0 +1,96 @@
+package android.content;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.ParcelFileDescriptor;
+import android.content.res.AssetFileDescriptor;
+
+import java.io.FileNotFoundException;
+
+/**
+ * The public interface object used to interact with a {@link ContentProvider}. This is obtained by
+ * calling {@link ContentResolver#acquireContentProviderClient}. This object must be released
+ * using {@link #release} in order to indicate to the system that the {@link ContentProvider} is
+ * no longer needed and can be killed to free up resources.
+ */
+public class ContentProviderClient {
+ private final IContentProvider mContentProvider;
+ private final ContentResolver mContentResolver;
+
+ /**
+ * @hide
+ */
+ ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider) {
+ mContentProvider = contentProvider;
+ mContentResolver = contentResolver;
+ }
+
+ /** {@see ContentProvider#query} */
+ public Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) throws RemoteException {
+ return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder);
+ }
+
+ /** {@see ContentProvider#getType} */
+ public String getType(Uri url) throws RemoteException {
+ return mContentProvider.getType(url);
+ }
+
+ /** {@see ContentProvider#insert} */
+ public Uri insert(Uri url, ContentValues initialValues)
+ throws RemoteException {
+ return mContentProvider.insert(url, initialValues);
+ }
+
+ /** {@see ContentProvider#bulkInsert} */
+ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
+ return mContentProvider.bulkInsert(url, initialValues);
+ }
+
+ /** {@see ContentProvider#delete} */
+ public int delete(Uri url, String selection, String[] selectionArgs)
+ throws RemoteException {
+ return mContentProvider.delete(url, selection, selectionArgs);
+ }
+
+ /** {@see ContentProvider#update} */
+ public int update(Uri url, ContentValues values, String selection,
+ String[] selectionArgs) throws RemoteException {
+ return mContentProvider.update(url, values, selection, selectionArgs);
+ }
+
+ /** {@see ContentProvider#openFile} */
+ public ParcelFileDescriptor openFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ return mContentProvider.openFile(url, mode);
+ }
+
+ /** {@see ContentProvider#openAssetFile} */
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ return mContentProvider.openAssetFile(url, mode);
+ }
+
+ /**
+ * Call this to indicate to the system that the associated {@link ContentProvider} is no
+ * longer needed by this {@link ContentProviderClient}.
+ * @return true if this was release, false if it was already released
+ */
+ public boolean release() {
+ return mContentResolver.releaseProvider(mContentProvider);
+ }
+
+ /**
+ * Get a reference to the {@link ContentProvider} that is associated with this
+ * client. If the {@link ContentProvider} is running in a different process then
+ * null will be returned. This can be used if you know you are running in the same
+ * process as a provider, and want to get direct access to its implementation details.
+ *
+ * @return If the associated {@link ContentProvider} is local, returns it.
+ * Otherwise returns null.
+ */
+ public ContentProvider getLocalContentProvider() {
+ return ContentProvider.coerceToLocalContentProvider(mContentProvider);
+ }
+}
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index e5e3f74..f9282e2 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -206,15 +206,6 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
}
return true;
}
-
- case GET_SYNC_ADAPTER_TRANSACTION:
- {
- data.enforceInterface(IContentProvider.descriptor);
- ISyncAdapter sa = getSyncAdapter();
- reply.writeNoException();
- reply.writeStrongBinder(sa != null ? sa.asBinder() : null);
- return true;
- }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -456,23 +447,6 @@ final class ContentProviderProxy implements IContentProvider
return fd;
}
- public ISyncAdapter getSyncAdapter() throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
-
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- mRemote.transact(IContentProvider.GET_SYNC_ADAPTER_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- ISyncAdapter syncAdapter = ISyncAdapter.Stub.asInterface(reply.readStrongBinder());
-
- data.recycle();
- reply.recycle();
-
- return syncAdapter;
- }
-
private IBinder mRemote;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 6577236..79cbc49 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -590,6 +590,46 @@ public abstract class ContentResolver {
}
/**
+ * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * that services the content at uri, starting the provider if necessary. Returns
+ * null if there is no provider associated wih the uri. The caller must indicate that they are
+ * done with the provider by calling {@link ContentProviderClient#release} which will allow
+ * the system to release the provider it it determines that there is no other reason for
+ * keeping it active.
+ * @param uri specifies which provider should be acquired
+ * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * that services the content at uri or null if there isn't one.
+ */
+ public final ContentProviderClient acquireContentProviderClient(Uri uri) {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * with the authority of name, starting the provider if necessary. Returns
+ * null if there is no provider associated wih the uri. The caller must indicate that they are
+ * done with the provider by calling {@link ContentProviderClient#release} which will allow
+ * the system to release the provider it it determines that there is no other reason for
+ * keeping it active.
+ * @param name specifies which provider should be acquired
+ * @return a {@link ContentProviderClient} that is associated with the {@link ContentProvider}
+ * with the authority of name or null if there isn't one.
+ */
+ public final ContentProviderClient acquireContentProviderClient(String name) {
+ IContentProvider provider = acquireProvider(name);
+ if (provider != null) {
+ return new ContentProviderClient(this, provider);
+ }
+
+ return null;
+ }
+
+ /**
* Register an observer class that gets callbacks when data identified by a
* given content URI changes.
*
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 0606956..0b81245 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -55,7 +55,6 @@ public interface IContentProvider extends IInterface {
throws RemoteException, FileNotFoundException;
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException;
- public ISyncAdapter getSyncAdapter() throws RemoteException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
@@ -65,7 +64,6 @@ public interface IContentProvider extends IInterface {
static final int INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
static final int DELETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
static final int UPDATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
- static final int GET_SYNC_ADAPTER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
index 3e91626..c658fb7 100644
--- a/core/java/android/content/SyncAdapter.java
+++ b/core/java/android/content/SyncAdapter.java
@@ -43,9 +43,9 @@ public abstract class SyncAdapter {
Transport mTransport = new Transport();
/**
- * Get the Transport object. (note this is package private).
+ * Get the Transport object.
*/
- final ISyncAdapter getISyncAdapter()
+ public final ISyncAdapter getISyncAdapter()
{
return mTransport;
}
diff --git a/core/java/android/content/SyncAdapterType.java b/core/java/android/content/SyncAdapterType.java
new file mode 100644
index 0000000..368a879
--- /dev/null
+++ b/core/java/android/content/SyncAdapterType.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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.text.TextUtils;
+
+/**
+ * Value type that represents a SyncAdapterType. This object overrides {@link #equals} and
+ * {@link #hashCode}, making it suitable for use as the key of a {@link java.util.Map}
+ */
+public class SyncAdapterType {
+ public final String authority;
+ public final String accountType;
+
+ public SyncAdapterType(String authority, String accountType) {
+ if (TextUtils.isEmpty(authority)) {
+ throw new IllegalArgumentException("the authority must not be empty: " + authority);
+ }
+ if (TextUtils.isEmpty(accountType)) {
+ throw new IllegalArgumentException("the accountType must not be empty: " + accountType);
+ }
+ this.authority = authority;
+ this.accountType = accountType;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof SyncAdapterType)) return false;
+ final SyncAdapterType other = (SyncAdapterType)o;
+ return authority.equals(other.authority) && accountType.equals(other.accountType);
+ }
+
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + authority.hashCode();
+ result = 31 * result + accountType.hashCode();
+ return result;
+ }
+
+ public String toString() {
+ return "SyncAdapterType {name=" + authority + ", type=" + accountType + "}";
+ }
+} \ No newline at end of file
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
new file mode 100644
index 0000000..56e3e75
--- /dev/null
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009 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;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.RegisteredServicesCache;
+import android.content.res.XmlResourceParser;
+import android.content.res.TypedArray;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.google.android.collect.Maps;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A cache of services that export the {@link android.content.ISyncAdapter} interface.
+ * @hide
+ */
+/* package private */ class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
+ private static final String TAG = "Account";
+
+ private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
+ private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
+ private static final String ATTRIBUTES_NAME = "sync-adapter";
+
+ SyncAdaptersCache(Context context) {
+ super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME);
+ }
+
+ public SyncAdapterType parseServiceAttributes(AttributeSet attrs) {
+ TypedArray sa = mContext.getResources().obtainAttributes(attrs,
+ com.android.internal.R.styleable.SyncAdapter);
+ try {
+ final String authority =
+ sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority);
+ final String accountType =
+ sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType);
+ if (authority == null || accountType == null) {
+ return null;
+ }
+ return new SyncAdapterType(authority, accountType);
+ } finally {
+ sa.recycle();
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 4474c62..9bf41c7 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -31,8 +31,8 @@ import android.app.PendingIntent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.content.pm.RegisteredServicesCache;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.ConnectivityManager;
@@ -54,7 +54,6 @@ import android.os.SystemProperties;
import android.provider.Sync;
import android.provider.Settings;
import android.provider.Sync.History;
-import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Config;
@@ -70,7 +69,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -79,6 +77,8 @@ import java.util.PriorityQueue;
import java.util.Random;
import java.util.Observer;
import java.util.Observable;
+import java.util.Set;
+import java.util.HashSet;
/**
* @hide
@@ -124,7 +124,6 @@ class SyncManager implements OnAccountsUpdatedListener {
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
private Context mContext;
- private ContentResolver mContentResolver;
private String mStatusText = "";
private long mHeartbeatTime = 0;
@@ -157,10 +156,11 @@ class SyncManager implements OnAccountsUpdatedListener {
private final PendingIntent mSyncAlarmIntent;
private final PendingIntent mSyncPollAlarmIntent;
+ private final SyncAdaptersCache mSyncAdapters;
+
private BroadcastReceiver mStorageIntentReceiver =
new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- ensureContentResolver();
String action = intent.getAction();
if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -294,6 +294,8 @@ class SyncManager implements OnAccountsUpdatedListener {
mPackageManager = null;
+ mSyncAdapters = new SyncAdaptersCache(mContext);
+
mSyncAlarmIntent = PendingIntent.getBroadcast(
mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
@@ -467,12 +469,6 @@ class SyncManager implements OnAccountsUpdatedListener {
return mSyncSettings;
}
- private void ensureContentResolver() {
- if (mContentResolver == null) {
- mContentResolver = mContext.getContentResolver();
- }
- }
-
private void ensureAlarmService() {
if (mAlarmService == null) {
mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
@@ -590,20 +586,33 @@ class SyncManager implements OnAccountsUpdatedListener {
source = Sync.History.SOURCE_SERVER;
}
- List<String> names = new ArrayList<String>();
- List<ProviderInfo> providers = new ArrayList<ProviderInfo>();
- populateProvidersList(url, names, providers);
+ // compile a list of authorities that have sync adapters
+ // for each authority sync each account that matches a sync adapter
+ Set<String> syncableAuthorities = new HashSet<String>();
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
+ mSyncAdapters.getAllServices()) {
+ syncableAuthorities.add(syncAdapter.type.authority);
+ }
- final int numProviders = providers.size();
- for (int i = 0; i < numProviders; i++) {
- if (!providers.get(i).isSyncable) continue;
- final String name = names.get(i);
+ // if the url was specified then replace the list of authorities with just this authority
+ // or clear it if this authority isn't syncable
+ if (url != null) {
+ boolean isSyncable = syncableAuthorities.contains(url.getAuthority());
+ syncableAuthorities.clear();
+ if (isSyncable) syncableAuthorities.add(url.getAuthority());
+ }
+
+ for (String authority : syncableAuthorities) {
for (Account account : accounts) {
- scheduleSyncOperation(new SyncOperation(account, source, name, extras, delay));
- // TODO: remove this when Calendar supports multiple accounts. Until then
- // pretend that only the first account exists when syncing calendar.
- if ("calendar".equals(name)) {
- break;
+ if (mSyncAdapters.getServiceInfo(new SyncAdapterType(authority, account.mType))
+ != null) {
+ scheduleSyncOperation(
+ new SyncOperation(account, source, authority, extras, delay));
+ // TODO: remove this when Calendar supports multiple accounts. Until then
+ // pretend that only the first account exists when syncing calendar.
+ if ("calendar".equals(authority)) {
+ break;
+ }
}
}
}
@@ -613,32 +622,6 @@ class SyncManager implements OnAccountsUpdatedListener {
mStatusText = message;
}
- private void populateProvidersList(Uri url, List<String> names, List<ProviderInfo> providers) {
- try {
- final IPackageManager packageManager = getPackageManager();
- if (url == null) {
- packageManager.querySyncProviders(names, providers);
- } else {
- final String authority = url.getAuthority();
- ProviderInfo info = packageManager.resolveContentProvider(url.getAuthority(), 0);
- if (info != null) {
- // only set this provider if the requested authority is the primary authority
- String[] providerNames = info.authority.split(";");
- if (url.getAuthority().equals(providerNames[0])) {
- names.add(authority);
- providers.add(info);
- }
- }
- }
- } catch (RemoteException ex) {
- // we should really never get this, but if we do then clear the lists, which
- // will result in the dropping of the sync request
- Log.e(TAG, "error trying to get the ProviderInfo for " + url, ex);
- names.clear();
- providers.clear();
- }
- }
-
public void scheduleLocalSync(Uri url) {
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
@@ -672,8 +655,7 @@ class SyncManager implements OnAccountsUpdatedListener {
public void updateHeartbeatTime() {
mHeartbeatTime = SystemClock.elapsedRealtime();
- ensureContentResolver();
- mContentResolver.notifyChange(Sync.Active.CONTENT_URI,
+ mContext.getContentResolver().notifyChange(Sync.Active.CONTENT_URI,
null /* this change wasn't made through an observer */);
}
@@ -738,8 +720,7 @@ class SyncManager implements OnAccountsUpdatedListener {
}
// Cap the delay
- ensureContentResolver();
- long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContentResolver,
+ long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContext.getContentResolver(),
Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
@@ -954,21 +935,19 @@ class SyncManager implements OnAccountsUpdatedListener {
/**
* @hide
*/
- class ActiveSyncContext extends ISyncContext.Stub {
+ class ActiveSyncContext extends ISyncContext.Stub implements ServiceConnection {
final SyncOperation mSyncOperation;
final long mHistoryRowId;
- final IContentProvider mContentProvider;
- final ISyncAdapter mSyncAdapter;
+ ISyncAdapter mSyncAdapter;
final long mStartTime;
long mTimeoutStartTime;
- public ActiveSyncContext(SyncOperation syncOperation, IContentProvider contentProvider,
- ISyncAdapter syncAdapter, long historyRowId) {
+ public ActiveSyncContext(SyncOperation syncOperation,
+ long historyRowId) {
super();
mSyncOperation = syncOperation;
mHistoryRowId = historyRowId;
- mContentProvider = contentProvider;
- mSyncAdapter = syncAdapter;
+ mSyncAdapter = null;
mStartTime = SystemClock.elapsedRealtime();
mTimeoutStartTime = mStartTime;
}
@@ -994,6 +973,37 @@ class SyncManager implements OnAccountsUpdatedListener {
.append(", syncOperation ").append(mSyncOperation);
}
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
+ msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
+ mSyncHandler.sendMessage(msg);
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
+ msg.obj = new ServiceConnectionData(this, null);
+ mSyncHandler.sendMessage(msg);
+ }
+
+ boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
+ }
+ Intent intent = new Intent();
+ intent.setAction("android.content.SyncAdapter");
+ intent.setComponent(info.componentName);
+ return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ void unBindFromSyncAdapter() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
+ }
+ mContext.unbindService(this);
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -1010,6 +1020,11 @@ class SyncManager implements OnAccountsUpdatedListener {
dumpSyncHistory(sb);
}
pw.println(sb.toString());
+
+ pw.println("SyncAdapters:");
+ for (RegisteredServicesCache.ServiceInfo info : mSyncAdapters.getAllServices()) {
+ pw.println(" " + info);
+ }
}
protected void dumpSyncState(StringBuilder sb) {
@@ -1277,6 +1292,15 @@ class SyncManager implements OnAccountsUpdatedListener {
}
}
+ class ServiceConnectionData {
+ public final ActiveSyncContext activeSyncContext;
+ public final ISyncAdapter syncAdapter;
+ ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
+ this.activeSyncContext = activeSyncContext;
+ this.syncAdapter = syncAdapter;
+ }
+ }
+
/**
* Handles SyncOperation Messages that are posted to the associated
* HandlerThread.
@@ -1286,6 +1310,8 @@ class SyncManager implements OnAccountsUpdatedListener {
private static final int MESSAGE_SYNC_FINISHED = 1;
private static final int MESSAGE_SYNC_ALARM = 2;
private static final int MESSAGE_CHECK_ALARMS = 3;
+ private static final int MESSAGE_SERVICE_CONNECTED = 4;
+ private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
@@ -1357,6 +1383,53 @@ class SyncManager implements OnAccountsUpdatedListener {
runStateIdle();
break;
+ case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
+ ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
+ + msgData.activeSyncContext
+ + " active is " + mActiveSyncContext);
+ }
+ // check that this isn't an old message
+ if (mActiveSyncContext == msgData.activeSyncContext) {
+ runBoundToSyncAdapter(msgData.syncAdapter);
+ }
+ break;
+ }
+
+ case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
+ ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
+ + msgData.activeSyncContext
+ + " active is " + mActiveSyncContext);
+ }
+ // check that this isn't an old message
+ if (mActiveSyncContext == msgData.activeSyncContext) {
+ // cancel the sync if we have a syncadapter, which means one is
+ // outstanding
+ if (mActiveSyncContext.mSyncAdapter != null) {
+ try {
+ mActiveSyncContext.mSyncAdapter.cancelSync();
+ } catch (RemoteException e) {
+ // we don't need to retry this in this case
+ }
+ }
+
+ // pretend that the sync failed with an IOException,
+ // which is a soft error
+ SyncResult syncResult = new SyncResult();
+ syncResult.stats.numIoExceptions++;
+ runSyncFinishedOrCanceled(syncResult);
+
+ // since we are no longer syncing, check if it is time to start a new
+ // sync
+ runStateIdle();
+ }
+
+ break;
+ }
+
case SyncHandler.MESSAGE_SYNC_ALARM: {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
if (isLoggable) {
@@ -1542,67 +1615,68 @@ class SyncManager implements OnAccountsUpdatedListener {
mSyncQueue.popHead();
}
- String providerName = syncOperation.authority;
- ensureContentResolver();
- IContentProvider contentProvider;
-
- // acquire the provider and update the sync history
- try {
- contentProvider = mContentResolver.acquireProvider(providerName);
- if (contentProvider == null) {
- Log.e(TAG, "Provider " + providerName + " doesn't exist");
- return;
- }
- if (contentProvider.getSyncAdapter() == null) {
- Log.e(TAG, "Provider " + providerName + " isn't syncable, " + contentProvider);
- return;
+ // connect to the sync adapter
+ SyncAdapterType syncAdapterType = new SyncAdapterType(syncOperation.authority,
+ syncOperation.account.mType);
+ RegisteredServicesCache.ServiceInfo syncAdapterInfo =
+ mSyncAdapters.getServiceInfo(syncAdapterType);
+ if (syncAdapterInfo == null) {
+ if (Config.LOGD) {
+ Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
}
- } catch (RemoteException remoteExc) {
- Log.e(TAG, "Caught a RemoteException while preparing for sync, rescheduling "
- + syncOperation, remoteExc);
- rescheduleWithDelay(syncOperation);
+ runStateIdle();
return;
- } catch (RuntimeException exc) {
- Log.e(TAG, "Caught a RuntimeException while validating sync of " + providerName,
- exc);
+ }
+
+ ActiveSyncContext activeSyncContext =
+ new ActiveSyncContext(syncOperation, insertStartSyncEvent(syncOperation));
+ mActiveSyncContext = activeSyncContext;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
+ }
+ mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
+ Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
+ mActiveSyncContext = null;
+ mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ runStateIdle();
return;
}
- final long historyRowId = insertStartSyncEvent(syncOperation);
+ mSyncWakeLock.acquire();
+ // no need to schedule an alarm, as that will be done by our caller.
+
+ // the next step will occur when we get either a timeout or a
+ // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
+ }
+ private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
+ mActiveSyncContext.mSyncAdapter = syncAdapter;
+ final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
try {
- ISyncAdapter syncAdapter = contentProvider.getSyncAdapter();
- ActiveSyncContext activeSyncContext = new ActiveSyncContext(syncOperation,
- contentProvider, syncAdapter, historyRowId);
- mSyncWakeLock.acquire();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "starting sync of " + syncOperation);
- }
- syncAdapter.startSync(activeSyncContext, syncOperation.account,
+ syncAdapter.startSync(mActiveSyncContext, syncOperation.account,
syncOperation.extras);
- mActiveSyncContext = activeSyncContext;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
} catch (RemoteException remoteExc) {
if (Config.LOGD) {
Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
}
+ mActiveSyncContext.unBindFromSyncAdapter();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
rescheduleWithDelay(syncOperation);
} catch (RuntimeException exc) {
+ mActiveSyncContext.unBindFromSyncAdapter();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation,
exc);
}
-
- // no need to schedule an alarm, as that will be done by our caller.
}
private void runSyncFinishedOrCanceled(SyncResult syncResult) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
+ final ActiveSyncContext activeSyncContext = mActiveSyncContext;
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
@@ -1642,10 +1716,12 @@ class SyncManager implements OnAccountsUpdatedListener {
Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation "
+ syncOperation);
}
- try {
- activeSyncContext.mSyncAdapter.cancelSync();
- } catch (RemoteException e) {
- // we don't need to retry this in this case
+ if (activeSyncContext.mSyncAdapter != null) {
+ try {
+ activeSyncContext.mSyncAdapter.cancelSync();
+ } catch (RemoteException e) {
+ // we don't need to retry this in this case
+ }
}
historyMessage = History.MESG_CANCELED;
downstreamActivity = 0;
@@ -1655,7 +1731,7 @@ class SyncManager implements OnAccountsUpdatedListener {
stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
upstreamActivity, downstreamActivity, elapsedTime);
- mContentResolver.releaseProvider(activeSyncContext.mContentProvider);
+ activeSyncContext.unBindFromSyncAdapter();
if (syncResult != null && syncResult.tooManyDeletions) {
installHandleTooManyDeletesNotification(syncOperation.account,
diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java
index 93ebbea..ab4e91c 100644
--- a/core/java/android/content/SyncableContentProvider.java
+++ b/core/java/android/content/SyncableContentProvider.java
@@ -33,6 +33,16 @@ import java.util.Map;
public abstract class SyncableContentProvider extends ContentProvider {
protected abstract boolean isTemporary();
+ private volatile TempProviderSyncAdapter mTempProviderSyncAdapter;
+
+ public void setTempProviderSyncAdapter(TempProviderSyncAdapter syncAdapter) {
+ mTempProviderSyncAdapter = syncAdapter;
+ }
+
+ public TempProviderSyncAdapter getTempProviderSyncAdapter() {
+ return mTempProviderSyncAdapter;
+ }
+
/**
* Close resources that must be closed. You must call this to properly release
* the resources used by the SyncableContentProvider.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index d3f6f3c..bf963ff 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -119,6 +119,7 @@ interface IPackageManager {
* providers that can sync.
* @param outInfo Filled in with a list of the ProviderInfo for each
* name in 'outNames'.
+ * @deprecated
*/
void querySyncProviders(inout List<String> outNames,
inout List<ProviderInfo> outInfo);
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index b67ddf6..1d11b31 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -65,7 +65,11 @@ public final class ProviderInfo extends ComponentInfo
* running in the same process. Higher goes first. */
public int initOrder = 0;
- /** Whether or not this provider is syncable. */
+ /**
+ * Whether or not this provider is syncable.
+ * @deprecated This flag is now being ignored. The current way to make a provider
+ * syncable is to provide a SyncAdapter service for a given provider/account type.
+ */
public boolean isSyncable = false;
public ProviderInfo() {
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
new file mode 100644
index 0000000..d8f8478
--- /dev/null
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2009 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.pm;
+
+import android.content.Context;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ComponentName;
+import android.content.res.XmlResourceParser;
+import android.util.Log;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import java.util.Map;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.IOException;
+
+import com.google.android.collect.Maps;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A cache of registered services. This cache
+ * is built by interrogating the {@link PackageManager} and is updated as packages are added,
+ * removed and changed. The services are referred to by type V and
+ * are made available via the {@link #getServiceInfo} method.
+ * @hide
+ */
+public abstract class RegisteredServicesCache<V> {
+ private static final String TAG = "PackageManager";
+
+ public final Context mContext;
+ private final String mInterfaceName;
+ private final String mMetaDataName;
+ private final String mAttributesName;
+
+ // no need to be synchronized since the map is never changed once mService is written
+ private volatile Map<V, ServiceInfo<V>> mServices;
+
+ // synchronized on "this"
+ private BroadcastReceiver mReceiver = null;
+
+ public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
+ String attributeName) {
+ mContext = context;
+ mInterfaceName = interfaceName;
+ mMetaDataName = metaDataName;
+ mAttributesName = attributeName;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ getAllServices();
+ Map<V, ServiceInfo<V>> services = mServices;
+ fout.println("RegisteredServicesCache: " + services.size() + " services");
+ for (ServiceInfo info : services.values()) {
+ fout.println(" " + info);
+ }
+ }
+
+ private boolean maybeRegisterForPackageChanges() {
+ synchronized (this) {
+ if (mReceiver == null) {
+ synchronized (this) {
+ mReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ mServices = generateServicesMap();
+ }
+ };
+ }
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ mContext.registerReceiver(mReceiver, intentFilter);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void maybeUnregisterForPackageChanges() {
+ synchronized (this) {
+ if (mReceiver != null) {
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+ }
+ }
+
+ /**
+ * Value type that describes a Service. The information within can be used
+ * to bind to the service.
+ */
+ public static class ServiceInfo<V> {
+ public final V type;
+ public final ComponentName componentName;
+
+ private ServiceInfo(V type, ComponentName componentName) {
+ this.type = type;
+ this.componentName = componentName;
+ }
+
+ public String toString() {
+ return "ServiceInfo: " + type + ", " + componentName;
+ }
+ }
+
+ /**
+ * Accessor for the registered authenticators.
+ * @param type the account type of the authenticator
+ * @return the AuthenticatorInfo that matches the account type or null if none is present
+ */
+ public ServiceInfo getServiceInfo(V type) {
+ if (mServices == null) {
+ maybeRegisterForPackageChanges();
+ mServices = generateServicesMap();
+ }
+ return mServices.get(type);
+ }
+
+ /**
+ * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
+ * registered authenticators.
+ */
+ public Collection<ServiceInfo<V>> getAllServices() {
+ if (mServices == null) {
+ maybeRegisterForPackageChanges();
+ mServices = generateServicesMap();
+ }
+ return Collections.unmodifiableCollection(mServices.values());
+ }
+
+ /**
+ * Stops the monitoring of package additions, removals and changes.
+ */
+ public void close() {
+ maybeUnregisterForPackageChanges();
+ }
+
+ protected void finalize() throws Throwable {
+ synchronized (this) {
+ if (mReceiver != null) {
+ Log.e(TAG, "RegisteredServicesCache finalized without being closed");
+ }
+ }
+ close();
+ super.finalize();
+ }
+
+ private Map<V, ServiceInfo<V>> generateServicesMap() {
+ Map<V, ServiceInfo<V>> services = Maps.newHashMap();
+ PackageManager pm = mContext.getPackageManager();
+
+ List<ResolveInfo> resolveInfos =
+ pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA);
+
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ try {
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo);
+ if (info != null) {
+ services.put(info.type, info);
+ } else {
+ Log.w(TAG, "Unable to load input method " + resolveInfo.toString());
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
+ }
+ }
+
+ return services;
+ }
+
+ private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
+ throws XmlPullParserException, IOException {
+ android.content.pm.ServiceInfo si = service.serviceInfo;
+ ComponentName componentName = new ComponentName(si.packageName, si.name);
+
+ PackageManager pm = mContext.getPackageManager();
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, mMetaDataName);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!mAttributesName.equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with " + mAttributesName + " tag");
+ }
+
+ V v = parseServiceAttributes(attrs);
+ if (v == null) {
+ return null;
+ }
+ return new ServiceInfo<V>(v, componentName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ public abstract V parseServiceAttributes(AttributeSet attrs);
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fbbd254..a494145 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3235,6 +3235,19 @@
<attr name="accountType" format="string"/>
</declare-styleable>
+ <!-- =============================== -->
+ <!-- Accounts package class attributes -->
+ <!-- =============================== -->
+
+ <!-- Use <code>account-authenticator</code> as the root tag of the XML resource that
+ describes an account authenticator.
+ -->
+ <declare-styleable name="SyncAdapter">
+ <!-- the authority of a content provider. -->
+ <attr name="contentAuthority" format="string"/>
+ <attr name="accountType"/>
+ </declare-styleable>
+
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 477ca3f..73708ba 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1081,7 +1081,7 @@
<!-- Drawable to use as a background for a taller version of the titlebar -->
<public type="drawable" name="title_bar_tall" id="0x010800a6" />
-
+
<public type="integer" name="config_shortAnimTime" id="0x010e0000" />
<public type="integer" name="config_mediumAnimTime" id="0x010e0001" />
<public type="integer" name="config_longAnimTime" id="0x010e0002" />
@@ -1109,4 +1109,5 @@
<eat-comment />
<public type="attr" name="accountType" id="0x0101026d" />
+ <public type="attr" name="contentAuthority" id="0x0101026e" />
</resources>
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 159bc76..f1dd1d2 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -1616,6 +1616,9 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
+ /**
+ * @deprecated
+ */
public void querySyncProviders(List outNames, List outInfo) {
synchronized (mPackages) {
Iterator<Map.Entry<String, PackageParser.Provider>> i
diff --git a/test-runner/android/test/mock/MockContentProvider.java b/test-runner/android/test/mock/MockContentProvider.java
index d04fc44..f40329d 100644
--- a/test-runner/android/test/mock/MockContentProvider.java
+++ b/test-runner/android/test/mock/MockContentProvider.java
@@ -62,11 +62,6 @@ public class MockContentProvider implements IContentProvider {
}
@SuppressWarnings("unused")
- public ISyncAdapter getSyncAdapter() throws RemoteException {
- throw new UnsupportedOperationException("unimplemented mock method");
- }
-
- @SuppressWarnings("unused")
public String getType(Uri url) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}