1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17package android.content.pm; 18 19import android.content.BroadcastReceiver; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.pm.PackageManager.NameNotFoundException; 25import android.content.res.Resources; 26import android.content.res.XmlResourceParser; 27import android.os.Environment; 28import android.os.Handler; 29import android.os.UserHandle; 30import android.util.AtomicFile; 31import android.util.AttributeSet; 32import android.util.Log; 33import android.util.Slog; 34import android.util.SparseArray; 35import android.util.Xml; 36 37import com.android.internal.annotations.GuardedBy; 38import com.android.internal.util.ArrayUtils; 39import com.android.internal.util.FastXmlSerializer; 40import com.google.android.collect.Lists; 41import com.google.android.collect.Maps; 42 43import org.xmlpull.v1.XmlPullParser; 44import org.xmlpull.v1.XmlPullParserException; 45import org.xmlpull.v1.XmlSerializer; 46 47import java.io.File; 48import java.io.FileDescriptor; 49import java.io.FileInputStream; 50import java.io.FileOutputStream; 51import java.io.IOException; 52import java.io.PrintWriter; 53import java.util.ArrayList; 54import java.util.Collection; 55import java.util.Collections; 56import java.util.List; 57import java.util.Map; 58 59/** 60 * Cache of registered services. This cache is lazily built by interrogating 61 * {@link PackageManager} on a per-user basis. It's updated as packages are 62 * added, removed and changed. Users are responsible for calling 63 * {@link #invalidateCache(int)} when a user is started, since 64 * {@link PackageManager} broadcasts aren't sent for stopped users. 65 * <p> 66 * The services are referred to by type V and are made available via the 67 * {@link #getServiceInfo} method. 68 * 69 * @hide 70 */ 71public abstract class RegisteredServicesCache<V> { 72 private static final String TAG = "PackageManager"; 73 private static final boolean DEBUG = false; 74 75 public final Context mContext; 76 private final String mInterfaceName; 77 private final String mMetaDataName; 78 private final String mAttributesName; 79 private final XmlSerializerAndParser<V> mSerializerAndParser; 80 81 private final Object mServicesLock = new Object(); 82 83 @GuardedBy("mServicesLock") 84 private boolean mPersistentServicesFileDidNotExist; 85 @GuardedBy("mServicesLock") 86 private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2); 87 88 private static class UserServices<V> { 89 @GuardedBy("mServicesLock") 90 public final Map<V, Integer> persistentServices = Maps.newHashMap(); 91 @GuardedBy("mServicesLock") 92 public Map<V, ServiceInfo<V>> services = null; 93 } 94 95 private UserServices<V> findOrCreateUserLocked(int userId) { 96 UserServices<V> services = mUserServices.get(userId); 97 if (services == null) { 98 services = new UserServices<V>(); 99 mUserServices.put(userId, services); 100 } 101 return services; 102 } 103 104 /** 105 * This file contains the list of known services. We would like to maintain this forever 106 * so we store it as an XML file. 107 */ 108 private final AtomicFile mPersistentServicesFile; 109 110 // the listener and handler are synchronized on "this" and must be updated together 111 private RegisteredServicesCacheListener<V> mListener; 112 private Handler mHandler; 113 114 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, 115 String attributeName, XmlSerializerAndParser<V> serializerAndParser) { 116 mContext = context; 117 mInterfaceName = interfaceName; 118 mMetaDataName = metaDataName; 119 mAttributesName = attributeName; 120 mSerializerAndParser = serializerAndParser; 121 122 File dataDir = Environment.getDataDirectory(); 123 File systemDir = new File(dataDir, "system"); 124 File syncDir = new File(systemDir, "registered_services"); 125 mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml")); 126 127 // Load persisted services from disk 128 readPersistentServicesLocked(); 129 130 IntentFilter intentFilter = new IntentFilter(); 131 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 132 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 133 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 134 intentFilter.addDataScheme("package"); 135 mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null); 136 137 // Register for events related to sdcard installation. 138 IntentFilter sdFilter = new IntentFilter(); 139 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 140 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 141 mContext.registerReceiver(mExternalReceiver, sdFilter); 142 } 143 144 private final void handlePackageEvent(Intent intent, int userId) { 145 // Don't regenerate the services map when the package is removed or its 146 // ASEC container unmounted as a step in replacement. The subsequent 147 // _ADDED / _AVAILABLE call will regenerate the map in the final state. 148 final String action = intent.getAction(); 149 // it's a new-component action if it isn't some sort of removal 150 final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action) 151 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action); 152 // if it's a removal, is it part of an update-in-place step? 153 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 154 155 if (isRemoval && replacing) { 156 // package is going away, but it's the middle of an upgrade: keep the current 157 // state and do nothing here. This clause is intentionally empty. 158 } else { 159 int[] uids = null; 160 // either we're adding/changing, or it's a removal without replacement, so 161 // we need to update the set of available services 162 if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) 163 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 164 uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); 165 } else { 166 int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 167 if (uid > 0) { 168 uids = new int[] { uid }; 169 } 170 } 171 generateServicesMap(uids, userId); 172 } 173 } 174 175 private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { 176 @Override 177 public void onReceive(Context context, Intent intent) { 178 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 179 if (uid != -1) { 180 handlePackageEvent(intent, UserHandle.getUserId(uid)); 181 } 182 } 183 }; 184 185 private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() { 186 @Override 187 public void onReceive(Context context, Intent intent) { 188 // External apps can't coexist with multi-user, so scan owner 189 handlePackageEvent(intent, UserHandle.USER_OWNER); 190 } 191 }; 192 193 public void invalidateCache(int userId) { 194 synchronized (mServicesLock) { 195 final UserServices<V> user = findOrCreateUserLocked(userId); 196 user.services = null; 197 } 198 } 199 200 public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) { 201 synchronized (mServicesLock) { 202 final UserServices<V> user = findOrCreateUserLocked(userId); 203 if (user.services != null) { 204 fout.println("RegisteredServicesCache: " + user.services.size() + " services"); 205 for (ServiceInfo<?> info : user.services.values()) { 206 fout.println(" " + info); 207 } 208 } else { 209 fout.println("RegisteredServicesCache: services not loaded"); 210 } 211 } 212 } 213 214 public RegisteredServicesCacheListener<V> getListener() { 215 synchronized (this) { 216 return mListener; 217 } 218 } 219 220 public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) { 221 if (handler == null) { 222 handler = new Handler(mContext.getMainLooper()); 223 } 224 synchronized (this) { 225 mHandler = handler; 226 mListener = listener; 227 } 228 } 229 230 private void notifyListener(final V type, final int userId, final boolean removed) { 231 if (DEBUG) { 232 Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added")); 233 } 234 RegisteredServicesCacheListener<V> listener; 235 Handler handler; 236 synchronized (this) { 237 listener = mListener; 238 handler = mHandler; 239 } 240 if (listener == null) { 241 return; 242 } 243 244 final RegisteredServicesCacheListener<V> listener2 = listener; 245 handler.post(new Runnable() { 246 public void run() { 247 listener2.onServiceChanged(type, userId, removed); 248 } 249 }); 250 } 251 252 /** 253 * Value type that describes a Service. The information within can be used 254 * to bind to the service. 255 */ 256 public static class ServiceInfo<V> { 257 public final V type; 258 public final ComponentName componentName; 259 public final int uid; 260 261 /** @hide */ 262 public ServiceInfo(V type, ComponentName componentName, int uid) { 263 this.type = type; 264 this.componentName = componentName; 265 this.uid = uid; 266 } 267 268 @Override 269 public String toString() { 270 return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid; 271 } 272 } 273 274 /** 275 * Accessor for the registered authenticators. 276 * @param type the account type of the authenticator 277 * @return the AuthenticatorInfo that matches the account type or null if none is present 278 */ 279 public ServiceInfo<V> getServiceInfo(V type, int userId) { 280 synchronized (mServicesLock) { 281 // Find user and lazily populate cache 282 final UserServices<V> user = findOrCreateUserLocked(userId); 283 if (user.services == null) { 284 generateServicesMap(null, userId); 285 } 286 return user.services.get(type); 287 } 288 } 289 290 /** 291 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all 292 * registered authenticators. 293 */ 294 public Collection<ServiceInfo<V>> getAllServices(int userId) { 295 synchronized (mServicesLock) { 296 // Find user and lazily populate cache 297 final UserServices<V> user = findOrCreateUserLocked(userId); 298 if (user.services == null) { 299 generateServicesMap(null, userId); 300 } 301 return Collections.unmodifiableCollection( 302 new ArrayList<ServiceInfo<V>>(user.services.values())); 303 } 304 } 305 306 private boolean inSystemImage(int callerUid) { 307 String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); 308 for (String name : packages) { 309 try { 310 PackageInfo packageInfo = 311 mContext.getPackageManager().getPackageInfo(name, 0 /* flags */); 312 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 313 return true; 314 } 315 } catch (PackageManager.NameNotFoundException e) { 316 return false; 317 } 318 } 319 return false; 320 } 321 322 /** 323 * Populate {@link UserServices#services} by scanning installed packages for 324 * given {@link UserHandle}. 325 * @param changedUids the array of uids that have been affected, as mentioned in the broadcast 326 * or null to assume that everything is affected. 327 * @param userId the user for whom to update the services map. 328 */ 329 private void generateServicesMap(int[] changedUids, int userId) { 330 if (DEBUG) { 331 Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids); 332 } 333 334 final PackageManager pm = mContext.getPackageManager(); 335 final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>(); 336 final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser( 337 new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId); 338 for (ResolveInfo resolveInfo : resolveInfos) { 339 try { 340 ServiceInfo<V> info = parseServiceInfo(resolveInfo); 341 if (info == null) { 342 Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); 343 continue; 344 } 345 serviceInfos.add(info); 346 } catch (XmlPullParserException e) { 347 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); 348 } catch (IOException e) { 349 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); 350 } 351 } 352 353 synchronized (mServicesLock) { 354 final UserServices<V> user = findOrCreateUserLocked(userId); 355 final boolean firstScan = user.services == null; 356 if (firstScan) { 357 user.services = Maps.newHashMap(); 358 } 359 360 StringBuilder changes = new StringBuilder(); 361 boolean changed = false; 362 for (ServiceInfo<V> info : serviceInfos) { 363 // four cases: 364 // - doesn't exist yet 365 // - add, notify user that it was added 366 // - exists and the UID is the same 367 // - replace, don't notify user 368 // - exists, the UID is different, and the new one is not a system package 369 // - ignore 370 // - exists, the UID is different, and the new one is a system package 371 // - add, notify user that it was added 372 Integer previousUid = user.persistentServices.get(info.type); 373 if (previousUid == null) { 374 if (DEBUG) { 375 changes.append(" New service added: ").append(info).append("\n"); 376 } 377 changed = true; 378 user.services.put(info.type, info); 379 user.persistentServices.put(info.type, info.uid); 380 if (!(mPersistentServicesFileDidNotExist && firstScan)) { 381 notifyListener(info.type, userId, false /* removed */); 382 } 383 } else if (previousUid == info.uid) { 384 if (DEBUG) { 385 changes.append(" Existing service (nop): ").append(info).append("\n"); 386 } 387 user.services.put(info.type, info); 388 } else if (inSystemImage(info.uid) 389 || !containsTypeAndUid(serviceInfos, info.type, previousUid)) { 390 if (DEBUG) { 391 if (inSystemImage(info.uid)) { 392 changes.append(" System service replacing existing: ").append(info) 393 .append("\n"); 394 } else { 395 changes.append(" Existing service replacing a removed service: ") 396 .append(info).append("\n"); 397 } 398 } 399 changed = true; 400 user.services.put(info.type, info); 401 user.persistentServices.put(info.type, info.uid); 402 notifyListener(info.type, userId, false /* removed */); 403 } else { 404 // ignore 405 if (DEBUG) { 406 changes.append(" Existing service with new uid ignored: ").append(info) 407 .append("\n"); 408 } 409 } 410 } 411 412 ArrayList<V> toBeRemoved = Lists.newArrayList(); 413 for (V v1 : user.persistentServices.keySet()) { 414 // Remove a persisted service that's not in the currently available services list. 415 // And only if it is in the list of changedUids. 416 if (!containsType(serviceInfos, v1) 417 && containsUid(changedUids, user.persistentServices.get(v1))) { 418 toBeRemoved.add(v1); 419 } 420 } 421 for (V v1 : toBeRemoved) { 422 if (DEBUG) { 423 changes.append(" Service removed: ").append(v1).append("\n"); 424 } 425 changed = true; 426 user.persistentServices.remove(v1); 427 user.services.remove(v1); 428 notifyListener(v1, userId, true /* removed */); 429 } 430 if (DEBUG) { 431 Log.d(TAG, "user.services="); 432 for (V v : user.services.keySet()) { 433 Log.d(TAG, " " + v + " " + user.services.get(v)); 434 } 435 Log.d(TAG, "user.persistentServices="); 436 for (V v : user.persistentServices.keySet()) { 437 Log.d(TAG, " " + v + " " + user.persistentServices.get(v)); 438 } 439 } 440 if (DEBUG) { 441 if (changes.length() > 0) { 442 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + 443 serviceInfos.size() + " services:\n" + changes); 444 } else { 445 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + 446 serviceInfos.size() + " services unchanged"); 447 } 448 } 449 if (changed) { 450 writePersistentServicesLocked(); 451 } 452 } 453 } 454 455 /** 456 * Returns true if the list of changed uids is null (wildcard) or the specified uid 457 * is contained in the list of changed uids. 458 */ 459 private boolean containsUid(int[] changedUids, int uid) { 460 return changedUids == null || ArrayUtils.contains(changedUids, uid); 461 } 462 463 private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) { 464 for (int i = 0, N = serviceInfos.size(); i < N; i++) { 465 if (serviceInfos.get(i).type.equals(type)) { 466 return true; 467 } 468 } 469 470 return false; 471 } 472 473 private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) { 474 for (int i = 0, N = serviceInfos.size(); i < N; i++) { 475 final ServiceInfo<V> serviceInfo = serviceInfos.get(i); 476 if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) { 477 return true; 478 } 479 } 480 481 return false; 482 } 483 484 private ServiceInfo<V> parseServiceInfo(ResolveInfo service) 485 throws XmlPullParserException, IOException { 486 android.content.pm.ServiceInfo si = service.serviceInfo; 487 ComponentName componentName = new ComponentName(si.packageName, si.name); 488 489 PackageManager pm = mContext.getPackageManager(); 490 491 XmlResourceParser parser = null; 492 try { 493 parser = si.loadXmlMetaData(pm, mMetaDataName); 494 if (parser == null) { 495 throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); 496 } 497 498 AttributeSet attrs = Xml.asAttributeSet(parser); 499 500 int type; 501 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 502 && type != XmlPullParser.START_TAG) { 503 } 504 505 String nodeName = parser.getName(); 506 if (!mAttributesName.equals(nodeName)) { 507 throw new XmlPullParserException( 508 "Meta-data does not start with " + mAttributesName + " tag"); 509 } 510 511 V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo), 512 si.packageName, attrs); 513 if (v == null) { 514 return null; 515 } 516 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo; 517 final ApplicationInfo applicationInfo = serviceInfo.applicationInfo; 518 final int uid = applicationInfo.uid; 519 return new ServiceInfo<V>(v, componentName, uid); 520 } catch (NameNotFoundException e) { 521 throw new XmlPullParserException( 522 "Unable to load resources for pacakge " + si.packageName); 523 } finally { 524 if (parser != null) parser.close(); 525 } 526 } 527 528 /** 529 * Read all sync status back in to the initial engine state. 530 */ 531 private void readPersistentServicesLocked() { 532 mUserServices.clear(); 533 if (mSerializerAndParser == null) { 534 return; 535 } 536 FileInputStream fis = null; 537 try { 538 mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists(); 539 if (mPersistentServicesFileDidNotExist) { 540 return; 541 } 542 fis = mPersistentServicesFile.openRead(); 543 XmlPullParser parser = Xml.newPullParser(); 544 parser.setInput(fis, null); 545 int eventType = parser.getEventType(); 546 while (eventType != XmlPullParser.START_TAG 547 && eventType != XmlPullParser.END_DOCUMENT) { 548 eventType = parser.next(); 549 } 550 String tagName = parser.getName(); 551 if ("services".equals(tagName)) { 552 eventType = parser.next(); 553 do { 554 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) { 555 tagName = parser.getName(); 556 if ("service".equals(tagName)) { 557 V service = mSerializerAndParser.createFromXml(parser); 558 if (service == null) { 559 break; 560 } 561 String uidString = parser.getAttributeValue(null, "uid"); 562 final int uid = Integer.parseInt(uidString); 563 final int userId = UserHandle.getUserId(uid); 564 final UserServices<V> user = findOrCreateUserLocked(userId); 565 user.persistentServices.put(service, uid); 566 } 567 } 568 eventType = parser.next(); 569 } while (eventType != XmlPullParser.END_DOCUMENT); 570 } 571 } catch (Exception e) { 572 Log.w(TAG, "Error reading persistent services, starting from scratch", e); 573 } finally { 574 if (fis != null) { 575 try { 576 fis.close(); 577 } catch (java.io.IOException e1) { 578 } 579 } 580 } 581 } 582 583 /** 584 * Write all sync status to the sync status file. 585 */ 586 private void writePersistentServicesLocked() { 587 if (mSerializerAndParser == null) { 588 return; 589 } 590 FileOutputStream fos = null; 591 try { 592 fos = mPersistentServicesFile.startWrite(); 593 XmlSerializer out = new FastXmlSerializer(); 594 out.setOutput(fos, "utf-8"); 595 out.startDocument(null, true); 596 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 597 out.startTag(null, "services"); 598 for (int i = 0; i < mUserServices.size(); i++) { 599 final UserServices<V> user = mUserServices.valueAt(i); 600 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) { 601 out.startTag(null, "service"); 602 out.attribute(null, "uid", Integer.toString(service.getValue())); 603 mSerializerAndParser.writeAsXml(service.getKey(), out); 604 out.endTag(null, "service"); 605 } 606 } 607 out.endTag(null, "services"); 608 out.endDocument(); 609 mPersistentServicesFile.finishWrite(fos); 610 } catch (java.io.IOException e1) { 611 Log.w(TAG, "Error writing accounts", e1); 612 if (fos != null) { 613 mPersistentServicesFile.failWrite(fos); 614 } 615 } 616 } 617 618 public abstract V parseServiceAttributes(Resources res, 619 String packageName, AttributeSet attrs); 620} 621