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.os.UserManager; 31 import android.util.AtomicFile; 32 import android.util.AttributeSet; 33 import android.util.IntArray; 34 import android.util.Log; 35 import android.util.Slog; 36 import android.util.SparseArray; 37 import android.util.Xml; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.ArrayUtils; 42 import com.android.internal.util.FastXmlSerializer; 43 import com.google.android.collect.Lists; 44 import com.google.android.collect.Maps; 45 46 import org.xmlpull.v1.XmlPullParser; 47 import org.xmlpull.v1.XmlPullParserException; 48 import org.xmlpull.v1.XmlSerializer; 49 50 import java.io.File; 51 import java.io.FileDescriptor; 52 import java.io.FileOutputStream; 53 import java.io.IOException; 54 import java.io.InputStream; 55 import java.io.PrintWriter; 56 import java.nio.charset.StandardCharsets; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.Collections; 61 import java.util.List; 62 import java.util.Map; 63 64 import libcore.io.IoUtils; 65 66 /** 67 * Cache of registered services. This cache is lazily built by interrogating 68 * {@link PackageManager} on a per-user basis. It's updated as packages are 69 * added, removed and changed. Users are responsible for calling 70 * {@link #invalidateCache(int)} when a user is started, since 71 * {@link PackageManager} broadcasts aren't sent for stopped users. 72 * <p> 73 * The services are referred to by type V and are made available via the 74 * {@link #getServiceInfo} method. 75 * 76 * @hide 77 */ 78 public abstract class RegisteredServicesCache<V> { 79 private static final String TAG = "PackageManager"; 80 private static final boolean DEBUG = false; 81 protected static final String REGISTERED_SERVICES_DIR = "registered_services"; 82 83 public final Context mContext; 84 private final String mInterfaceName; 85 private final String mMetaDataName; 86 private final String mAttributesName; 87 private final XmlSerializerAndParser<V> mSerializerAndParser; 88 89 protected final Object mServicesLock = new Object(); 90 91 @GuardedBy("mServicesLock") 92 private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2); 93 94 private static class UserServices<V> { 95 @GuardedBy("mServicesLock") 96 final Map<V, Integer> persistentServices = Maps.newHashMap(); 97 @GuardedBy("mServicesLock") 98 Map<V, ServiceInfo<V>> services = null; 99 @GuardedBy("mServicesLock") 100 boolean mPersistentServicesFileDidNotExist = true; 101 } 102 103 @GuardedBy("mServicesLock") findOrCreateUserLocked(int userId)104 private UserServices<V> findOrCreateUserLocked(int userId) { 105 return findOrCreateUserLocked(userId, true); 106 } 107 108 @GuardedBy("mServicesLock") findOrCreateUserLocked(int userId, boolean loadFromFileIfNew)109 private UserServices<V> findOrCreateUserLocked(int userId, boolean loadFromFileIfNew) { 110 UserServices<V> services = mUserServices.get(userId); 111 if (services == null) { 112 services = new UserServices<V>(); 113 mUserServices.put(userId, services); 114 if (loadFromFileIfNew && mSerializerAndParser != null) { 115 // Check if user exists and try loading data from file 116 // clear existing data if there was an error during migration 117 UserInfo user = getUser(userId); 118 if (user != null) { 119 AtomicFile file = createFileForUser(user.id); 120 if (file.getBaseFile().exists()) { 121 if (DEBUG) { 122 Slog.i(TAG, String.format("Loading u%s data from %s", user.id, file)); 123 } 124 InputStream is = null; 125 try { 126 is = file.openRead(); 127 readPersistentServicesLocked(is); 128 } catch (Exception e) { 129 Log.w(TAG, "Error reading persistent services for user " + user.id, e); 130 } finally { 131 IoUtils.closeQuietly(is); 132 } 133 } 134 } 135 } 136 } 137 return services; 138 } 139 140 // the listener and handler are synchronized on "this" and must be updated together 141 private RegisteredServicesCacheListener<V> mListener; 142 private Handler mHandler; 143 RegisteredServicesCache(Context context, String interfaceName, String metaDataName, String attributeName, XmlSerializerAndParser<V> serializerAndParser)144 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, 145 String attributeName, XmlSerializerAndParser<V> serializerAndParser) { 146 mContext = context; 147 mInterfaceName = interfaceName; 148 mMetaDataName = metaDataName; 149 mAttributesName = attributeName; 150 mSerializerAndParser = serializerAndParser; 151 152 migrateIfNecessaryLocked(); 153 154 IntentFilter intentFilter = new IntentFilter(); 155 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 156 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 157 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 158 intentFilter.addDataScheme("package"); 159 mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null); 160 161 // Register for events related to sdcard installation. 162 IntentFilter sdFilter = new IntentFilter(); 163 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 164 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 165 mContext.registerReceiver(mExternalReceiver, sdFilter); 166 167 // Register for user-related events 168 IntentFilter userFilter = new IntentFilter(); 169 sdFilter.addAction(Intent.ACTION_USER_REMOVED); 170 mContext.registerReceiver(mUserRemovedReceiver, userFilter); 171 } 172 handlePackageEvent(Intent intent, int userId)173 private final void handlePackageEvent(Intent intent, int userId) { 174 // Don't regenerate the services map when the package is removed or its 175 // ASEC container unmounted as a step in replacement. The subsequent 176 // _ADDED / _AVAILABLE call will regenerate the map in the final state. 177 final String action = intent.getAction(); 178 // it's a new-component action if it isn't some sort of removal 179 final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action) 180 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action); 181 // if it's a removal, is it part of an update-in-place step? 182 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 183 184 if (isRemoval && replacing) { 185 // package is going away, but it's the middle of an upgrade: keep the current 186 // state and do nothing here. This clause is intentionally empty. 187 } else { 188 int[] uids = null; 189 // either we're adding/changing, or it's a removal without replacement, so 190 // we need to update the set of available services 191 if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) 192 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 193 uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); 194 } else { 195 int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 196 if (uid > 0) { 197 uids = new int[] { uid }; 198 } 199 } 200 generateServicesMap(uids, userId); 201 } 202 } 203 204 private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { 205 @Override 206 public void onReceive(Context context, Intent intent) { 207 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 208 if (uid != -1) { 209 handlePackageEvent(intent, UserHandle.getUserId(uid)); 210 } 211 } 212 }; 213 214 private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() { 215 @Override 216 public void onReceive(Context context, Intent intent) { 217 // External apps can't coexist with multi-user, so scan owner 218 handlePackageEvent(intent, UserHandle.USER_SYSTEM); 219 } 220 }; 221 222 private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() { 223 @Override 224 public void onReceive(Context context, Intent intent) { 225 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 226 if (DEBUG) { 227 Slog.d(TAG, "u" + userId + " removed - cleaning up"); 228 } 229 onUserRemoved(userId); 230 } 231 }; 232 invalidateCache(int userId)233 public void invalidateCache(int userId) { 234 synchronized (mServicesLock) { 235 final UserServices<V> user = findOrCreateUserLocked(userId); 236 user.services = null; 237 onServicesChangedLocked(userId); 238 } 239 } 240 dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId)241 public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) { 242 synchronized (mServicesLock) { 243 final UserServices<V> user = findOrCreateUserLocked(userId); 244 if (user.services != null) { 245 fout.println("RegisteredServicesCache: " + user.services.size() + " services"); 246 for (ServiceInfo<?> info : user.services.values()) { 247 fout.println(" " + info); 248 } 249 } else { 250 fout.println("RegisteredServicesCache: services not loaded"); 251 } 252 } 253 } 254 getListener()255 public RegisteredServicesCacheListener<V> getListener() { 256 synchronized (this) { 257 return mListener; 258 } 259 } 260 setListener(RegisteredServicesCacheListener<V> listener, Handler handler)261 public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) { 262 if (handler == null) { 263 handler = new Handler(mContext.getMainLooper()); 264 } 265 synchronized (this) { 266 mHandler = handler; 267 mListener = listener; 268 } 269 } 270 notifyListener(final V type, final int userId, final boolean removed)271 private void notifyListener(final V type, final int userId, final boolean removed) { 272 if (DEBUG) { 273 Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added")); 274 } 275 RegisteredServicesCacheListener<V> listener; 276 Handler handler; 277 synchronized (this) { 278 listener = mListener; 279 handler = mHandler; 280 } 281 if (listener == null) { 282 return; 283 } 284 285 final RegisteredServicesCacheListener<V> listener2 = listener; 286 handler.post(new Runnable() { 287 public void run() { 288 listener2.onServiceChanged(type, userId, removed); 289 } 290 }); 291 } 292 293 /** 294 * Value type that describes a Service. The information within can be used 295 * to bind to the service. 296 */ 297 public static class ServiceInfo<V> { 298 public final V type; 299 public final ComponentInfo componentInfo; 300 public final ComponentName componentName; 301 public final int uid; 302 303 /** @hide */ ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName)304 public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) { 305 this.type = type; 306 this.componentInfo = componentInfo; 307 this.componentName = componentName; 308 this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1; 309 } 310 311 @Override toString()312 public String toString() { 313 return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid; 314 } 315 } 316 317 /** 318 * Accessor for the registered authenticators. 319 * @param type the account type of the authenticator 320 * @return the AuthenticatorInfo that matches the account type or null if none is present 321 */ getServiceInfo(V type, int userId)322 public ServiceInfo<V> getServiceInfo(V type, int userId) { 323 synchronized (mServicesLock) { 324 // Find user and lazily populate cache 325 final UserServices<V> user = findOrCreateUserLocked(userId); 326 if (user.services == null) { 327 generateServicesMap(null, userId); 328 } 329 return user.services.get(type); 330 } 331 } 332 333 /** 334 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all 335 * registered authenticators. 336 */ getAllServices(int userId)337 public Collection<ServiceInfo<V>> getAllServices(int userId) { 338 synchronized (mServicesLock) { 339 // Find user and lazily populate cache 340 final UserServices<V> user = findOrCreateUserLocked(userId); 341 if (user.services == null) { 342 generateServicesMap(null, userId); 343 } 344 return Collections.unmodifiableCollection( 345 new ArrayList<ServiceInfo<V>>(user.services.values())); 346 } 347 } 348 updateServices(int userId)349 public void updateServices(int userId) { 350 if (DEBUG) { 351 Slog.d(TAG, "updateServices u" + userId); 352 } 353 List<ServiceInfo<V>> allServices; 354 synchronized (mServicesLock) { 355 final UserServices<V> user = findOrCreateUserLocked(userId); 356 // If services haven't been initialized yet - no updates required 357 if (user.services == null) { 358 return; 359 } 360 allServices = new ArrayList<>(user.services.values()); 361 } 362 IntArray updatedUids = null; 363 for (ServiceInfo<V> service : allServices) { 364 int versionCode = service.componentInfo.applicationInfo.versionCode; 365 String pkg = service.componentInfo.packageName; 366 ApplicationInfo newAppInfo = null; 367 try { 368 newAppInfo = mContext.getPackageManager().getApplicationInfoAsUser(pkg, 0, userId); 369 } catch (NameNotFoundException e) { 370 // Package uninstalled - treat as null app info 371 } 372 // If package updated or removed 373 if ((newAppInfo == null) || (newAppInfo.versionCode != versionCode)) { 374 if (DEBUG) { 375 Slog.d(TAG, "Package " + pkg + " uid=" + service.uid 376 + " updated. New appInfo: " + newAppInfo); 377 } 378 if (updatedUids == null) { 379 updatedUids = new IntArray(); 380 } 381 updatedUids.add(service.uid); 382 } 383 } 384 if (updatedUids != null && updatedUids.size() > 0) { 385 int[] updatedUidsArray = updatedUids.toArray(); 386 generateServicesMap(updatedUidsArray, userId); 387 } 388 } 389 390 @VisibleForTesting inSystemImage(int callerUid)391 protected boolean inSystemImage(int callerUid) { 392 String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); 393 for (String name : packages) { 394 try { 395 PackageInfo packageInfo = 396 mContext.getPackageManager().getPackageInfo(name, 0 /* flags */); 397 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 398 return true; 399 } 400 } catch (PackageManager.NameNotFoundException e) { 401 return false; 402 } 403 } 404 return false; 405 } 406 407 @VisibleForTesting queryIntentServices(int userId)408 protected List<ResolveInfo> queryIntentServices(int userId) { 409 final PackageManager pm = mContext.getPackageManager(); 410 return pm.queryIntentServicesAsUser(new Intent(mInterfaceName), 411 PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE 412 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 413 userId); 414 } 415 416 /** 417 * Populate {@link UserServices#services} by scanning installed packages for 418 * given {@link UserHandle}. 419 * @param changedUids the array of uids that have been affected, as mentioned in the broadcast 420 * or null to assume that everything is affected. 421 * @param userId the user for whom to update the services map. 422 */ generateServicesMap(int[] changedUids, int userId)423 private void generateServicesMap(int[] changedUids, int userId) { 424 if (DEBUG) { 425 Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " 426 + Arrays.toString(changedUids)); 427 } 428 429 final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>(); 430 final List<ResolveInfo> resolveInfos = queryIntentServices(userId); 431 for (ResolveInfo resolveInfo : resolveInfos) { 432 try { 433 ServiceInfo<V> info = parseServiceInfo(resolveInfo); 434 if (info == null) { 435 Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); 436 continue; 437 } 438 serviceInfos.add(info); 439 } catch (XmlPullParserException|IOException e) { 440 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); 441 } 442 } 443 444 synchronized (mServicesLock) { 445 final UserServices<V> user = findOrCreateUserLocked(userId); 446 final boolean firstScan = user.services == null; 447 if (firstScan) { 448 user.services = Maps.newHashMap(); 449 } 450 451 StringBuilder changes = new StringBuilder(); 452 boolean changed = false; 453 for (ServiceInfo<V> info : serviceInfos) { 454 // four cases: 455 // - doesn't exist yet 456 // - add, notify user that it was added 457 // - exists and the UID is the same 458 // - replace, don't notify user 459 // - exists, the UID is different, and the new one is not a system package 460 // - ignore 461 // - exists, the UID is different, and the new one is a system package 462 // - add, notify user that it was added 463 Integer previousUid = user.persistentServices.get(info.type); 464 if (previousUid == null) { 465 if (DEBUG) { 466 changes.append(" New service added: ").append(info).append("\n"); 467 } 468 changed = true; 469 user.services.put(info.type, info); 470 user.persistentServices.put(info.type, info.uid); 471 if (!(user.mPersistentServicesFileDidNotExist && firstScan)) { 472 notifyListener(info.type, userId, false /* removed */); 473 } 474 } else if (previousUid == info.uid) { 475 if (DEBUG) { 476 changes.append(" Existing service (nop): ").append(info).append("\n"); 477 } 478 user.services.put(info.type, info); 479 } else if (inSystemImage(info.uid) 480 || !containsTypeAndUid(serviceInfos, info.type, previousUid)) { 481 if (DEBUG) { 482 if (inSystemImage(info.uid)) { 483 changes.append(" System service replacing existing: ").append(info) 484 .append("\n"); 485 } else { 486 changes.append(" Existing service replacing a removed service: ") 487 .append(info).append("\n"); 488 } 489 } 490 changed = true; 491 user.services.put(info.type, info); 492 user.persistentServices.put(info.type, info.uid); 493 notifyListener(info.type, userId, false /* removed */); 494 } else { 495 // ignore 496 if (DEBUG) { 497 changes.append(" Existing service with new uid ignored: ").append(info) 498 .append("\n"); 499 } 500 } 501 } 502 503 ArrayList<V> toBeRemoved = Lists.newArrayList(); 504 for (V v1 : user.persistentServices.keySet()) { 505 // Remove a persisted service that's not in the currently available services list. 506 // And only if it is in the list of changedUids. 507 if (!containsType(serviceInfos, v1) 508 && containsUid(changedUids, user.persistentServices.get(v1))) { 509 toBeRemoved.add(v1); 510 } 511 } 512 for (V v1 : toBeRemoved) { 513 if (DEBUG) { 514 changes.append(" Service removed: ").append(v1).append("\n"); 515 } 516 changed = true; 517 user.persistentServices.remove(v1); 518 user.services.remove(v1); 519 notifyListener(v1, userId, true /* removed */); 520 } 521 if (DEBUG) { 522 Log.d(TAG, "user.services="); 523 for (V v : user.services.keySet()) { 524 Log.d(TAG, " " + v + " " + user.services.get(v)); 525 } 526 Log.d(TAG, "user.persistentServices="); 527 for (V v : user.persistentServices.keySet()) { 528 Log.d(TAG, " " + v + " " + user.persistentServices.get(v)); 529 } 530 } 531 if (DEBUG) { 532 if (changes.length() > 0) { 533 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + 534 serviceInfos.size() + " services:\n" + changes); 535 } else { 536 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " + 537 serviceInfos.size() + " services unchanged"); 538 } 539 } 540 if (changed) { 541 onServicesChangedLocked(userId); 542 writePersistentServicesLocked(user, userId); 543 } 544 } 545 } 546 onServicesChangedLocked(int userId)547 protected void onServicesChangedLocked(int userId) { 548 // Feel free to override 549 } 550 551 /** 552 * Returns true if the list of changed uids is null (wildcard) or the specified uid 553 * is contained in the list of changed uids. 554 */ containsUid(int[] changedUids, int uid)555 private boolean containsUid(int[] changedUids, int uid) { 556 return changedUids == null || ArrayUtils.contains(changedUids, uid); 557 } 558 containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type)559 private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) { 560 for (int i = 0, N = serviceInfos.size(); i < N; i++) { 561 if (serviceInfos.get(i).type.equals(type)) { 562 return true; 563 } 564 } 565 566 return false; 567 } 568 containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid)569 private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) { 570 for (int i = 0, N = serviceInfos.size(); i < N; i++) { 571 final ServiceInfo<V> serviceInfo = serviceInfos.get(i); 572 if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) { 573 return true; 574 } 575 } 576 577 return false; 578 } 579 580 @VisibleForTesting parseServiceInfo(ResolveInfo service)581 protected ServiceInfo<V> parseServiceInfo(ResolveInfo service) 582 throws XmlPullParserException, IOException { 583 android.content.pm.ServiceInfo si = service.serviceInfo; 584 ComponentName componentName = new ComponentName(si.packageName, si.name); 585 586 PackageManager pm = mContext.getPackageManager(); 587 588 XmlResourceParser parser = null; 589 try { 590 parser = si.loadXmlMetaData(pm, mMetaDataName); 591 if (parser == null) { 592 throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); 593 } 594 595 AttributeSet attrs = Xml.asAttributeSet(parser); 596 597 int type; 598 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 599 && type != XmlPullParser.START_TAG) { 600 } 601 602 String nodeName = parser.getName(); 603 if (!mAttributesName.equals(nodeName)) { 604 throw new XmlPullParserException( 605 "Meta-data does not start with " + mAttributesName + " tag"); 606 } 607 608 V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo), 609 si.packageName, attrs); 610 if (v == null) { 611 return null; 612 } 613 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo; 614 return new ServiceInfo<V>(v, serviceInfo, componentName); 615 } catch (NameNotFoundException e) { 616 throw new XmlPullParserException( 617 "Unable to load resources for pacakge " + si.packageName); 618 } finally { 619 if (parser != null) parser.close(); 620 } 621 } 622 623 /** 624 * Read all sync status back in to the initial engine state. 625 */ readPersistentServicesLocked(InputStream is)626 private void readPersistentServicesLocked(InputStream is) 627 throws XmlPullParserException, IOException { 628 XmlPullParser parser = Xml.newPullParser(); 629 parser.setInput(is, StandardCharsets.UTF_8.name()); 630 int eventType = parser.getEventType(); 631 while (eventType != XmlPullParser.START_TAG 632 && eventType != XmlPullParser.END_DOCUMENT) { 633 eventType = parser.next(); 634 } 635 String tagName = parser.getName(); 636 if ("services".equals(tagName)) { 637 eventType = parser.next(); 638 do { 639 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) { 640 tagName = parser.getName(); 641 if ("service".equals(tagName)) { 642 V service = mSerializerAndParser.createFromXml(parser); 643 if (service == null) { 644 break; 645 } 646 String uidString = parser.getAttributeValue(null, "uid"); 647 final int uid = Integer.parseInt(uidString); 648 final int userId = UserHandle.getUserId(uid); 649 final UserServices<V> user = findOrCreateUserLocked(userId, 650 false /*loadFromFileIfNew*/) ; 651 user.persistentServices.put(service, uid); 652 } 653 } 654 eventType = parser.next(); 655 } while (eventType != XmlPullParser.END_DOCUMENT); 656 } 657 } 658 migrateIfNecessaryLocked()659 private void migrateIfNecessaryLocked() { 660 if (mSerializerAndParser == null) { 661 return; 662 } 663 File systemDir = new File(getDataDirectory(), "system"); 664 File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR); 665 AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml")); 666 boolean oldFileExists = oldFile.getBaseFile().exists(); 667 668 if (oldFileExists) { 669 File marker = new File(syncDir, mInterfaceName + ".xml.migrated"); 670 // if not migrated, perform the migration and add a marker 671 if (!marker.exists()) { 672 if (DEBUG) { 673 Slog.i(TAG, "Marker file " + marker + " does not exist - running migration"); 674 } 675 InputStream is = null; 676 try { 677 is = oldFile.openRead(); 678 mUserServices.clear(); 679 readPersistentServicesLocked(is); 680 } catch (Exception e) { 681 Log.w(TAG, "Error reading persistent services, starting from scratch", e); 682 } finally { 683 IoUtils.closeQuietly(is); 684 } 685 try { 686 for (UserInfo user : getUsers()) { 687 UserServices<V> userServices = mUserServices.get(user.id); 688 if (userServices != null) { 689 if (DEBUG) { 690 Slog.i(TAG, "Migrating u" + user.id + " services " 691 + userServices.persistentServices); 692 } 693 writePersistentServicesLocked(userServices, user.id); 694 } 695 } 696 marker.createNewFile(); 697 } catch (Exception e) { 698 Log.w(TAG, "Migration failed", e); 699 } 700 // Migration is complete and we don't need to keep data for all users anymore, 701 // It will be loaded from a new location when requested 702 mUserServices.clear(); 703 } 704 } 705 } 706 707 /** 708 * Writes services of a specified user to the file. 709 */ writePersistentServicesLocked(UserServices<V> user, int userId)710 private void writePersistentServicesLocked(UserServices<V> user, int userId) { 711 if (mSerializerAndParser == null) { 712 return; 713 } 714 AtomicFile atomicFile = createFileForUser(userId); 715 FileOutputStream fos = null; 716 try { 717 fos = atomicFile.startWrite(); 718 XmlSerializer out = new FastXmlSerializer(); 719 out.setOutput(fos, StandardCharsets.UTF_8.name()); 720 out.startDocument(null, true); 721 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 722 out.startTag(null, "services"); 723 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) { 724 out.startTag(null, "service"); 725 out.attribute(null, "uid", Integer.toString(service.getValue())); 726 mSerializerAndParser.writeAsXml(service.getKey(), out); 727 out.endTag(null, "service"); 728 } 729 out.endTag(null, "services"); 730 out.endDocument(); 731 atomicFile.finishWrite(fos); 732 } catch (IOException e1) { 733 Log.w(TAG, "Error writing accounts", e1); 734 if (fos != null) { 735 atomicFile.failWrite(fos); 736 } 737 } 738 } 739 740 @VisibleForTesting onUserRemoved(int userId)741 protected void onUserRemoved(int userId) { 742 synchronized (mServicesLock) { 743 mUserServices.remove(userId); 744 } 745 } 746 747 @VisibleForTesting getUsers()748 protected List<UserInfo> getUsers() { 749 return UserManager.get(mContext).getUsers(true); 750 } 751 752 @VisibleForTesting getUser(int userId)753 protected UserInfo getUser(int userId) { 754 return UserManager.get(mContext).getUserInfo(userId); 755 } 756 createFileForUser(int userId)757 private AtomicFile createFileForUser(int userId) { 758 File userDir = getUserSystemDirectory(userId); 759 File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml"); 760 return new AtomicFile(userFile); 761 } 762 763 @VisibleForTesting getUserSystemDirectory(int userId)764 protected File getUserSystemDirectory(int userId) { 765 return Environment.getUserSystemDirectory(userId); 766 } 767 768 @VisibleForTesting getDataDirectory()769 protected File getDataDirectory() { 770 return Environment.getDataDirectory(); 771 } 772 773 @VisibleForTesting getPersistentServices(int userId)774 protected Map<V, Integer> getPersistentServices(int userId) { 775 return findOrCreateUserLocked(userId).persistentServices; 776 } 777 parseServiceAttributes(Resources res, String packageName, AttributeSet attrs)778 public abstract V parseServiceAttributes(Resources res, 779 String packageName, AttributeSet attrs); 780 } 781