• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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