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