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