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