RegisteredServicesCache.java revision d4a1d2e14297a3387fdb5761090961e714370492
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.util.Log;
26import android.util.AttributeSet;
27import android.util.Xml;
28
29import java.util.Map;
30import java.util.Collection;
31import java.util.Collections;
32import java.util.List;
33import java.io.FileDescriptor;
34import java.io.PrintWriter;
35import java.io.IOException;
36
37import com.google.android.collect.Maps;
38import org.xmlpull.v1.XmlPullParserException;
39import org.xmlpull.v1.XmlPullParser;
40
41/**
42 * A cache of registered services. This cache
43 * is built by interrogating the {@link PackageManager} and is updated as packages are added,
44 * removed and changed. The services are referred to by type V and
45 * are made available via the {@link #getServiceInfo} method.
46 * @hide
47 */
48public abstract class RegisteredServicesCache<V> {
49    private static final String TAG = "PackageManager";
50
51    public final Context mContext;
52    private final String mInterfaceName;
53    private final String mMetaDataName;
54    private final String mAttributesName;
55
56    // no need to be synchronized since the map is never changed once mService is written
57    private volatile Map<V, ServiceInfo<V>> mServices;
58
59    // synchronized on "this"
60    private BroadcastReceiver mReceiver = null;
61
62    public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
63            String attributeName) {
64        mContext = context;
65        mInterfaceName = interfaceName;
66        mMetaDataName = metaDataName;
67        mAttributesName = attributeName;
68    }
69
70    public void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
71        getAllServices();
72        Map<V, ServiceInfo<V>> services = mServices;
73        fout.println("RegisteredServicesCache: " + services.size() + " services");
74        for (ServiceInfo info : services.values()) {
75            fout.println("  " + info);
76        }
77    }
78
79    private boolean maybeRegisterForPackageChanges() {
80        synchronized (this) {
81            if (mReceiver == null) {
82                synchronized (this) {
83                    mReceiver = new BroadcastReceiver() {
84                        public void onReceive(Context context, Intent intent) {
85                            mServices = generateServicesMap();
86                        }
87                    };
88                }
89
90                IntentFilter intentFilter = new IntentFilter();
91                intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
92                intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
93                intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
94                mContext.registerReceiver(mReceiver, intentFilter);
95                return true;
96            }
97            return false;
98        }
99    }
100
101    private void maybeUnregisterForPackageChanges() {
102        synchronized (this) {
103            if (mReceiver != null) {
104                mContext.unregisterReceiver(mReceiver);
105                mReceiver = null;
106            }
107        }
108    }
109
110    /**
111     * Value type that describes a Service. The information within can be used
112     * to bind to the service.
113     */
114    public static class ServiceInfo<V> {
115        public final V type;
116        public final ComponentName componentName;
117        public final int uid;
118
119        private ServiceInfo(V type, ComponentName componentName, int uid) {
120            this.type = type;
121            this.componentName = componentName;
122            this.uid = uid;
123        }
124
125        public String toString() {
126            return "ServiceInfo: " + type + ", " + componentName;
127        }
128    }
129
130    /**
131     * Accessor for the registered authenticators.
132     * @param type the account type of the authenticator
133     * @return the AuthenticatorInfo that matches the account type or null if none is present
134     */
135    public ServiceInfo<V> getServiceInfo(V type) {
136        if (mServices == null) {
137            maybeRegisterForPackageChanges();
138            mServices = generateServicesMap();
139        }
140        return mServices.get(type);
141    }
142
143    /**
144     * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
145     * registered authenticators.
146     */
147    public Collection<ServiceInfo<V>> getAllServices() {
148        if (mServices == null) {
149            maybeRegisterForPackageChanges();
150            mServices = generateServicesMap();
151        }
152        return Collections.unmodifiableCollection(mServices.values());
153    }
154
155    /**
156     * Stops the monitoring of package additions, removals and changes.
157     */
158    public void close() {
159        maybeUnregisterForPackageChanges();
160    }
161
162    protected void finalize() throws Throwable {
163        synchronized (this) {
164            if (mReceiver != null) {
165                Log.e(TAG, "RegisteredServicesCache finalized without being closed");
166            }
167        }
168        close();
169        super.finalize();
170    }
171
172    private Map<V, ServiceInfo<V>> generateServicesMap() {
173        Map<V, ServiceInfo<V>> services = Maps.newHashMap();
174        PackageManager pm = mContext.getPackageManager();
175
176        List<ResolveInfo> resolveInfos =
177                pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA);
178
179        for (ResolveInfo resolveInfo : resolveInfos) {
180            try {
181                ServiceInfo<V> info = parseServiceInfo(resolveInfo);
182                if (info != null) {
183                    services.put(info.type, info);
184                } else {
185                    Log.w(TAG, "Unable to load input method " + resolveInfo.toString());
186                }
187            } catch (XmlPullParserException e) {
188                Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
189            } catch (IOException e) {
190                Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
191            }
192        }
193
194        return services;
195    }
196
197    private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
198            throws XmlPullParserException, IOException {
199        android.content.pm.ServiceInfo si = service.serviceInfo;
200        ComponentName componentName = new ComponentName(si.packageName, si.name);
201
202        PackageManager pm = mContext.getPackageManager();
203
204        XmlResourceParser parser = null;
205        try {
206            parser = si.loadXmlMetaData(pm, mMetaDataName);
207            if (parser == null) {
208                throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
209            }
210
211            AttributeSet attrs = Xml.asAttributeSet(parser);
212
213            int type;
214            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
215                    && type != XmlPullParser.START_TAG) {
216            }
217
218            String nodeName = parser.getName();
219            if (!mAttributesName.equals(nodeName)) {
220                throw new XmlPullParserException(
221                        "Meta-data does not start with " + mAttributesName +  " tag");
222            }
223
224            V v = parseServiceAttributes(si.packageName, attrs);
225            if (v == null) {
226                return null;
227            }
228            final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
229            final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
230            final int uid = applicationInfo.uid;
231            return new ServiceInfo<V>(v, componentName, uid);
232        } finally {
233            if (parser != null) parser.close();
234        }
235    }
236
237    public abstract V parseServiceAttributes(String packageName, AttributeSet attrs);
238}
239