diff options
author | Fred Quintana <fredq@google.com> | 2009-11-09 15:42:20 -0800 |
---|---|---|
committer | Fred Quintana <fredq@google.com> | 2009-11-10 16:10:54 -0800 |
commit | 5ebbb4a6b3e16f711735ae0615b9a9ea64faad38 (patch) | |
tree | 57cd54aa0cdb48dcadc3cf236bf0947caf1a6f6e /core/java/android/content/pm/RegisteredServicesCache.java | |
parent | 50c548d242d637328ec6b2c4987969b02695cc7d (diff) | |
download | frameworks_base-5ebbb4a6b3e16f711735ae0615b9a9ea64faad38.zip frameworks_base-5ebbb4a6b3e16f711735ae0615b9a9ea64faad38.tar.gz frameworks_base-5ebbb4a6b3e16f711735ae0615b9a9ea64faad38.tar.bz2 |
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.
Notify the listener when a service is added or removed.
Make the AccountManagerService remove the accounts for an authenticator
when the registered authenticator changes from one uid to another.
Make the AbstractSyncableContentProvider force a sync when the database is first created.
Diffstat (limited to 'core/java/android/content/pm/RegisteredServicesCache.java')
-rw-r--r-- | core/java/android/content/pm/RegisteredServicesCache.java | 384 |
1 files changed, 313 insertions, 71 deletions
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); } |