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