RegisteredServicesCache.java revision 9788976b1465ce982b5ae7c741345edd0ecd9322
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
118        private ServiceInfo(V type, ComponentName componentName) {
119            this.type = type;
120            this.componentName = componentName;
121        }
122
123        public String toString() {
124            return "ServiceInfo: " + type + ", " + componentName;
125        }
126    }
127
128    /**
129     * Accessor for the registered authenticators.
130     * @param type the account type of the authenticator
131     * @return the AuthenticatorInfo that matches the account type or null if none is present
132     */
133    public ServiceInfo<V> getServiceInfo(V type) {
134        if (mServices == null) {
135            maybeRegisterForPackageChanges();
136            mServices = generateServicesMap();
137        }
138        return mServices.get(type);
139    }
140
141    /**
142     * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
143     * registered authenticators.
144     */
145    public Collection<ServiceInfo<V>> getAllServices() {
146        if (mServices == null) {
147            maybeRegisterForPackageChanges();
148            mServices = generateServicesMap();
149        }
150        return Collections.unmodifiableCollection(mServices.values());
151    }
152
153    /**
154     * Stops the monitoring of package additions, removals and changes.
155     */
156    public void close() {
157        maybeUnregisterForPackageChanges();
158    }
159
160    protected void finalize() throws Throwable {
161        synchronized (this) {
162            if (mReceiver != null) {
163                Log.e(TAG, "RegisteredServicesCache finalized without being closed");
164            }
165        }
166        close();
167        super.finalize();
168    }
169
170    private Map<V, ServiceInfo<V>> generateServicesMap() {
171        Map<V, ServiceInfo<V>> services = Maps.newHashMap();
172        PackageManager pm = mContext.getPackageManager();
173
174        List<ResolveInfo> resolveInfos =
175                pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA);
176
177        for (ResolveInfo resolveInfo : resolveInfos) {
178            try {
179                ServiceInfo<V> info = parseServiceInfo(resolveInfo);
180                if (info != null) {
181                    services.put(info.type, info);
182                } else {
183                    Log.w(TAG, "Unable to load input method " + resolveInfo.toString());
184                }
185            } catch (XmlPullParserException e) {
186                Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
187            } catch (IOException e) {
188                Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
189            }
190        }
191
192        return services;
193    }
194
195    private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
196            throws XmlPullParserException, IOException {
197        android.content.pm.ServiceInfo si = service.serviceInfo;
198        ComponentName componentName = new ComponentName(si.packageName, si.name);
199
200        PackageManager pm = mContext.getPackageManager();
201
202        XmlResourceParser parser = null;
203        try {
204            parser = si.loadXmlMetaData(pm, mMetaDataName);
205            if (parser == null) {
206                throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
207            }
208
209            AttributeSet attrs = Xml.asAttributeSet(parser);
210
211            int type;
212            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
213                    && type != XmlPullParser.START_TAG) {
214            }
215
216            String nodeName = parser.getName();
217            if (!mAttributesName.equals(nodeName)) {
218                throw new XmlPullParserException(
219                        "Meta-data does not start with " + mAttributesName +  " tag");
220            }
221
222            V v = parseServiceAttributes(si.packageName, attrs);
223            if (v == null) {
224                return null;
225            }
226            return new ServiceInfo<V>(v, componentName);
227        } finally {
228            if (parser != null) parser.close();
229        }
230    }
231
232    public abstract V parseServiceAttributes(String packageName, AttributeSet attrs);
233}
234