decouple SyncAdapter from ContentProvider

This commit is contained in:
Fred Quintana
2009-04-29 17:53:20 -07:00
parent 90b6abd839
commit 718d8a2d7f
22 changed files with 1001 additions and 537 deletions

View File

@@ -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>

View File

@@ -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();
}
};
super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME);
}
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();
}
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();
sa.recycle();
}
if (authenticatorType == null) {
return null;
}
return new AuthenticatorInfo(authenticatorType, componentName);
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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();
@@ -550,23 +544,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ParcelFileDescriptor.open(new File(path), modeBits);
}
/**
* 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

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -589,6 +589,46 @@ public abstract class ContentResolver {
return acquireProvider(mContext, name);
}
/**
* 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.

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 + "}";
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
// 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);
}
if (contentProvider.getSyncAdapter() == null) {
Log.e(TAG, "Provider " + providerName + " isn't syncable, " + contentProvider);
return;
}
} catch (RemoteException remoteExc) {
Log.e(TAG, "Caught a RemoteException while preparing for sync, rescheduling "
+ syncOperation, remoteExc);
rescheduleWithDelay(syncOperation);
return;
} catch (RuntimeException exc) {
Log.e(TAG, "Caught a RuntimeException while validating sync of " + providerName,
exc);
runStateIdle();
return;
}
final long historyRowId = insertStartSyncEvent(syncOperation);
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,
syncOperation.extras);
mActiveSyncContext = activeSyncContext;
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;
}
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 {
syncAdapter.startSync(mActiveSyncContext, syncOperation.account,
syncOperation.extras);
} 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,

View File

@@ -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.

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -61,11 +61,6 @@ public class MockContentProvider implements IContentProvider {
throw new UnsupportedOperationException("unimplemented mock method");
}
@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");