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.IntArray;
34import android.util.Log;
35import android.util.Slog;
36import android.util.SparseArray;
37import android.util.Xml;
38
39import com.android.internal.annotations.GuardedBy;
40import com.android.internal.annotations.VisibleForTesting;
41import com.android.internal.util.ArrayUtils;
42import com.android.internal.util.FastXmlSerializer;
43import com.google.android.collect.Lists;
44import com.google.android.collect.Maps;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48import org.xmlpull.v1.XmlSerializer;
49
50import java.io.File;
51import java.io.FileDescriptor;
52import java.io.FileOutputStream;
53import java.io.IOException;
54import java.io.InputStream;
55import java.io.PrintWriter;
56import java.nio.charset.StandardCharsets;
57import java.util.ArrayList;
58import java.util.Arrays;
59import java.util.Collection;
60import java.util.Collections;
61import java.util.List;
62import java.util.Map;
63
64import 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 */
78public 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")
104    private UserServices<V> findOrCreateUserLocked(int userId) {
105        return findOrCreateUserLocked(userId, true);
106    }
107
108    @GuardedBy("mServicesLock")
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
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
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
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
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
255    public RegisteredServicesCacheListener<V> getListener() {
256        synchronized (this) {
257            return mListener;
258        }
259    }
260
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
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 */
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
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     */
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     */
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
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
391    protected boolean inSystemImage(int callerUid) {
392        String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
393        if (packages != null) {
394            for (String name : packages) {
395                try {
396                    PackageInfo packageInfo =
397                            mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
398                    if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
399                        return true;
400                    }
401                } catch (PackageManager.NameNotFoundException e) {
402                    return false;
403                }
404            }
405        }
406        return false;
407    }
408
409    @VisibleForTesting
410    protected List<ResolveInfo> queryIntentServices(int userId) {
411        final PackageManager pm = mContext.getPackageManager();
412        return pm.queryIntentServicesAsUser(new Intent(mInterfaceName),
413                PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
414                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
415                userId);
416    }
417
418    /**
419     * Populate {@link UserServices#services} by scanning installed packages for
420     * given {@link UserHandle}.
421     * @param changedUids the array of uids that have been affected, as mentioned in the broadcast
422     *                    or null to assume that everything is affected.
423     * @param userId the user for whom to update the services map.
424     */
425    private void generateServicesMap(int[] changedUids, int userId) {
426        if (DEBUG) {
427            Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = "
428                    + Arrays.toString(changedUids));
429        }
430
431        final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
432        final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
433        for (ResolveInfo resolveInfo : resolveInfos) {
434            try {
435                ServiceInfo<V> info = parseServiceInfo(resolveInfo);
436                if (info == null) {
437                    Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
438                    continue;
439                }
440                serviceInfos.add(info);
441            } catch (XmlPullParserException|IOException e) {
442                Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
443            }
444        }
445
446        synchronized (mServicesLock) {
447            final UserServices<V> user = findOrCreateUserLocked(userId);
448            final boolean firstScan = user.services == null;
449            if (firstScan) {
450                user.services = Maps.newHashMap();
451            }
452
453            StringBuilder changes = new StringBuilder();
454            boolean changed = false;
455            for (ServiceInfo<V> info : serviceInfos) {
456                // four cases:
457                // - doesn't exist yet
458                //   - add, notify user that it was added
459                // - exists and the UID is the same
460                //   - replace, don't notify user
461                // - exists, the UID is different, and the new one is not a system package
462                //   - ignore
463                // - exists, the UID is different, and the new one is a system package
464                //   - add, notify user that it was added
465                Integer previousUid = user.persistentServices.get(info.type);
466                if (previousUid == null) {
467                    if (DEBUG) {
468                        changes.append("  New service added: ").append(info).append("\n");
469                    }
470                    changed = true;
471                    user.services.put(info.type, info);
472                    user.persistentServices.put(info.type, info.uid);
473                    if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
474                        notifyListener(info.type, userId, false /* removed */);
475                    }
476                } else if (previousUid == info.uid) {
477                    if (DEBUG) {
478                        changes.append("  Existing service (nop): ").append(info).append("\n");
479                    }
480                    user.services.put(info.type, info);
481                } else if (inSystemImage(info.uid)
482                        || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
483                    if (DEBUG) {
484                        if (inSystemImage(info.uid)) {
485                            changes.append("  System service replacing existing: ").append(info)
486                                    .append("\n");
487                        } else {
488                            changes.append("  Existing service replacing a removed service: ")
489                                    .append(info).append("\n");
490                        }
491                    }
492                    changed = true;
493                    user.services.put(info.type, info);
494                    user.persistentServices.put(info.type, info.uid);
495                    notifyListener(info.type, userId, false /* removed */);
496                } else {
497                    // ignore
498                    if (DEBUG) {
499                        changes.append("  Existing service with new uid ignored: ").append(info)
500                                .append("\n");
501                    }
502                }
503            }
504
505            ArrayList<V> toBeRemoved = Lists.newArrayList();
506            for (V v1 : user.persistentServices.keySet()) {
507                // Remove a persisted service that's not in the currently available services list.
508                // And only if it is in the list of changedUids.
509                if (!containsType(serviceInfos, v1)
510                        && containsUid(changedUids, user.persistentServices.get(v1))) {
511                    toBeRemoved.add(v1);
512                }
513            }
514            for (V v1 : toBeRemoved) {
515                if (DEBUG) {
516                    changes.append("  Service removed: ").append(v1).append("\n");
517                }
518                changed = true;
519                user.persistentServices.remove(v1);
520                user.services.remove(v1);
521                notifyListener(v1, userId, true /* removed */);
522            }
523            if (DEBUG) {
524                Log.d(TAG, "user.services=");
525                for (V v : user.services.keySet()) {
526                    Log.d(TAG, "  " + v + " " + user.services.get(v));
527                }
528                Log.d(TAG, "user.persistentServices=");
529                for (V v : user.persistentServices.keySet()) {
530                    Log.d(TAG, "  " + v + " " + user.persistentServices.get(v));
531                }
532            }
533            if (DEBUG) {
534                if (changes.length() > 0) {
535                    Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
536                            serviceInfos.size() + " services:\n" + changes);
537                } else {
538                    Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
539                            serviceInfos.size() + " services unchanged");
540                }
541            }
542            if (changed) {
543                onServicesChangedLocked(userId);
544                writePersistentServicesLocked(user, userId);
545            }
546        }
547    }
548
549    protected void onServicesChangedLocked(int userId) {
550        // Feel free to override
551    }
552
553    /**
554     * Returns true if the list of changed uids is null (wildcard) or the specified uid
555     * is contained in the list of changed uids.
556     */
557    private boolean containsUid(int[] changedUids, int uid) {
558        return changedUids == null || ArrayUtils.contains(changedUids, uid);
559    }
560
561    private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
562        for (int i = 0, N = serviceInfos.size(); i < N; i++) {
563            if (serviceInfos.get(i).type.equals(type)) {
564                return true;
565            }
566        }
567
568        return false;
569    }
570
571    private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
572        for (int i = 0, N = serviceInfos.size(); i < N; i++) {
573            final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
574            if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
575                return true;
576            }
577        }
578
579        return false;
580    }
581
582    @VisibleForTesting
583    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
584            throws XmlPullParserException, IOException {
585        android.content.pm.ServiceInfo si = service.serviceInfo;
586        ComponentName componentName = new ComponentName(si.packageName, si.name);
587
588        PackageManager pm = mContext.getPackageManager();
589
590        XmlResourceParser parser = null;
591        try {
592            parser = si.loadXmlMetaData(pm, mMetaDataName);
593            if (parser == null) {
594                throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
595            }
596
597            AttributeSet attrs = Xml.asAttributeSet(parser);
598
599            int type;
600            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
601                    && type != XmlPullParser.START_TAG) {
602            }
603
604            String nodeName = parser.getName();
605            if (!mAttributesName.equals(nodeName)) {
606                throw new XmlPullParserException(
607                        "Meta-data does not start with " + mAttributesName +  " tag");
608            }
609
610            V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
611                    si.packageName, attrs);
612            if (v == null) {
613                return null;
614            }
615            final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
616            return new ServiceInfo<V>(v, serviceInfo, componentName);
617        } catch (NameNotFoundException e) {
618            throw new XmlPullParserException(
619                    "Unable to load resources for pacakge " + si.packageName);
620        } finally {
621            if (parser != null) parser.close();
622        }
623    }
624
625    /**
626     * Read all sync status back in to the initial engine state.
627     */
628    private void readPersistentServicesLocked(InputStream is)
629            throws XmlPullParserException, IOException {
630        XmlPullParser parser = Xml.newPullParser();
631        parser.setInput(is, StandardCharsets.UTF_8.name());
632        int eventType = parser.getEventType();
633        while (eventType != XmlPullParser.START_TAG
634                && eventType != XmlPullParser.END_DOCUMENT) {
635            eventType = parser.next();
636        }
637        String tagName = parser.getName();
638        if ("services".equals(tagName)) {
639            eventType = parser.next();
640            do {
641                if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
642                    tagName = parser.getName();
643                    if ("service".equals(tagName)) {
644                        V service = mSerializerAndParser.createFromXml(parser);
645                        if (service == null) {
646                            break;
647                        }
648                        String uidString = parser.getAttributeValue(null, "uid");
649                        final int uid = Integer.parseInt(uidString);
650                        final int userId = UserHandle.getUserId(uid);
651                        final UserServices<V> user = findOrCreateUserLocked(userId,
652                                false /*loadFromFileIfNew*/) ;
653                        user.persistentServices.put(service, uid);
654                    }
655                }
656                eventType = parser.next();
657            } while (eventType != XmlPullParser.END_DOCUMENT);
658        }
659    }
660
661    private void migrateIfNecessaryLocked() {
662        if (mSerializerAndParser == null) {
663            return;
664        }
665        File systemDir = new File(getDataDirectory(), "system");
666        File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR);
667        AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml"));
668        boolean oldFileExists = oldFile.getBaseFile().exists();
669
670        if (oldFileExists) {
671            File marker = new File(syncDir, mInterfaceName + ".xml.migrated");
672            // if not migrated, perform the migration and add a marker
673            if (!marker.exists()) {
674                if (DEBUG) {
675                    Slog.i(TAG, "Marker file " + marker + " does not exist - running migration");
676                }
677                InputStream is = null;
678                try {
679                    is = oldFile.openRead();
680                    mUserServices.clear();
681                    readPersistentServicesLocked(is);
682                } catch (Exception e) {
683                    Log.w(TAG, "Error reading persistent services, starting from scratch", e);
684                } finally {
685                    IoUtils.closeQuietly(is);
686                }
687                try {
688                    for (UserInfo user : getUsers()) {
689                        UserServices<V> userServices = mUserServices.get(user.id);
690                        if (userServices != null) {
691                            if (DEBUG) {
692                                Slog.i(TAG, "Migrating u" + user.id + " services "
693                                        + userServices.persistentServices);
694                            }
695                            writePersistentServicesLocked(userServices, user.id);
696                        }
697                    }
698                    marker.createNewFile();
699                } catch (Exception e) {
700                    Log.w(TAG, "Migration failed", e);
701                }
702                // Migration is complete and we don't need to keep data for all users anymore,
703                // It will be loaded from a new location when requested
704                mUserServices.clear();
705            }
706        }
707    }
708
709    /**
710     * Writes services of a specified user to the file.
711     */
712    private void writePersistentServicesLocked(UserServices<V> user, int userId) {
713        if (mSerializerAndParser == null) {
714            return;
715        }
716        AtomicFile atomicFile = createFileForUser(userId);
717        FileOutputStream fos = null;
718        try {
719            fos = atomicFile.startWrite();
720            XmlSerializer out = new FastXmlSerializer();
721            out.setOutput(fos, StandardCharsets.UTF_8.name());
722            out.startDocument(null, true);
723            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
724            out.startTag(null, "services");
725            for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
726                out.startTag(null, "service");
727                out.attribute(null, "uid", Integer.toString(service.getValue()));
728                mSerializerAndParser.writeAsXml(service.getKey(), out);
729                out.endTag(null, "service");
730            }
731            out.endTag(null, "services");
732            out.endDocument();
733            atomicFile.finishWrite(fos);
734        } catch (IOException e1) {
735            Log.w(TAG, "Error writing accounts", e1);
736            if (fos != null) {
737                atomicFile.failWrite(fos);
738            }
739        }
740    }
741
742    @VisibleForTesting
743    protected void onUserRemoved(int userId) {
744        synchronized (mServicesLock) {
745            mUserServices.remove(userId);
746        }
747    }
748
749    @VisibleForTesting
750    protected List<UserInfo> getUsers() {
751        return UserManager.get(mContext).getUsers(true);
752    }
753
754    @VisibleForTesting
755    protected UserInfo getUser(int userId) {
756        return UserManager.get(mContext).getUserInfo(userId);
757    }
758
759    private AtomicFile createFileForUser(int userId) {
760        File userDir = getUserSystemDirectory(userId);
761        File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
762        return new AtomicFile(userFile);
763    }
764
765    @VisibleForTesting
766    protected File getUserSystemDirectory(int userId) {
767        return Environment.getUserSystemDirectory(userId);
768    }
769
770    @VisibleForTesting
771    protected File getDataDirectory() {
772        return Environment.getDataDirectory();
773    }
774
775    @VisibleForTesting
776    protected Map<V, Integer> getPersistentServices(int userId) {
777        return findOrCreateUserLocked(userId).persistentServices;
778    }
779
780    public abstract V parseServiceAttributes(Resources res,
781            String packageName, AttributeSet attrs);
782}
783