diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-11-10 16:53:43 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-11-10 16:53:43 -0800 |
commit | ae0cf6dc9eb92282ef92b00ac68bfaca8aad2a1e (patch) | |
tree | 9b6c713114278c57422b89d9271b1b428b8bff37 | |
parent | 971af0030568ba25a83788506a3f29bc9f1cd10d (diff) | |
parent | 5ebbb4a6b3e16f711735ae0615b9a9ea64faad38 (diff) | |
download | frameworks_base-ae0cf6dc9eb92282ef92b00ac68bfaca8aad2a1e.zip frameworks_base-ae0cf6dc9eb92282ef92b00ac68bfaca8aad2a1e.tar.gz frameworks_base-ae0cf6dc9eb92282ef92b00ac68bfaca8aad2a1e.tar.bz2 |
Merge change I50986dd4 into eclair
* changes:
Make the RegisteredSErvices Cache not allow the registered service for a type to change without first uninstalling the previous service for that type, unless the newly installed service is in the system image.
8 files changed, 391 insertions, 90 deletions
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java index ce063a7..d6c76a2 100644 --- a/core/java/android/accounts/AccountAuthenticatorCache.java +++ b/core/java/android/accounts/AccountAuthenticatorCache.java @@ -18,10 +18,16 @@ package android.accounts; import android.content.pm.PackageManager; import android.content.pm.RegisteredServicesCache; +import android.content.pm.XmlSerializerAndParser; import android.content.res.TypedArray; import android.content.Context; import android.util.AttributeSet; import android.text.TextUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; /** * A cache of services that export the {@link IAccountAuthenticator} interface. This cache @@ -33,10 +39,12 @@ import android.text.TextUtils; /* package private */ class AccountAuthenticatorCache extends RegisteredServicesCache<AuthenticatorDescription> { private static final String TAG = "Account"; + private static final MySerializer sSerializer = new MySerializer(); public AccountAuthenticatorCache(Context context) { super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT, - AccountManager.AUTHENTICATOR_META_DATA_NAME, AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME); + AccountManager.AUTHENTICATOR_META_DATA_NAME, + AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer); } public AuthenticatorDescription parseServiceAttributes(String packageName, AttributeSet attrs) { @@ -62,4 +70,16 @@ import android.text.TextUtils; sa.recycle(); } } + + private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> { + public void writeAsXml(AuthenticatorDescription item, XmlSerializer out) + throws IOException { + out.attribute(null, "type", item.type); + } + + public AuthenticatorDescription createFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type")); + } + } } diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 4f59c4e..800ad749 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -72,7 +72,7 @@ import com.android.internal.R; */ public class AccountManagerService extends IAccountManager.Stub - implements RegisteredServicesCacheListener { + implements RegisteredServicesCacheListener<AuthenticatorDescription> { private static final String GOOGLE_ACCOUNT_TYPE = "com.google"; private static final String NO_BROADCAST_FLAG = "nobroadcast"; @@ -220,34 +220,29 @@ public class AccountManagerService mMessageHandler = new MessageHandler(mMessageThread.getLooper()); mAuthenticatorCache = new AccountAuthenticatorCache(mContext); - mAuthenticatorCache.setListener(this); + mAuthenticatorCache.setListener(this, null /* Handler */); mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler, MESSAGE_CONNECTED, MESSAGE_DISCONNECTED); mSimWatcher = new SimWatcher(mContext); sThis.set(this); - - onRegisteredServicesCacheChanged(); } - public void onRegisteredServicesCacheChanged() { + public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { boolean accountDeleted = false; SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, - null, null, null, null, null); + ACCOUNTS_TYPE + "=?", new String[]{desc.type}, null, null, null); try { while (cursor.moveToNext()) { final long accountId = cursor.getLong(0); final String accountType = cursor.getString(1); final String accountName = cursor.getString(2); - if (mAuthenticatorCache.getServiceInfo(AuthenticatorDescription.newKey(accountType)) - == null) { - Log.d(TAG, "deleting account " + accountName + " because type " - + accountType + " no longer has a registered authenticator"); - db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); - accountDeleted= true; - } + Log.d(TAG, "deleting account " + accountName + " because type " + + accountType + " no longer has a registered authenticator"); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); + accountDeleted = true; } } finally { cursor.close(); diff --git a/core/java/android/accounts/AuthenticatorDescription.java b/core/java/android/accounts/AuthenticatorDescription.java index e642700..91c94e6 100644 --- a/core/java/android/accounts/AuthenticatorDescription.java +++ b/core/java/android/accounts/AuthenticatorDescription.java @@ -87,6 +87,10 @@ public class AuthenticatorDescription implements Parcelable { return type.equals(other.type); } + public String toString() { + return "AuthenticatorDescription {type=" + type + "}"; + } + /** @inhericDoc */ public void writeToParcel(Parcel dest, int flags) { dest.writeString(type); diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java index eba8715..fbe3548 100644 --- a/core/java/android/content/AbstractSyncableContentProvider.java +++ b/core/java/android/content/AbstractSyncableContentProvider.java @@ -135,6 +135,8 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro public void onCreate(SQLiteDatabase db) { bootstrapDatabase(db); mSyncState.createDatabase(db); + ContentResolver.requestSync(null /* all accounts */, + mContentUri.getAuthority(), new Bundle()); } @Override diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java index 7d9f1de..6ade837 100644 --- a/core/java/android/content/SyncAdaptersCache.java +++ b/core/java/android/content/SyncAdaptersCache.java @@ -17,9 +17,14 @@ package android.content; import android.content.pm.RegisteredServicesCache; +import android.content.pm.XmlSerializerAndParser; import android.content.res.TypedArray; -import android.content.Context; import android.util.AttributeSet; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; /** * A cache of services that export the {@link android.content.ISyncAdapter} interface. @@ -31,9 +36,10 @@ import android.util.AttributeSet; 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"; + private static final MySerializer sSerializer = new MySerializer(); SyncAdaptersCache(Context context) { - super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME); + super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer); } public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) { @@ -57,4 +63,18 @@ import android.util.AttributeSet; sa.recycle(); } } + + static class MySerializer implements XmlSerializerAndParser<SyncAdapterType> { + public void writeAsXml(SyncAdapterType item, XmlSerializer out) throws IOException { + out.attribute(null, "authority", item.authority); + out.attribute(null, "accountType", item.accountType); + } + + public SyncAdapterType createFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + final String authority = parser.getAttributeValue(null, "authority"); + final String accountType = parser.getAttributeValue(null, "accountType"); + return SyncAdapterType.newKey(authority, accountType); + } + } }
\ No newline at end of file diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 538373f..b39a67d 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ComponentName; import android.content.res.XmlResourceParser; +import android.os.Environment; +import android.os.Handler; import android.util.Log; import android.util.AttributeSet; import android.util.Xml; @@ -29,14 +31,26 @@ import android.util.Xml; import java.util.Map; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicReference; +import java.io.File; +import java.io.FileOutputStream; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.IOException; +import java.io.FileInputStream; + +import com.android.internal.os.AtomicFile; +import com.android.internal.util.FastXmlSerializer; import com.google.android.collect.Maps; +import com.google.android.collect.Lists; + import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; /** * A cache of registered services. This cache @@ -52,75 +66,104 @@ public abstract class RegisteredServicesCache<V> { private final String mInterfaceName; private final String mMetaDataName; private final String mAttributesName; + private final XmlSerializerAndParser<V> mSerializerAndParser; + private final AtomicReference<BroadcastReceiver> mReceiver; - public RegisteredServicesCacheListener getListener() { - return mListener; - } - - public void setListener(RegisteredServicesCacheListener listener) { - mListener = listener; - } - - private volatile RegisteredServicesCacheListener mListener; + private final Object mServicesLock = new Object(); + // synchronized on mServicesLock + private HashMap<V, Integer> mPersistentServices; + // synchronized on mServicesLock + private Map<V, ServiceInfo<V>> mServices; + // synchronized on mServicesLock + private boolean mPersistentServicesFileDidNotExist; - // no need to be synchronized since the map is never changed once mService is written - volatile Map<V, ServiceInfo<V>> mServices; + /** + * This file contains the list of known services. We would like to maintain this forever + * so we store it as an XML file. + */ + private final AtomicFile mPersistentServicesFile; - // synchronized on "this" - private BroadcastReceiver mReceiver = null; + // the listener and handler are synchronized on "this" and must be updated together + private RegisteredServicesCacheListener<V> mListener; + private Handler mHandler; public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, - String attributeName) { + String attributeName, XmlSerializerAndParser<V> serializerAndParser) { mContext = context; mInterfaceName = interfaceName; mMetaDataName = metaDataName; mAttributesName = attributeName; + mSerializerAndParser = serializerAndParser; + + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + File syncDir = new File(systemDir, "registered_services"); + mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml")); + + generateServicesMap(); + + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context1, Intent intent) { + generateServicesMap(); + } + }; + mReceiver = new AtomicReference<BroadcastReceiver>(receiver); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiver(receiver, intentFilter); } public void dump(FileDescriptor fd, PrintWriter fout, String[] args) { - getAllServices(); - Map<V, ServiceInfo<V>> services = mServices; + Map<V, ServiceInfo<V>> services; + synchronized (mServicesLock) { + services = mServices; + } fout.println("RegisteredServicesCache: " + services.size() + " services"); for (ServiceInfo info : services.values()) { fout.println(" " + info); } } - private boolean maybeRegisterForPackageChanges() { + public RegisteredServicesCacheListener<V> getListener() { synchronized (this) { - if (mReceiver == null) { - synchronized (this) { - mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mServices = generateServicesMap(); - RegisteredServicesCacheListener listener = mListener; - if (listener != null) { - listener.onRegisteredServicesCacheChanged(); - } - } - }; - } + return mListener; + } + } - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mContext.registerReceiver(mReceiver, intentFilter); - return true; - } - return false; + public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) { + if (handler == null) { + handler = new Handler(mContext.getMainLooper()); + } + synchronized (this) { + mHandler = handler; + mListener = listener; } } - private void maybeUnregisterForPackageChanges() { + private void notifyListener(final V type, final boolean removed) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added")); + } + RegisteredServicesCacheListener<V> listener; + Handler handler; synchronized (this) { - if (mReceiver != null) { - mContext.unregisterReceiver(mReceiver); - mReceiver = null; - } + listener = mListener; + handler = mHandler; + } + if (listener == null) { + return; } + + final RegisteredServicesCacheListener<V> listener2 = listener; + handler.post(new Runnable() { + public void run() { + listener2.onServiceChanged(type, removed); + } + }); } /** @@ -140,7 +183,7 @@ public abstract class RegisteredServicesCache<V> { @Override public String toString() { - return "ServiceInfo: " + type + ", " + componentName; + return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid; } } @@ -150,11 +193,9 @@ public abstract class RegisteredServicesCache<V> { * @return the AuthenticatorInfo that matches the account type or null if none is present */ public ServiceInfo<V> getServiceInfo(V type) { - if (mServices == null) { - maybeRegisterForPackageChanges(); - mServices = generateServicesMap(); + synchronized (mServicesLock) { + return mServices.get(type); } - return mServices.get(type); } /** @@ -162,54 +203,171 @@ public abstract class RegisteredServicesCache<V> { * registered authenticators. */ public Collection<ServiceInfo<V>> getAllServices() { - if (mServices == null) { - maybeRegisterForPackageChanges(); - mServices = generateServicesMap(); + synchronized (mServicesLock) { + return Collections.unmodifiableCollection(mServices.values()); } - return Collections.unmodifiableCollection(mServices.values()); } /** * Stops the monitoring of package additions, removals and changes. */ public void close() { - maybeUnregisterForPackageChanges(); + final BroadcastReceiver receiver = mReceiver.getAndSet(null); + if (receiver != null) { + mContext.unregisterReceiver(receiver); + } } @Override protected void finalize() throws Throwable { - synchronized (this) { - if (mReceiver != null) { - Log.e(TAG, "RegisteredServicesCache finalized without being closed"); - } + if (mReceiver.get() != null) { + Log.e(TAG, "RegisteredServicesCache finalized without being closed"); } close(); super.finalize(); } - 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); + private boolean inSystemImage(int callerUid) { + String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); + for (String name : packages) { + try { + PackageInfo packageInfo = + mContext.getPackageManager().getPackageInfo(name, 0 /* flags */); + if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + return false; + } + void generateServicesMap() { + PackageManager pm = mContext.getPackageManager(); + ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>(); + 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()); + if (info == null) { + Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); + continue; } + serviceInfos.add(info); } catch (XmlPullParserException e) { - Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); + Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); } catch (IOException e) { - Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); + Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); } } - return services; + synchronized (mServicesLock) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "generateServicesMap: " + mInterfaceName); + } + if (mPersistentServices == null) { + readPersistentServicesLocked(); + } + mServices = Maps.newHashMap(); + boolean changed = false; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "found " + serviceInfos.size() + " services"); + } + for (ServiceInfo<V> info : serviceInfos) { + // four cases: + // - doesn't exist yet + // - add, notify user that it was added + // - exists and the UID is the same + // - replace, don't notify user + // - exists, the UID is different, and the new one is not a system package + // - ignore + // - exists, the UID is different, and the new one is a system package + // - add, notify user that it was added + Integer previousUid = mPersistentServices.get(info.type); + if (previousUid == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "encountered new type: " + info); + } + changed = true; + mServices.put(info.type, info); + mPersistentServices.put(info.type, info.uid); + if (!mPersistentServicesFileDidNotExist) { + notifyListener(info.type, false /* removed */); + } + } else if (previousUid == info.uid) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "encountered existing type with the same uid: " + info); + } + mServices.put(info.type, info); + } else if (inSystemImage(info.uid) + || !containsTypeAndUid(serviceInfos, info.type, previousUid)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + if (inSystemImage(info.uid)) { + Log.d(TAG, "encountered existing type with a new uid but from" + + " the system: " + info); + } else { + Log.d(TAG, "encountered existing type with a new uid but existing was" + + " removed: " + info); + } + } + changed = true; + mServices.put(info.type, info); + mPersistentServices.put(info.type, info.uid); + notifyListener(info.type, false /* removed */); + } else { + // ignore + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "encountered existing type with a new uid, ignoring: " + info); + } + } + } + + ArrayList<V> toBeRemoved = Lists.newArrayList(); + for (V v1 : mPersistentServices.keySet()) { + if (!containsType(serviceInfos, v1)) { + toBeRemoved.add(v1); + } + } + for (V v1 : toBeRemoved) { + mPersistentServices.remove(v1); + changed = true; + notifyListener(v1, true /* removed */); + } + if (changed) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "writing updated list of persistent services"); + } + writePersistentServicesLocked(); + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "persistent services did not change, so not writing anything"); + } + } + mPersistentServicesFileDidNotExist = false; + } + } + + private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) { + for (int i = 0, N = serviceInfos.size(); i < N; i++) { + if (serviceInfos.get(i).type.equals(type)) { + return true; + } + } + + return false; + } + + private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) { + for (int i = 0, N = serviceInfos.size(); i < N; i++) { + final ServiceInfo<V> serviceInfo = serviceInfos.get(i); + if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) { + return true; + } + } + + return false; } private ServiceInfo<V> parseServiceInfo(ResolveInfo service) @@ -252,5 +410,89 @@ public abstract class RegisteredServicesCache<V> { } } + /** + * Read all sync status back in to the initial engine state. + */ + private void readPersistentServicesLocked() { + mPersistentServices = Maps.newHashMap(); + if (mSerializerAndParser == null) { + return; + } + FileInputStream fis = null; + try { + mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists(); + if (mPersistentServicesFileDidNotExist) { + return; + } + fis = mPersistentServicesFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG) { + eventType = parser.next(); + } + String tagName = parser.getName(); + if ("services".equals(tagName)) { + eventType = parser.next(); + do { + if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) { + tagName = parser.getName(); + if ("service".equals(tagName)) { + V service = mSerializerAndParser.createFromXml(parser); + if (service == null) { + break; + } + String uidString = parser.getAttributeValue(null, "uid"); + int uid = Integer.parseInt(uidString); + mPersistentServices.put(service, uid); + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + } catch (Exception e) { + Log.w(TAG, "Error reading persistent services, starting from scratch", e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) { + } + } + } + } + + /** + * Write all sync status to the sync status file. + */ + private void writePersistentServicesLocked() { + if (mSerializerAndParser == null) { + return; + } + FileOutputStream fos = null; + try { + fos = mPersistentServicesFile.startWrite(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + out.startTag(null, "services"); + for (Map.Entry<V, Integer> service : mPersistentServices.entrySet()) { + out.startTag(null, "service"); + out.attribute(null, "uid", Integer.toString(service.getValue())); + mSerializerAndParser.writeAsXml(service.getKey(), out); + out.endTag(null, "service"); + } + out.endTag(null, "services"); + out.endDocument(); + mPersistentServicesFile.finishWrite(fos); + } catch (java.io.IOException e1) { + Log.w(TAG, "Error writing accounts", e1); + if (fos != null) { + mPersistentServicesFile.failWrite(fos); + } + } + } + public abstract V parseServiceAttributes(String packageName, AttributeSet attrs); } diff --git a/core/java/android/content/pm/RegisteredServicesCacheListener.java b/core/java/android/content/pm/RegisteredServicesCacheListener.java index c92c86e..2bc0942 100644 --- a/core/java/android/content/pm/RegisteredServicesCacheListener.java +++ b/core/java/android/content/pm/RegisteredServicesCacheListener.java @@ -1,12 +1,16 @@ package android.content.pm; +import android.os.Parcelable; + /** * Listener for changes to the set of registered services managed by a RegisteredServicesCache. * @hide */ -public interface RegisteredServicesCacheListener { +public interface RegisteredServicesCacheListener<V> { /** - * Invoked when the registered services cache changes. + * Invoked when a service is registered or changed. + * @param type the type of registered service + * @param removed true if the service was removed */ - void onRegisteredServicesCacheChanged(); + void onServiceChanged(V type, boolean removed); } diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java new file mode 100644 index 0000000..33598f0 --- /dev/null +++ b/core/java/android/content/pm/XmlSerializerAndParser.java @@ -0,0 +1,14 @@ +package android.content.pm; + +import org.xmlpull.v1.XmlSerializer; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import android.os.Parcel; + +import java.io.IOException; + +/** @hide */ +public interface XmlSerializerAndParser<T> { + void writeAsXml(T item, XmlSerializer out) throws IOException; + T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException; +} |