2 * Copyright (C) 2009 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.content.pm;
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.content.res.Resources;
26 import android.content.res.XmlResourceParser;
27 import android.os.Environment;
28 import android.os.Handler;
29 import android.os.UserHandle;
30 import android.util.AtomicFile;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.util.Slog;
34 import android.util.SparseArray;
35 import android.util.Xml;
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.util.FastXmlSerializer;
39 import com.google.android.collect.Lists;
40 import com.google.android.collect.Maps;
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 import org.xmlpull.v1.XmlSerializer;
47 import java.io.FileDescriptor;
48 import java.io.FileInputStream;
49 import java.io.FileOutputStream;
50 import java.io.IOException;
51 import java.io.PrintWriter;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.List;
59 * Cache of registered services. This cache is lazily built by interrogating
60 * {@link PackageManager} on a per-user basis. It's updated as packages are
61 * added, removed and changed. Users are responsible for calling
62 * {@link #invalidateCache(int)} when a user is started, since
63 * {@link PackageManager} broadcasts aren't sent for stopped users.
65 * The services are referred to by type V and are made available via the
66 * {@link #getServiceInfo} method.
70 public abstract class RegisteredServicesCache<V> {
71 private static final String TAG = "PackageManager";
72 private static final boolean DEBUG = false;
74 public final Context mContext;
75 private final String mInterfaceName;
76 private final String mMetaDataName;
77 private final String mAttributesName;
78 private final XmlSerializerAndParser<V> mSerializerAndParser;
80 private final Object mServicesLock = new Object();
82 @GuardedBy("mServicesLock")
83 private boolean mPersistentServicesFileDidNotExist;
84 @GuardedBy("mServicesLock")
85 private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
87 private static class UserServices<V> {
88 @GuardedBy("mServicesLock")
89 public final Map<V, Integer> persistentServices = Maps.newHashMap();
90 @GuardedBy("mServicesLock")
91 public Map<V, ServiceInfo<V>> services = null;
94 private UserServices<V> findOrCreateUserLocked(int userId) {
95 UserServices<V> services = mUserServices.get(userId);
96 if (services == null) {
97 services = new UserServices<V>();
98 mUserServices.put(userId, services);
104 * This file contains the list of known services. We would like to maintain this forever
105 * so we store it as an XML file.
107 private final AtomicFile mPersistentServicesFile;
109 // the listener and handler are synchronized on "this" and must be updated together
110 private RegisteredServicesCacheListener<V> mListener;
111 private Handler mHandler;
113 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
114 String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
116 mInterfaceName = interfaceName;
117 mMetaDataName = metaDataName;
118 mAttributesName = attributeName;
119 mSerializerAndParser = serializerAndParser;
121 File dataDir = Environment.getDataDirectory();
122 File systemDir = new File(dataDir, "system");
123 File syncDir = new File(systemDir, "registered_services");
124 mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
126 // Load persisted services from disk
127 readPersistentServicesLocked();
129 IntentFilter intentFilter = new IntentFilter();
130 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
131 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
132 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
133 intentFilter.addDataScheme("package");
134 mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null);
136 // Register for events related to sdcard installation.
137 IntentFilter sdFilter = new IntentFilter();
138 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
139 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
140 mContext.registerReceiver(mExternalReceiver, sdFilter);
143 private final void handlePackageEvent(Intent intent, int userId) {
144 // Don't regenerate the services map when the package is removed or its
145 // ASEC container unmounted as a step in replacement. The subsequent
146 // _ADDED / _AVAILABLE call will regenerate the map in the final state.
147 final String action = intent.getAction();
148 // it's a new-component action if it isn't some sort of removal
149 final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action)
150 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action);
151 // if it's a removal, is it part of an update-in-place step?
152 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
154 if (isRemoval && replacing) {
155 // package is going away, but it's the middle of an upgrade: keep the current
156 // state and do nothing here. This clause is intentionally empty.
158 // either we're adding/changing, or it's a removal without replacement, so
159 // we need to recalculate the set of available services
160 generateServicesMap(userId);
164 private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
166 public void onReceive(Context context, Intent intent) {
167 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
169 handlePackageEvent(intent, UserHandle.getUserId(uid));
174 private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() {
176 public void onReceive(Context context, Intent intent) {
177 // External apps can't coexist with multi-user, so scan owner
178 handlePackageEvent(intent, UserHandle.USER_OWNER);
182 public void invalidateCache(int userId) {
183 synchronized (mServicesLock) {
184 final UserServices<V> user = findOrCreateUserLocked(userId);
185 user.services = null;
189 public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) {
190 synchronized (mServicesLock) {
191 final UserServices<V> user = findOrCreateUserLocked(userId);
192 if (user.services != null) {
193 fout.println("RegisteredServicesCache: " + user.services.size() + " services");
194 for (ServiceInfo<?> info : user.services.values()) {
195 fout.println(" " + info);
198 fout.println("RegisteredServicesCache: services not loaded");
203 public RegisteredServicesCacheListener<V> getListener() {
204 synchronized (this) {
209 public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
210 if (handler == null) {
211 handler = new Handler(mContext.getMainLooper());
213 synchronized (this) {
215 mListener = listener;
219 private void notifyListener(final V type, final int userId, final boolean removed) {
221 Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
223 RegisteredServicesCacheListener<V> listener;
225 synchronized (this) {
226 listener = mListener;
229 if (listener == null) {
233 final RegisteredServicesCacheListener<V> listener2 = listener;
234 handler.post(new Runnable() {
236 listener2.onServiceChanged(type, userId, removed);
242 * Value type that describes a Service. The information within can be used
243 * to bind to the service.
245 public static class ServiceInfo<V> {
247 public final ComponentName componentName;
248 public final int uid;
251 public ServiceInfo(V type, ComponentName componentName, int uid) {
253 this.componentName = componentName;
258 public String toString() {
259 return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
264 * Accessor for the registered authenticators.
265 * @param type the account type of the authenticator
266 * @return the AuthenticatorInfo that matches the account type or null if none is present
268 public ServiceInfo<V> getServiceInfo(V type, int userId) {
269 synchronized (mServicesLock) {
270 // Find user and lazily populate cache
271 final UserServices<V> user = findOrCreateUserLocked(userId);
272 if (user.services == null) {
273 generateServicesMap(userId);
275 return user.services.get(type);
280 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
281 * registered authenticators.
283 public Collection<ServiceInfo<V>> getAllServices(int userId) {
284 synchronized (mServicesLock) {
285 // Find user and lazily populate cache
286 final UserServices<V> user = findOrCreateUserLocked(userId);
287 if (user.services == null) {
288 generateServicesMap(userId);
290 return Collections.unmodifiableCollection(
291 new ArrayList<ServiceInfo<V>>(user.services.values()));
295 private boolean inSystemImage(int callerUid) {
296 String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
297 for (String name : packages) {
299 PackageInfo packageInfo =
300 mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
301 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
304 } catch (PackageManager.NameNotFoundException e) {
312 * Populate {@link UserServices#services} by scanning installed packages for
313 * given {@link UserHandle}.
315 private void generateServicesMap(int userId) {
317 Slog.d(TAG, "generateServicesMap() for " + userId);
320 final PackageManager pm = mContext.getPackageManager();
321 final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
322 final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(
323 new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
324 for (ResolveInfo resolveInfo : resolveInfos) {
326 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
328 Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
331 serviceInfos.add(info);
332 } catch (XmlPullParserException e) {
333 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
334 } catch (IOException e) {
335 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
339 synchronized (mServicesLock) {
340 final UserServices<V> user = findOrCreateUserLocked(userId);
341 final boolean firstScan = user.services == null;
343 user.services = Maps.newHashMap();
345 user.services.clear();
348 StringBuilder changes = new StringBuilder();
349 boolean changed = false;
350 for (ServiceInfo<V> info : serviceInfos) {
352 // - doesn't exist yet
353 // - add, notify user that it was added
354 // - exists and the UID is the same
355 // - replace, don't notify user
356 // - exists, the UID is different, and the new one is not a system package
358 // - exists, the UID is different, and the new one is a system package
359 // - add, notify user that it was added
360 Integer previousUid = user.persistentServices.get(info.type);
361 if (previousUid == null) {
363 changes.append(" New service added: ").append(info).append("\n");
366 user.services.put(info.type, info);
367 user.persistentServices.put(info.type, info.uid);
368 if (!(mPersistentServicesFileDidNotExist && firstScan)) {
369 notifyListener(info.type, userId, false /* removed */);
371 } else if (previousUid == info.uid) {
373 changes.append(" Existing service (nop): ").append(info).append("\n");
375 user.services.put(info.type, info);
376 } else if (inSystemImage(info.uid)
377 || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
379 if (inSystemImage(info.uid)) {
380 changes.append(" System service replacing existing: ").append(info)
383 changes.append(" Existing service replacing a removed service: ")
384 .append(info).append("\n");
388 user.services.put(info.type, info);
389 user.persistentServices.put(info.type, info.uid);
390 notifyListener(info.type, userId, false /* removed */);
394 changes.append(" Existing service with new uid ignored: ").append(info)
400 ArrayList<V> toBeRemoved = Lists.newArrayList();
401 for (V v1 : user.persistentServices.keySet()) {
402 if (!containsType(serviceInfos, v1)) {
406 for (V v1 : toBeRemoved) {
408 changes.append(" Service removed: ").append(v1).append("\n");
411 user.persistentServices.remove(v1);
412 notifyListener(v1, userId, true /* removed */);
415 if (changes.length() > 0) {
416 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
417 serviceInfos.size() + " services:\n" + changes);
419 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
420 serviceInfos.size() + " services unchanged");
424 writePersistentServicesLocked();
429 private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
430 for (int i = 0, N = serviceInfos.size(); i < N; i++) {
431 if (serviceInfos.get(i).type.equals(type)) {
439 private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
440 for (int i = 0, N = serviceInfos.size(); i < N; i++) {
441 final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
442 if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
450 private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
451 throws XmlPullParserException, IOException {
452 android.content.pm.ServiceInfo si = service.serviceInfo;
453 ComponentName componentName = new ComponentName(si.packageName, si.name);
455 PackageManager pm = mContext.getPackageManager();
457 XmlResourceParser parser = null;
459 parser = si.loadXmlMetaData(pm, mMetaDataName);
460 if (parser == null) {
461 throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
464 AttributeSet attrs = Xml.asAttributeSet(parser);
467 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
468 && type != XmlPullParser.START_TAG) {
471 String nodeName = parser.getName();
472 if (!mAttributesName.equals(nodeName)) {
473 throw new XmlPullParserException(
474 "Meta-data does not start with " + mAttributesName + " tag");
477 V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
478 si.packageName, attrs);
482 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
483 final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
484 final int uid = applicationInfo.uid;
485 return new ServiceInfo<V>(v, componentName, uid);
486 } catch (NameNotFoundException e) {
487 throw new XmlPullParserException(
488 "Unable to load resources for pacakge " + si.packageName);
490 if (parser != null) parser.close();
495 * Read all sync status back in to the initial engine state.
497 private void readPersistentServicesLocked() {
498 mUserServices.clear();
499 if (mSerializerAndParser == null) {
502 FileInputStream fis = null;
504 mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
505 if (mPersistentServicesFileDidNotExist) {
508 fis = mPersistentServicesFile.openRead();
509 XmlPullParser parser = Xml.newPullParser();
510 parser.setInput(fis, null);
511 int eventType = parser.getEventType();
512 while (eventType != XmlPullParser.START_TAG
513 && eventType != XmlPullParser.END_DOCUMENT) {
514 eventType = parser.next();
516 String tagName = parser.getName();
517 if ("services".equals(tagName)) {
518 eventType = parser.next();
520 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
521 tagName = parser.getName();
522 if ("service".equals(tagName)) {
523 V service = mSerializerAndParser.createFromXml(parser);
524 if (service == null) {
527 String uidString = parser.getAttributeValue(null, "uid");
528 final int uid = Integer.parseInt(uidString);
529 final int userId = UserHandle.getUserId(uid);
530 final UserServices<V> user = findOrCreateUserLocked(userId);
531 user.persistentServices.put(service, uid);
534 eventType = parser.next();
535 } while (eventType != XmlPullParser.END_DOCUMENT);
537 } catch (Exception e) {
538 Log.w(TAG, "Error reading persistent services, starting from scratch", e);
543 } catch (java.io.IOException e1) {
550 * Write all sync status to the sync status file.
552 private void writePersistentServicesLocked() {
553 if (mSerializerAndParser == null) {
556 FileOutputStream fos = null;
558 fos = mPersistentServicesFile.startWrite();
559 XmlSerializer out = new FastXmlSerializer();
560 out.setOutput(fos, "utf-8");
561 out.startDocument(null, true);
562 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
563 out.startTag(null, "services");
564 for (int i = 0; i < mUserServices.size(); i++) {
565 final UserServices<V> user = mUserServices.valueAt(i);
566 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
567 out.startTag(null, "service");
568 out.attribute(null, "uid", Integer.toString(service.getValue()));
569 mSerializerAndParser.writeAsXml(service.getKey(), out);
570 out.endTag(null, "service");
573 out.endTag(null, "services");
575 mPersistentServicesFile.finishWrite(fos);
576 } catch (java.io.IOException e1) {
577 Log.w(TAG, "Error writing accounts", e1);
579 mPersistentServicesFile.failWrite(fos);
584 public abstract V parseServiceAttributes(Resources res,
585 String packageName, AttributeSet attrs);