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 17 package android.content.pm; 18 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; 36 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; 41 42 import org.xmlpull.v1.XmlPullParser; 43 import org.xmlpull.v1.XmlPullParserException; 44 import org.xmlpull.v1.XmlSerializer; 45 46 import java.io.File; 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; 56 import java.util.Map; 57 58 /** 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. 64 * <p> 65 * The services are referred to by type V and are made available via the 66 * {@link #getServiceInfo} method. 67 * 68 * @hide 69 */ 70 public abstract class RegisteredServicesCache<V> { 71 private static final String TAG = "PackageManager"; 72 private static final boolean DEBUG = false; 73 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; 79 80 private final Object mServicesLock = new Object(); 81 82 @GuardedBy("mServicesLock") 83 private boolean mPersistentServicesFileDidNotExist; 84 @GuardedBy("mServicesLock") 85 private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(); 86 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; 92 } 93 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); 99 } 100 return services; 101 } 102 103 /** 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. 106 */ 107 private final AtomicFile mPersistentServicesFile; 108 109 // the listener and handler are synchronized on "this" and must be updated together 110 private RegisteredServicesCacheListener<V> mListener; 111 private Handler mHandler; 112 113 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, 114 String attributeName, XmlSerializerAndParser<V> serializerAndParser) { 115 mContext = context; 116 mInterfaceName = interfaceName; 117 mMetaDataName = metaDataName; 118 mAttributesName = attributeName; 119 mSerializerAndParser = serializerAndParser; 120 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")); 125 126 // Load persisted services from disk 127 readPersistentServicesLocked(); 128 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); 135 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); 141 } 142 143 private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { 144 @Override 145 public void onReceive(Context context, Intent intent) { 146 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 147 if (uid != -1) { 148 generateServicesMap(UserHandle.getUserId(uid)); 149 } 150 } 151 }; 152 153 private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() { 154 @Override 155 public void onReceive(Context context, Intent intent) { 156 // External apps can't coexist with multi-user, so scan owner 157 generateServicesMap(UserHandle.USER_OWNER); 158 } 159 }; 160 161 public void invalidateCache(int userId) { 162 synchronized (mServicesLock) { 163 final UserServices<V> user = findOrCreateUserLocked(userId); 164 user.services = null; 165 } 166 } 167 168 public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) { 169 synchronized (mServicesLock) { 170 final UserServices<V> user = findOrCreateUserLocked(userId); 171 if (user.services != null) { 172 fout.println("RegisteredServicesCache: " + user.services.size() + " services"); 173 for (ServiceInfo<?> info : user.services.values()) { 174 fout.println(" " + info); 175 } 176 } else { 177 fout.println("RegisteredServicesCache: services not loaded"); 178 } 179 } 180 } 181 182 public RegisteredServicesCacheListener<V> getListener() { 183 synchronized (this) { 184 return mListener; 185 } 186 } 187 188 public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) { 189 if (handler == null) { 190 handler = new Handler(mContext.getMainLooper()); 191 } 192 synchronized (this) { 193 mHandler = handler; 194 mListener = listener; 195 } 196 } 197 198 private void notifyListener(final V type, final int userId, final boolean removed) { 199 if (DEBUG) { 200 Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added")); 201 } 202 RegisteredServicesCacheListener<V> listener; 203 Handler handler; 204 synchronized (this) { 205 listener = mListener; 206 handler = mHandler; 207 } 208 if (listener == null) { 209 return; 210 } 211 212 final RegisteredServicesCacheListener<V> listener2 = listener; 213 handler.post(new Runnable() { 214 public void run() { 215 listener2.onServiceChanged(type, userId, removed); 216 } 217 }); 218 } 219 220 /** 221 * Value type that describes a Service. The information within can be used 222 * to bind to the service. 223 */ 224 public static class ServiceInfo<V> { 225 public final V type; 226 public final ComponentName componentName; 227 public final int uid; 228 229 /** @hide */ 230 public ServiceInfo(V type, ComponentName componentName, int uid) { 231 this.type = type; 232 this.componentName = componentName; 233 this.uid = uid; 234 } 235 236 @Override 237 public String toString() { 238 return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid; 239 } 240 } 241 242 /** 243 * Accessor for the registered authenticators. 244 * @param type the account type of the authenticator 245 * @return the AuthenticatorInfo that matches the account type or null if none is present 246 */ 247 public ServiceInfo<V> getServiceInfo(V type, int userId) { 248 synchronized (mServicesLock) { 249 // Find user and lazily populate cache 250 final UserServices<V> user = findOrCreateUserLocked(userId); 251 if (user.services == null) { 252 generateServicesMap(userId); 253 } 254 return user.services.get(type); 255 } 256 } 257 258 /** 259 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all 260 * registered authenticators. 261 */ 262 public Collection<ServiceInfo<V>> getAllServices(int userId) { 263 synchronized (mServicesLock) { 264 // Find user and lazily populate cache 265 final UserServices<V> user = findOrCreateUserLocked(userId); 266 if (user.services == null) { 267 generateServicesMap(userId); 268 } 269 return Collections.unmodifiableCollection( 270 new ArrayList<ServiceInfo<V>>(user.services.values())); 271 } 272 } 273 274 private boolean inSystemImage(int callerUid) { 275 String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); 276 for (String name : packages) { 277 try { 278 PackageInfo packageInfo = 279 mContext.getPackageManager().getPackageInfo(name, 0 /* flags */); 280 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 281 return true; 282 } 283 } catch (PackageManager.NameNotFoundException e) { 284 return false; 285 } 286 } 287 return false; 288 } 289 290 /** 291 * Populate {@link UserServices#services} by scanning installed packages for 292 * given {@link UserHandle}. 293 */ 294 private void generateServicesMap(int userId) { 295 if (DEBUG) { 296 Slog.d(TAG, "generateServicesMap() for " + userId); 297 } 298 299 final PackageManager pm = mContext.getPackageManager(); 300 final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>(); 301 final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser( 302 new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId); 303 for (ResolveInfo resolveInfo : resolveInfos) { 304 try { 305 ServiceInfo<V> info = parseServiceInfo(resolveInfo); 306 if (info == null) { 307 Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); 308 continue; 309 } 310 serviceInfos.add(info); 311 } catch (XmlPullParserException e) { 312 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); 313 } catch (IOException e) { 314 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); 315 } 316 } 317 318 synchronized (mServicesLock) { 319 final UserServices<V> user = findOrCreateUserLocked(userId); 320 final boolean firstScan = user.services == null; 321 if (firstScan) { 322 user.services = Maps.newHashMap(); 323 } else { 324 user.services.clear(); 325 } 326 327 StringBuilder changes = new StringBuilder(); 328 boolean changed = false; 329 for (ServiceInfo<V> info : serviceInfos) { 330 // four cases: 331 // - doesn't exist yet 332 // - add, notify user that it was added 333 // - exists and the UID is the same 334 // - replace, don't notify user 335 // - exists, the UID is different, and the new one is not a system package 336 // - ignore 337 // - exists, the UID is different, and the new one is a system package 338 // - add, notify user that it was added 339 Integer previousUid = user.persistentServices.get(info.type); 340 if (previousUid == null) { 341 if (DEBUG) { 342 changes.append(" New service added: ").append(info).append("\n"); 343 } 344 changed = true; 345 user.services.put(info.type, info); 346 user.persistentServices.put(info.type, info.uid); 347 if (!(mPersistentServicesFileDidNotExist && firstScan)) { 348 notifyListener(info.type, userId, false /* removed */); 349 } 350 } else if (previousUid == info.uid) { 351 if (DEBUG) { 352 changes.append(" Existing service (nop): ").append(info).append("\n"); 353 } 354 user.services.put(info.type, info); 355 } else if (inSystemImage(info.uid) 356 || !containsTypeAndUid(serviceInfos, info.type, previousUid)) { 357 if (DEBUG) { 358 if (inSystemImage(info.uid)) { 359 changes.append(" System service replacing existing: ").append(info) 360 .append("\n"); 361 } else { 362 changes.append(" Existing service replacing a removed service: ") 363 .append(info).append("\n"); 364 } 365 } 366 changed = true; 367 user.services.put(info.type, info); 368 user.persistentServices.put(info.type, info.uid); 369 notifyListener(info.type, userId, false /* removed */); 370 } else { 371 // ignore 372 if (DEBUG) { 373 changes.append(" Existing service with new uid ignored: ").append(info) 374 .append("\n"); 375 } 376 } 377 } 378 379 ArrayList<V> toBeRemoved = Lists.newArrayList(); 380 for (V v1 : user.persistentServices.keySet()) { 381 if (!containsType(serviceInfos, v1)) { 382 toBeRemoved.add(v1); 383 } 384 } 385 for (V v1 : toBeRemoved) { 386 if (DEBUG) { 387 changes.append(" Service removed: ").append(v1).append("\n"); 388 } 389 changed = true; 390 user.persistentServices.remove(v1); 391 notifyListener(v1, userId, true /* removed */); 392 } 393 if (DEBUG) { 394 if (changes.length() > 0) { 395 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + 396 serviceInfos.size() + " services:\n" + changes); 397 } else { 398 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + 399 serviceInfos.size() + " services unchanged"); 400 } 401 } 402 if (changed) { 403 writePersistentServicesLocked(); 404 } 405 } 406 } 407 408 private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) { 409 for (int i = 0, N = serviceInfos.size(); i < N; i++) { 410 if (serviceInfos.get(i).type.equals(type)) { 411 return true; 412 } 413 } 414 415 return false; 416 } 417 418 private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) { 419 for (int i = 0, N = serviceInfos.size(); i < N; i++) { 420 final ServiceInfo<V> serviceInfo = serviceInfos.get(i); 421 if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) { 422 return true; 423 } 424 } 425 426 return false; 427 } 428 429 private ServiceInfo<V> parseServiceInfo(ResolveInfo service) 430 throws XmlPullParserException, IOException { 431 android.content.pm.ServiceInfo si = service.serviceInfo; 432 ComponentName componentName = new ComponentName(si.packageName, si.name); 433 434 PackageManager pm = mContext.getPackageManager(); 435 436 XmlResourceParser parser = null; 437 try { 438 parser = si.loadXmlMetaData(pm, mMetaDataName); 439 if (parser == null) { 440 throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); 441 } 442 443 AttributeSet attrs = Xml.asAttributeSet(parser); 444 445 int type; 446 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 447 && type != XmlPullParser.START_TAG) { 448 } 449 450 String nodeName = parser.getName(); 451 if (!mAttributesName.equals(nodeName)) { 452 throw new XmlPullParserException( 453 "Meta-data does not start with " + mAttributesName + " tag"); 454 } 455 456 V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo), 457 si.packageName, attrs); 458 if (v == null) { 459 return null; 460 } 461 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo; 462 final ApplicationInfo applicationInfo = serviceInfo.applicationInfo; 463 final int uid = applicationInfo.uid; 464 return new ServiceInfo<V>(v, componentName, uid); 465 } catch (NameNotFoundException e) { 466 throw new XmlPullParserException( 467 "Unable to load resources for pacakge " + si.packageName); 468 } finally { 469 if (parser != null) parser.close(); 470 } 471 } 472 473 /** 474 * Read all sync status back in to the initial engine state. 475 */ 476 private void readPersistentServicesLocked() { 477 mUserServices.clear(); 478 if (mSerializerAndParser == null) { 479 return; 480 } 481 FileInputStream fis = null; 482 try { 483 mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists(); 484 if (mPersistentServicesFileDidNotExist) { 485 return; 486 } 487 fis = mPersistentServicesFile.openRead(); 488 XmlPullParser parser = Xml.newPullParser(); 489 parser.setInput(fis, null); 490 int eventType = parser.getEventType(); 491 while (eventType != XmlPullParser.START_TAG 492 && eventType != XmlPullParser.END_DOCUMENT) { 493 eventType = parser.next(); 494 } 495 String tagName = parser.getName(); 496 if ("services".equals(tagName)) { 497 eventType = parser.next(); 498 do { 499 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) { 500 tagName = parser.getName(); 501 if ("service".equals(tagName)) { 502 V service = mSerializerAndParser.createFromXml(parser); 503 if (service == null) { 504 break; 505 } 506 String uidString = parser.getAttributeValue(null, "uid"); 507 final int uid = Integer.parseInt(uidString); 508 final int userId = UserHandle.getUserId(uid); 509 final UserServices<V> user = findOrCreateUserLocked(userId); 510 user.persistentServices.put(service, uid); 511 } 512 } 513 eventType = parser.next(); 514 } while (eventType != XmlPullParser.END_DOCUMENT); 515 } 516 } catch (Exception e) { 517 Log.w(TAG, "Error reading persistent services, starting from scratch", e); 518 } finally { 519 if (fis != null) { 520 try { 521 fis.close(); 522 } catch (java.io.IOException e1) { 523 } 524 } 525 } 526 } 527 528 /** 529 * Write all sync status to the sync status file. 530 */ 531 private void writePersistentServicesLocked() { 532 if (mSerializerAndParser == null) { 533 return; 534 } 535 FileOutputStream fos = null; 536 try { 537 fos = mPersistentServicesFile.startWrite(); 538 XmlSerializer out = new FastXmlSerializer(); 539 out.setOutput(fos, "utf-8"); 540 out.startDocument(null, true); 541 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 542 out.startTag(null, "services"); 543 for (int i = 0; i < mUserServices.size(); i++) { 544 final UserServices<V> user = mUserServices.valueAt(i); 545 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) { 546 out.startTag(null, "service"); 547 out.attribute(null, "uid", Integer.toString(service.getValue())); 548 mSerializerAndParser.writeAsXml(service.getKey(), out); 549 out.endTag(null, "service"); 550 } 551 } 552 out.endTag(null, "services"); 553 out.endDocument(); 554 mPersistentServicesFile.finishWrite(fos); 555 } catch (java.io.IOException e1) { 556 Log.w(TAG, "Error writing accounts", e1); 557 if (fos != null) { 558 mPersistentServicesFile.failWrite(fos); 559 } 560 } 561 } 562 563 public abstract V parseServiceAttributes(Resources res, 564 String packageName, AttributeSet attrs); 565 } 566