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