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