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