RegisteredServicesCachepublic abstract class RegisteredServicesCache extends Object Cache of registered services. This cache is lazily built by interrogating
{@link PackageManager} on a per-user basis. It's updated as packages are
added, removed and changed. Users are responsible for calling
{@link #invalidateCache(int)} when a user is started, since
{@link PackageManager} broadcasts aren't sent for stopped users.
The services are referred to by type V and are made available via the
{@link #getServiceInfo} method. |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DEBUG | public final android.content.Context | mContext | private final String | mInterfaceName | private final String | mMetaDataName | private final String | mAttributesName | private final XmlSerializerAndParser | mSerializerAndParser | private final Object | mServicesLock | private boolean | mPersistentServicesFileDidNotExist | private final android.util.SparseArray | mUserServices | private final android.util.AtomicFile | mPersistentServicesFileThis file contains the list of known services. We would like to maintain this forever
so we store it as an XML file. | private RegisteredServicesCacheListener | mListener | private android.os.Handler | mHandler | private final android.content.BroadcastReceiver | mPackageReceiver | private final android.content.BroadcastReceiver | mExternalReceiver |
Constructors Summary |
---|
public RegisteredServicesCache(android.content.Context context, String interfaceName, String metaDataName, String attributeName, XmlSerializerAndParser 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"));
// Load persisted services from disk
readPersistentServicesLocked();
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.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mExternalReceiver, sdFilter);
|
Methods Summary |
---|
private boolean | containsType(java.util.ArrayList 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(java.util.ArrayList 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 boolean | containsUid(int[] changedUids, int uid)Returns true if the list of changed uids is null (wildcard) or the specified uid
is contained in the list of changed uids.
return changedUids == null || ArrayUtils.contains(changedUids, uid);
| public void | dump(java.io.FileDescriptor fd, java.io.PrintWriter fout, java.lang.String[] args, int userId)
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services != null) {
fout.println("RegisteredServicesCache: " + user.services.size() + " services");
for (ServiceInfo<?> info : user.services.values()) {
fout.println(" " + info);
}
} else {
fout.println("RegisteredServicesCache: services not loaded");
}
}
| private android.content.pm.RegisteredServicesCache$UserServices | findOrCreateUserLocked(int userId)
UserServices<V> services = mUserServices.get(userId);
if (services == null) {
services = new UserServices<V>();
mUserServices.put(userId, services);
}
return services;
| private void | generateServicesMap(int[] changedUids, int userId)Populate {@link UserServices#services} by scanning installed packages for
given {@link UserHandle}.
if (DEBUG) {
Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids);
}
final PackageManager pm = mContext.getPackageManager();
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(
new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
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 service info " + resolveInfo.toString(), e);
} catch (IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
final boolean firstScan = user.services == null;
if (firstScan) {
user.services = Maps.newHashMap();
}
StringBuilder changes = new StringBuilder();
boolean changed = false;
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 = user.persistentServices.get(info.type);
if (previousUid == null) {
if (DEBUG) {
changes.append(" New service added: ").append(info).append("\n");
}
changed = true;
user.services.put(info.type, info);
user.persistentServices.put(info.type, info.uid);
if (!(mPersistentServicesFileDidNotExist && firstScan)) {
notifyListener(info.type, userId, false /* removed */);
}
} else if (previousUid == info.uid) {
if (DEBUG) {
changes.append(" Existing service (nop): ").append(info).append("\n");
}
user.services.put(info.type, info);
} else if (inSystemImage(info.uid)
|| !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
if (DEBUG) {
if (inSystemImage(info.uid)) {
changes.append(" System service replacing existing: ").append(info)
.append("\n");
} else {
changes.append(" Existing service replacing a removed service: ")
.append(info).append("\n");
}
}
changed = true;
user.services.put(info.type, info);
user.persistentServices.put(info.type, info.uid);
notifyListener(info.type, userId, false /* removed */);
} else {
// ignore
if (DEBUG) {
changes.append(" Existing service with new uid ignored: ").append(info)
.append("\n");
}
}
}
ArrayList<V> toBeRemoved = Lists.newArrayList();
for (V v1 : user.persistentServices.keySet()) {
// Remove a persisted service that's not in the currently available services list.
// And only if it is in the list of changedUids.
if (!containsType(serviceInfos, v1)
&& containsUid(changedUids, user.persistentServices.get(v1))) {
toBeRemoved.add(v1);
}
}
for (V v1 : toBeRemoved) {
if (DEBUG) {
changes.append(" Service removed: ").append(v1).append("\n");
}
changed = true;
user.persistentServices.remove(v1);
user.services.remove(v1);
notifyListener(v1, userId, true /* removed */);
}
if (DEBUG) {
Log.d(TAG, "user.services=");
for (V v : user.services.keySet()) {
Log.d(TAG, " " + v + " " + user.services.get(v));
}
Log.d(TAG, "user.persistentServices=");
for (V v : user.persistentServices.keySet()) {
Log.d(TAG, " " + v + " " + user.persistentServices.get(v));
}
}
if (DEBUG) {
if (changes.length() > 0) {
Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
serviceInfos.size() + " services:\n" + changes);
} else {
Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
serviceInfos.size() + " services unchanged");
}
}
if (changed) {
writePersistentServicesLocked();
}
}
| public java.util.Collection | getAllServices(int userId)
synchronized (mServicesLock) {
// Find user and lazily populate cache
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services == null) {
generateServicesMap(null, userId);
}
return Collections.unmodifiableCollection(
new ArrayList<ServiceInfo<V>>(user.services.values()));
}
| public RegisteredServicesCacheListener | getListener()
synchronized (this) {
return mListener;
}
| public android.content.pm.RegisteredServicesCache$ServiceInfo | getServiceInfo(V type, int userId)Accessor for the registered authenticators.
synchronized (mServicesLock) {
// Find user and lazily populate cache
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services == null) {
generateServicesMap(null, userId);
}
return user.services.get(type);
}
| private final void | handlePackageEvent(android.content.Intent intent, int userId)
// Don't regenerate the services map when the package is removed or its
// ASEC container unmounted as a step in replacement. The subsequent
// _ADDED / _AVAILABLE call will regenerate the map in the final state.
final String action = intent.getAction();
// it's a new-component action if it isn't some sort of removal
final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action);
// if it's a removal, is it part of an update-in-place step?
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (isRemoval && replacing) {
// package is going away, but it's the middle of an upgrade: keep the current
// state and do nothing here. This clause is intentionally empty.
} else {
int[] uids = null;
// either we're adding/changing, or it's a removal without replacement, so
// we need to update the set of available services
if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)
|| Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
} else {
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (uid > 0) {
uids = new int[] { uid };
}
}
generateServicesMap(uids, userId);
}
| 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;
| public void | invalidateCache(int userId)
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
user.services = null;
}
| private void | notifyListener(V type, int userId, boolean removed)
if (DEBUG) {
Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
}
RegisteredServicesCacheListener<V> listener;
Handler handler;
synchronized (this) {
listener = mListener;
handler = mHandler;
}
if (listener == null) {
return;
}
final RegisteredServicesCacheListener<V> listener2 = listener;
handler.post(new Runnable() {
public void run() {
listener2.onServiceChanged(type, userId, removed);
}
});
| public abstract V | parseServiceAttributes(android.content.res.Resources res, java.lang.String packageName, android.util.AttributeSet attrs)
| private android.content.pm.RegisteredServicesCache$ServiceInfo | parseServiceInfo(ResolveInfo service)
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(pm.getResourcesForApplication(si.applicationInfo),
si.packageName, attrs);
if (v == null) {
return null;
}
final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
final int uid = applicationInfo.uid;
return new ServiceInfo<V>(v, componentName, uid);
} catch (NameNotFoundException e) {
throw new XmlPullParserException(
"Unable to load resources for pacakge " + si.packageName);
} finally {
if (parser != null) parser.close();
}
| private void | readPersistentServicesLocked()Read all sync status back in to the initial engine state.
mUserServices.clear();
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 != XmlPullParser.END_DOCUMENT) {
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");
final int uid = Integer.parseInt(uidString);
final int userId = UserHandle.getUserId(uid);
final UserServices<V> user = findOrCreateUserLocked(userId);
user.persistentServices.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) {
}
}
}
| public void | setListener(RegisteredServicesCacheListener listener, android.os.Handler handler)
if (handler == null) {
handler = new Handler(mContext.getMainLooper());
}
synchronized (this) {
mHandler = handler;
mListener = listener;
}
| private void | writePersistentServicesLocked()Write all sync status to the sync status file.
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 (int i = 0; i < mUserServices.size(); i++) {
final UserServices<V> user = mUserServices.valueAt(i);
for (Map.Entry<V, Integer> service : user.persistentServices.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);
}
}
|
|