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