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