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
17package android.content.pm;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.content.res.Resources;
26import android.content.res.XmlResourceParser;
27import android.os.Environment;
28import android.os.Handler;
29import android.os.UserHandle;
30import android.os.UserManager;
31import android.util.AtomicFile;
32import android.util.AttributeSet;
33import android.util.Log;
34import android.util.Slog;
35import android.util.SparseArray;
36import android.util.Xml;
37
38import com.android.internal.annotations.GuardedBy;
39import com.android.internal.annotations.VisibleForTesting;
40import com.android.internal.util.ArrayUtils;
41import com.android.internal.util.FastXmlSerializer;
42import com.google.android.collect.Lists;
43import com.google.android.collect.Maps;
44
45import org.xmlpull.v1.XmlPullParser;
46import org.xmlpull.v1.XmlPullParserException;
47import org.xmlpull.v1.XmlSerializer;
48
49import java.io.File;
50import java.io.FileDescriptor;
51import java.io.FileOutputStream;
52import java.io.IOException;
53import java.io.InputStream;
54import java.io.PrintWriter;
55import java.nio.charset.StandardCharsets;
56import java.util.ArrayList;
57import java.util.Collection;
58import java.util.Collections;
59import java.util.List;
60import java.util.Map;
61
62import 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 */
76public 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")
102    private UserServices<V> findOrCreateUserLocked(int userId) {
103        return findOrCreateUserLocked(userId, true);
104    }
105
106    @GuardedBy("mServicesLock")
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
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
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_SYSTEM);
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
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
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
253    public RegisteredServicesCacheListener<V> getListener() {
254        synchronized (this) {
255            return mListener;
256        }
257    }
258
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
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 ComponentInfo componentInfo;
298        public final ComponentName componentName;
299        public final int uid;
300
301        /** @hide */
302        public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) {
303            this.type = type;
304            this.componentInfo = componentInfo;
305            this.componentName = componentName;
306            this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1;
307        }
308
309        @Override
310        public String toString() {
311            return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
312        }
313    }
314
315    /**
316     * Accessor for the registered authenticators.
317     * @param type the account type of the authenticator
318     * @return the AuthenticatorInfo that matches the account type or null if none is present
319     */
320    public ServiceInfo<V> getServiceInfo(V type, int userId) {
321        synchronized (mServicesLock) {
322            // Find user and lazily populate cache
323            final UserServices<V> user = findOrCreateUserLocked(userId);
324            if (user.services == null) {
325                generateServicesMap(null, userId);
326            }
327            return user.services.get(type);
328        }
329    }
330
331    /**
332     * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
333     * registered authenticators.
334     */
335    public Collection<ServiceInfo<V>> getAllServices(int userId) {
336        synchronized (mServicesLock) {
337            // Find user and lazily populate cache
338            final UserServices<V> user = findOrCreateUserLocked(userId);
339            if (user.services == null) {
340                generateServicesMap(null, userId);
341            }
342            return Collections.unmodifiableCollection(
343                    new ArrayList<ServiceInfo<V>>(user.services.values()));
344        }
345    }
346
347    @VisibleForTesting
348    protected boolean inSystemImage(int callerUid) {
349        String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
350        for (String name : packages) {
351            try {
352                PackageInfo packageInfo =
353                        mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
354                if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
355                    return true;
356                }
357            } catch (PackageManager.NameNotFoundException e) {
358                return false;
359            }
360        }
361        return false;
362    }
363
364    @VisibleForTesting
365    protected List<ResolveInfo> queryIntentServices(int userId) {
366        final PackageManager pm = mContext.getPackageManager();
367        return pm.queryIntentServicesAsUser(new Intent(mInterfaceName),
368                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
369                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
370                userId);
371    }
372
373    /**
374     * Populate {@link UserServices#services} by scanning installed packages for
375     * given {@link UserHandle}.
376     * @param changedUids the array of uids that have been affected, as mentioned in the broadcast
377     *                    or null to assume that everything is affected.
378     * @param userId the user for whom to update the services map.
379     */
380    private void generateServicesMap(int[] changedUids, int userId) {
381        if (DEBUG) {
382            Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids);
383        }
384
385        final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
386        final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
387        for (ResolveInfo resolveInfo : resolveInfos) {
388            try {
389                ServiceInfo<V> info = parseServiceInfo(resolveInfo);
390                if (info == null) {
391                    Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
392                    continue;
393                }
394                serviceInfos.add(info);
395            } catch (XmlPullParserException|IOException e) {
396                Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
397            }
398        }
399
400        synchronized (mServicesLock) {
401            final UserServices<V> user = findOrCreateUserLocked(userId);
402            final boolean firstScan = user.services == null;
403            if (firstScan) {
404                user.services = Maps.newHashMap();
405            }
406
407            StringBuilder changes = new StringBuilder();
408            boolean changed = false;
409            for (ServiceInfo<V> info : serviceInfos) {
410                // four cases:
411                // - doesn't exist yet
412                //   - add, notify user that it was added
413                // - exists and the UID is the same
414                //   - replace, don't notify user
415                // - exists, the UID is different, and the new one is not a system package
416                //   - ignore
417                // - exists, the UID is different, and the new one is a system package
418                //   - add, notify user that it was added
419                Integer previousUid = user.persistentServices.get(info.type);
420                if (previousUid == null) {
421                    if (DEBUG) {
422                        changes.append("  New service added: ").append(info).append("\n");
423                    }
424                    changed = true;
425                    user.services.put(info.type, info);
426                    user.persistentServices.put(info.type, info.uid);
427                    if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
428                        notifyListener(info.type, userId, false /* removed */);
429                    }
430                } else if (previousUid == info.uid) {
431                    if (DEBUG) {
432                        changes.append("  Existing service (nop): ").append(info).append("\n");
433                    }
434                    user.services.put(info.type, info);
435                } else if (inSystemImage(info.uid)
436                        || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
437                    if (DEBUG) {
438                        if (inSystemImage(info.uid)) {
439                            changes.append("  System service replacing existing: ").append(info)
440                                    .append("\n");
441                        } else {
442                            changes.append("  Existing service replacing a removed service: ")
443                                    .append(info).append("\n");
444                        }
445                    }
446                    changed = true;
447                    user.services.put(info.type, info);
448                    user.persistentServices.put(info.type, info.uid);
449                    notifyListener(info.type, userId, false /* removed */);
450                } else {
451                    // ignore
452                    if (DEBUG) {
453                        changes.append("  Existing service with new uid ignored: ").append(info)
454                                .append("\n");
455                    }
456                }
457            }
458
459            ArrayList<V> toBeRemoved = Lists.newArrayList();
460            for (V v1 : user.persistentServices.keySet()) {
461                // Remove a persisted service that's not in the currently available services list.
462                // And only if it is in the list of changedUids.
463                if (!containsType(serviceInfos, v1)
464                        && containsUid(changedUids, user.persistentServices.get(v1))) {
465                    toBeRemoved.add(v1);
466                }
467            }
468            for (V v1 : toBeRemoved) {
469                if (DEBUG) {
470                    changes.append("  Service removed: ").append(v1).append("\n");
471                }
472                changed = true;
473                user.persistentServices.remove(v1);
474                user.services.remove(v1);
475                notifyListener(v1, userId, true /* removed */);
476            }
477            if (DEBUG) {
478                Log.d(TAG, "user.services=");
479                for (V v : user.services.keySet()) {
480                    Log.d(TAG, "  " + v + " " + user.services.get(v));
481                }
482                Log.d(TAG, "user.persistentServices=");
483                for (V v : user.persistentServices.keySet()) {
484                    Log.d(TAG, "  " + v + " " + user.persistentServices.get(v));
485                }
486            }
487            if (DEBUG) {
488                if (changes.length() > 0) {
489                    Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
490                            serviceInfos.size() + " services:\n" + changes);
491                } else {
492                    Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
493                            serviceInfos.size() + " services unchanged");
494                }
495            }
496            if (changed) {
497                onServicesChangedLocked(userId);
498                writePersistentServicesLocked(user, userId);
499            }
500        }
501    }
502
503    protected void onServicesChangedLocked(int userId) {
504        // Feel free to override
505    }
506
507    /**
508     * Returns true if the list of changed uids is null (wildcard) or the specified uid
509     * is contained in the list of changed uids.
510     */
511    private boolean containsUid(int[] changedUids, int uid) {
512        return changedUids == null || ArrayUtils.contains(changedUids, uid);
513    }
514
515    private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
516        for (int i = 0, N = serviceInfos.size(); i < N; i++) {
517            if (serviceInfos.get(i).type.equals(type)) {
518                return true;
519            }
520        }
521
522        return false;
523    }
524
525    private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
526        for (int i = 0, N = serviceInfos.size(); i < N; i++) {
527            final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
528            if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
529                return true;
530            }
531        }
532
533        return false;
534    }
535
536    @VisibleForTesting
537    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
538            throws XmlPullParserException, IOException {
539        android.content.pm.ServiceInfo si = service.serviceInfo;
540        ComponentName componentName = new ComponentName(si.packageName, si.name);
541
542        PackageManager pm = mContext.getPackageManager();
543
544        XmlResourceParser parser = null;
545        try {
546            parser = si.loadXmlMetaData(pm, mMetaDataName);
547            if (parser == null) {
548                throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
549            }
550
551            AttributeSet attrs = Xml.asAttributeSet(parser);
552
553            int type;
554            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
555                    && type != XmlPullParser.START_TAG) {
556            }
557
558            String nodeName = parser.getName();
559            if (!mAttributesName.equals(nodeName)) {
560                throw new XmlPullParserException(
561                        "Meta-data does not start with " + mAttributesName +  " tag");
562            }
563
564            V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
565                    si.packageName, attrs);
566            if (v == null) {
567                return null;
568            }
569            final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
570            return new ServiceInfo<V>(v, serviceInfo, componentName);
571        } catch (NameNotFoundException e) {
572            throw new XmlPullParserException(
573                    "Unable to load resources for pacakge " + si.packageName);
574        } finally {
575            if (parser != null) parser.close();
576        }
577    }
578
579    /**
580     * Read all sync status back in to the initial engine state.
581     */
582    private void readPersistentServicesLocked(InputStream is)
583            throws XmlPullParserException, IOException {
584        XmlPullParser parser = Xml.newPullParser();
585        parser.setInput(is, StandardCharsets.UTF_8.name());
586        int eventType = parser.getEventType();
587        while (eventType != XmlPullParser.START_TAG
588                && eventType != XmlPullParser.END_DOCUMENT) {
589            eventType = parser.next();
590        }
591        String tagName = parser.getName();
592        if ("services".equals(tagName)) {
593            eventType = parser.next();
594            do {
595                if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
596                    tagName = parser.getName();
597                    if ("service".equals(tagName)) {
598                        V service = mSerializerAndParser.createFromXml(parser);
599                        if (service == null) {
600                            break;
601                        }
602                        String uidString = parser.getAttributeValue(null, "uid");
603                        final int uid = Integer.parseInt(uidString);
604                        final int userId = UserHandle.getUserId(uid);
605                        final UserServices<V> user = findOrCreateUserLocked(userId,
606                                false /*loadFromFileIfNew*/) ;
607                        user.persistentServices.put(service, uid);
608                    }
609                }
610                eventType = parser.next();
611            } while (eventType != XmlPullParser.END_DOCUMENT);
612        }
613    }
614
615    private void migrateIfNecessaryLocked() {
616        if (mSerializerAndParser == null) {
617            return;
618        }
619        File systemDir = new File(getDataDirectory(), "system");
620        File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR);
621        AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml"));
622        boolean oldFileExists = oldFile.getBaseFile().exists();
623
624        if (oldFileExists) {
625            File marker = new File(syncDir, mInterfaceName + ".xml.migrated");
626            // if not migrated, perform the migration and add a marker
627            if (!marker.exists()) {
628                if (DEBUG) {
629                    Slog.i(TAG, "Marker file " + marker + " does not exist - running migration");
630                }
631                InputStream is = null;
632                try {
633                    is = oldFile.openRead();
634                    mUserServices.clear();
635                    readPersistentServicesLocked(is);
636                } catch (Exception e) {
637                    Log.w(TAG, "Error reading persistent services, starting from scratch", e);
638                } finally {
639                    IoUtils.closeQuietly(is);
640                }
641                try {
642                    for (UserInfo user : getUsers()) {
643                        UserServices<V> userServices = mUserServices.get(user.id);
644                        if (userServices != null) {
645                            if (DEBUG) {
646                                Slog.i(TAG, "Migrating u" + user.id + " services "
647                                        + userServices.persistentServices);
648                            }
649                            writePersistentServicesLocked(userServices, user.id);
650                        }
651                    }
652                    marker.createNewFile();
653                } catch (Exception e) {
654                    Log.w(TAG, "Migration failed", e);
655                }
656                // Migration is complete and we don't need to keep data for all users anymore,
657                // It will be loaded from a new location when requested
658                mUserServices.clear();
659            }
660        }
661    }
662
663    /**
664     * Writes services of a specified user to the file.
665     */
666    private void writePersistentServicesLocked(UserServices<V> user, int userId) {
667        if (mSerializerAndParser == null) {
668            return;
669        }
670        AtomicFile atomicFile = createFileForUser(userId);
671        FileOutputStream fos = null;
672        try {
673            fos = atomicFile.startWrite();
674            XmlSerializer out = new FastXmlSerializer();
675            out.setOutput(fos, StandardCharsets.UTF_8.name());
676            out.startDocument(null, true);
677            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
678            out.startTag(null, "services");
679            for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
680                out.startTag(null, "service");
681                out.attribute(null, "uid", Integer.toString(service.getValue()));
682                mSerializerAndParser.writeAsXml(service.getKey(), out);
683                out.endTag(null, "service");
684            }
685            out.endTag(null, "services");
686            out.endDocument();
687            atomicFile.finishWrite(fos);
688        } catch (IOException e1) {
689            Log.w(TAG, "Error writing accounts", e1);
690            if (fos != null) {
691                atomicFile.failWrite(fos);
692            }
693        }
694    }
695
696    @VisibleForTesting
697    protected void onUserRemoved(int userId) {
698        synchronized (mServicesLock) {
699            mUserServices.remove(userId);
700        }
701    }
702
703    @VisibleForTesting
704    protected List<UserInfo> getUsers() {
705        return UserManager.get(mContext).getUsers(true);
706    }
707
708    @VisibleForTesting
709    protected UserInfo getUser(int userId) {
710        return UserManager.get(mContext).getUserInfo(userId);
711    }
712
713    private AtomicFile createFileForUser(int userId) {
714        File userDir = getUserSystemDirectory(userId);
715        File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
716        return new AtomicFile(userFile);
717    }
718
719    @VisibleForTesting
720    protected File getUserSystemDirectory(int userId) {
721        return Environment.getUserSystemDirectory(userId);
722    }
723
724    @VisibleForTesting
725    protected File getDataDirectory() {
726        return Environment.getDataDirectory();
727    }
728
729    @VisibleForTesting
730    protected Map<V, Integer> getPersistentServices(int userId) {
731        return findOrCreateUserLocked(userId).persistentServices;
732    }
733
734    public abstract V parseServiceAttributes(Resources res,
735            String packageName, AttributeSet attrs);
736}
737