diff options
author | Fred Quintana <fredq@google.com> | 2009-04-29 17:53:20 -0700 |
---|---|---|
committer | Fred Quintana <fredq@google.com> | 2009-04-30 17:28:09 -0700 |
commit | 718d8a2d7ff3e864a73879eb646f46c14ab74d07 (patch) | |
tree | 0368a95bc5cf042971f7151547cbfa221674b020 /core/java | |
parent | 90b6abd83952e42fe2bb15af4fb117d427e640f0 (diff) | |
download | frameworks_base-718d8a2d7ff3e864a73879eb646f46c14ab74d07.zip frameworks_base-718d8a2d7ff3e864a73879eb646f46c14ab74d07.tar.gz frameworks_base-718d8a2d7ff3e864a73879eb646f46c14ab74d07.tar.bz2 |
decouple SyncAdapter from ContentProvider
Diffstat (limited to 'core/java')
17 files changed, 720 insertions, 323 deletions
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); +} |