• 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.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