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