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