blob: 7362394ee180d5847bc30ec0d4ec304e05491e3c [file] [log] [blame]
Fred Quintana718d8a22009-04-29 17:53:20 -07001/*
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;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080025import android.os.Environment;
26import android.os.Handler;
Fred Quintana718d8a22009-04-29 17:53:20 -070027import android.util.Log;
28import android.util.AttributeSet;
29import android.util.Xml;
30
31import java.util.Map;
32import java.util.Collection;
33import java.util.Collections;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080034import java.util.HashMap;
Fred Quintana718d8a22009-04-29 17:53:20 -070035import java.util.List;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080036import java.util.ArrayList;
37import java.util.concurrent.atomic.AtomicReference;
38import java.io.File;
39import java.io.FileOutputStream;
Fred Quintana718d8a22009-04-29 17:53:20 -070040import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.io.IOException;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080043import java.io.FileInputStream;
44
45import com.android.internal.os.AtomicFile;
Tom Taylord4a47292009-12-21 13:59:18 -080046import com.android.common.FastXmlSerializer;
Fred Quintana718d8a22009-04-29 17:53:20 -070047
48import com.google.android.collect.Maps;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080049import com.google.android.collect.Lists;
50
Fred Quintana718d8a22009-04-29 17:53:20 -070051import org.xmlpull.v1.XmlPullParserException;
52import org.xmlpull.v1.XmlPullParser;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080053import org.xmlpull.v1.XmlSerializer;
Fred Quintana718d8a22009-04-29 17:53:20 -070054
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;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080069 private final XmlSerializerAndParser<V> mSerializerAndParser;
70 private final AtomicReference<BroadcastReceiver> mReceiver;
Fred Quintana718d8a22009-04-29 17:53:20 -070071
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080072 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;
Fred Quintana3ecd5f42009-09-17 12:42:35 -070079
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080080 /**
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;
Fred Quintana3ecd5f42009-09-17 12:42:35 -070085
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080086 // the listener and handler are synchronized on "this" and must be updated together
87 private RegisteredServicesCacheListener<V> mListener;
88 private Handler mHandler;
Fred Quintana718d8a22009-04-29 17:53:20 -070089
90 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080091 String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
Fred Quintana718d8a22009-04-29 17:53:20 -070092 mContext = context;
93 mInterfaceName = interfaceName;
94 mMetaDataName = metaDataName;
95 mAttributesName = attributeName;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080096 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);
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800118 // Register for events related to sdcard installation.
119 IntentFilter sdFilter = new IntentFilter();
120 sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_AVAILABLE);
121 sdFilter.addAction(Intent.ACTION_MEDIA_RESOURCES_UNAVAILABLE);
122 mContext.registerReceiver(receiver, sdFilter);
Fred Quintana718d8a22009-04-29 17:53:20 -0700123 }
124
125 public void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800126 Map<V, ServiceInfo<V>> services;
127 synchronized (mServicesLock) {
128 services = mServices;
129 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700130 fout.println("RegisteredServicesCache: " + services.size() + " services");
131 for (ServiceInfo info : services.values()) {
132 fout.println(" " + info);
133 }
134 }
135
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800136 public RegisteredServicesCacheListener<V> getListener() {
Fred Quintana718d8a22009-04-29 17:53:20 -0700137 synchronized (this) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800138 return mListener;
Fred Quintana718d8a22009-04-29 17:53:20 -0700139 }
140 }
141
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800142 public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
143 if (handler == null) {
144 handler = new Handler(mContext.getMainLooper());
Fred Quintana718d8a22009-04-29 17:53:20 -0700145 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800146 synchronized (this) {
147 mHandler = handler;
148 mListener = listener;
149 }
150 }
151
152 private void notifyListener(final V type, final boolean removed) {
153 if (Log.isLoggable(TAG, Log.VERBOSE)) {
154 Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
155 }
156 RegisteredServicesCacheListener<V> listener;
157 Handler handler;
158 synchronized (this) {
159 listener = mListener;
160 handler = mHandler;
161 }
162 if (listener == null) {
163 return;
164 }
165
166 final RegisteredServicesCacheListener<V> listener2 = listener;
167 handler.post(new Runnable() {
168 public void run() {
169 listener2.onServiceChanged(type, removed);
170 }
171 });
Fred Quintana718d8a22009-04-29 17:53:20 -0700172 }
173
174 /**
175 * Value type that describes a Service. The information within can be used
176 * to bind to the service.
177 */
178 public static class ServiceInfo<V> {
179 public final V type;
180 public final ComponentName componentName;
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700181 public final int uid;
Fred Quintana718d8a22009-04-29 17:53:20 -0700182
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700183 private ServiceInfo(V type, ComponentName componentName, int uid) {
Fred Quintana718d8a22009-04-29 17:53:20 -0700184 this.type = type;
185 this.componentName = componentName;
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700186 this.uid = uid;
Fred Quintana718d8a22009-04-29 17:53:20 -0700187 }
188
Jeff Hamiltonc42c0dd2009-09-03 09:08:30 -0500189 @Override
Fred Quintana718d8a22009-04-29 17:53:20 -0700190 public String toString() {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800191 return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
Fred Quintana718d8a22009-04-29 17:53:20 -0700192 }
193 }
194
195 /**
196 * Accessor for the registered authenticators.
197 * @param type the account type of the authenticator
198 * @return the AuthenticatorInfo that matches the account type or null if none is present
199 */
Fred Quintana97889762009-06-15 12:29:24 -0700200 public ServiceInfo<V> getServiceInfo(V type) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800201 synchronized (mServicesLock) {
202 return mServices.get(type);
Fred Quintana718d8a22009-04-29 17:53:20 -0700203 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700204 }
205
206 /**
207 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
208 * registered authenticators.
209 */
210 public Collection<ServiceInfo<V>> getAllServices() {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800211 synchronized (mServicesLock) {
212 return Collections.unmodifiableCollection(mServices.values());
Fred Quintana718d8a22009-04-29 17:53:20 -0700213 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700214 }
215
216 /**
217 * Stops the monitoring of package additions, removals and changes.
218 */
219 public void close() {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800220 final BroadcastReceiver receiver = mReceiver.getAndSet(null);
221 if (receiver != null) {
222 mContext.unregisterReceiver(receiver);
223 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700224 }
225
Jeff Hamiltonc42c0dd2009-09-03 09:08:30 -0500226 @Override
Fred Quintana718d8a22009-04-29 17:53:20 -0700227 protected void finalize() throws Throwable {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800228 if (mReceiver.get() != null) {
229 Log.e(TAG, "RegisteredServicesCache finalized without being closed");
Fred Quintana718d8a22009-04-29 17:53:20 -0700230 }
231 close();
232 super.finalize();
233 }
234
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800235 private boolean inSystemImage(int callerUid) {
236 String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
237 for (String name : packages) {
238 try {
239 PackageInfo packageInfo =
240 mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
241 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
242 return true;
243 }
244 } catch (PackageManager.NameNotFoundException e) {
245 return false;
246 }
247 }
248 return false;
249 }
250
251 void generateServicesMap() {
Fred Quintana718d8a22009-04-29 17:53:20 -0700252 PackageManager pm = mContext.getPackageManager();
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800253 ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
254 List<ResolveInfo> resolveInfos = pm.queryIntentServices(new Intent(mInterfaceName),
255 PackageManager.GET_META_DATA);
Fred Quintana718d8a22009-04-29 17:53:20 -0700256 for (ResolveInfo resolveInfo : resolveInfos) {
257 try {
258 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800259 if (info == null) {
260 Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
261 continue;
Fred Quintana718d8a22009-04-29 17:53:20 -0700262 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800263 serviceInfos.add(info);
Fred Quintana718d8a22009-04-29 17:53:20 -0700264 } catch (XmlPullParserException e) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800265 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
Fred Quintana718d8a22009-04-29 17:53:20 -0700266 } catch (IOException e) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800267 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
Fred Quintana718d8a22009-04-29 17:53:20 -0700268 }
269 }
270
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800271 synchronized (mServicesLock) {
272 if (Log.isLoggable(TAG, Log.VERBOSE)) {
273 Log.d(TAG, "generateServicesMap: " + mInterfaceName);
274 }
275 if (mPersistentServices == null) {
276 readPersistentServicesLocked();
277 }
278 mServices = Maps.newHashMap();
279 boolean changed = false;
280 if (Log.isLoggable(TAG, Log.VERBOSE)) {
281 Log.d(TAG, "found " + serviceInfos.size() + " services");
282 }
283 for (ServiceInfo<V> info : serviceInfos) {
284 // four cases:
285 // - doesn't exist yet
286 // - add, notify user that it was added
287 // - exists and the UID is the same
288 // - replace, don't notify user
289 // - exists, the UID is different, and the new one is not a system package
290 // - ignore
291 // - exists, the UID is different, and the new one is a system package
292 // - add, notify user that it was added
293 Integer previousUid = mPersistentServices.get(info.type);
294 if (previousUid == null) {
295 if (Log.isLoggable(TAG, Log.VERBOSE)) {
296 Log.d(TAG, "encountered new type: " + info);
297 }
298 changed = true;
299 mServices.put(info.type, info);
300 mPersistentServices.put(info.type, info.uid);
301 if (!mPersistentServicesFileDidNotExist) {
302 notifyListener(info.type, false /* removed */);
303 }
304 } else if (previousUid == info.uid) {
305 if (Log.isLoggable(TAG, Log.VERBOSE)) {
306 Log.d(TAG, "encountered existing type with the same uid: " + info);
307 }
308 mServices.put(info.type, info);
309 } else if (inSystemImage(info.uid)
310 || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
311 if (Log.isLoggable(TAG, Log.VERBOSE)) {
312 if (inSystemImage(info.uid)) {
313 Log.d(TAG, "encountered existing type with a new uid but from"
314 + " the system: " + info);
315 } else {
316 Log.d(TAG, "encountered existing type with a new uid but existing was"
317 + " removed: " + info);
318 }
319 }
320 changed = true;
321 mServices.put(info.type, info);
322 mPersistentServices.put(info.type, info.uid);
323 notifyListener(info.type, false /* removed */);
324 } else {
325 // ignore
326 if (Log.isLoggable(TAG, Log.VERBOSE)) {
327 Log.d(TAG, "encountered existing type with a new uid, ignoring: " + info);
328 }
329 }
330 }
331
332 ArrayList<V> toBeRemoved = Lists.newArrayList();
333 for (V v1 : mPersistentServices.keySet()) {
334 if (!containsType(serviceInfos, v1)) {
335 toBeRemoved.add(v1);
336 }
337 }
338 for (V v1 : toBeRemoved) {
339 mPersistentServices.remove(v1);
340 changed = true;
341 notifyListener(v1, true /* removed */);
342 }
343 if (changed) {
344 if (Log.isLoggable(TAG, Log.VERBOSE)) {
345 Log.d(TAG, "writing updated list of persistent services");
346 }
347 writePersistentServicesLocked();
348 } else {
349 if (Log.isLoggable(TAG, Log.VERBOSE)) {
350 Log.d(TAG, "persistent services did not change, so not writing anything");
351 }
352 }
353 mPersistentServicesFileDidNotExist = false;
354 }
355 }
356
357 private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
358 for (int i = 0, N = serviceInfos.size(); i < N; i++) {
359 if (serviceInfos.get(i).type.equals(type)) {
360 return true;
361 }
362 }
363
364 return false;
365 }
366
367 private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
368 for (int i = 0, N = serviceInfos.size(); i < N; i++) {
369 final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
370 if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
371 return true;
372 }
373 }
374
375 return false;
Fred Quintana718d8a22009-04-29 17:53:20 -0700376 }
377
378 private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
379 throws XmlPullParserException, IOException {
380 android.content.pm.ServiceInfo si = service.serviceInfo;
381 ComponentName componentName = new ComponentName(si.packageName, si.name);
382
383 PackageManager pm = mContext.getPackageManager();
384
385 XmlResourceParser parser = null;
386 try {
387 parser = si.loadXmlMetaData(pm, mMetaDataName);
388 if (parser == null) {
389 throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
390 }
391
392 AttributeSet attrs = Xml.asAttributeSet(parser);
393
394 int type;
395 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
396 && type != XmlPullParser.START_TAG) {
397 }
398
399 String nodeName = parser.getName();
400 if (!mAttributesName.equals(nodeName)) {
401 throw new XmlPullParserException(
402 "Meta-data does not start with " + mAttributesName + " tag");
403 }
404
Fred Quintana97889762009-06-15 12:29:24 -0700405 V v = parseServiceAttributes(si.packageName, attrs);
Fred Quintana718d8a22009-04-29 17:53:20 -0700406 if (v == null) {
407 return null;
408 }
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700409 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
410 final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
411 final int uid = applicationInfo.uid;
412 return new ServiceInfo<V>(v, componentName, uid);
Fred Quintana718d8a22009-04-29 17:53:20 -0700413 } finally {
414 if (parser != null) parser.close();
415 }
416 }
417
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800418 /**
419 * Read all sync status back in to the initial engine state.
420 */
421 private void readPersistentServicesLocked() {
422 mPersistentServices = Maps.newHashMap();
423 if (mSerializerAndParser == null) {
424 return;
425 }
426 FileInputStream fis = null;
427 try {
428 mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
429 if (mPersistentServicesFileDidNotExist) {
430 return;
431 }
432 fis = mPersistentServicesFile.openRead();
433 XmlPullParser parser = Xml.newPullParser();
434 parser.setInput(fis, null);
435 int eventType = parser.getEventType();
436 while (eventType != XmlPullParser.START_TAG) {
437 eventType = parser.next();
438 }
439 String tagName = parser.getName();
440 if ("services".equals(tagName)) {
441 eventType = parser.next();
442 do {
443 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
444 tagName = parser.getName();
445 if ("service".equals(tagName)) {
446 V service = mSerializerAndParser.createFromXml(parser);
447 if (service == null) {
448 break;
449 }
450 String uidString = parser.getAttributeValue(null, "uid");
451 int uid = Integer.parseInt(uidString);
452 mPersistentServices.put(service, uid);
453 }
454 }
455 eventType = parser.next();
456 } while (eventType != XmlPullParser.END_DOCUMENT);
457 }
458 } catch (Exception e) {
459 Log.w(TAG, "Error reading persistent services, starting from scratch", e);
460 } finally {
461 if (fis != null) {
462 try {
463 fis.close();
464 } catch (java.io.IOException e1) {
465 }
466 }
467 }
468 }
469
470 /**
471 * Write all sync status to the sync status file.
472 */
473 private void writePersistentServicesLocked() {
474 if (mSerializerAndParser == null) {
475 return;
476 }
477 FileOutputStream fos = null;
478 try {
479 fos = mPersistentServicesFile.startWrite();
480 XmlSerializer out = new FastXmlSerializer();
481 out.setOutput(fos, "utf-8");
482 out.startDocument(null, true);
483 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
484 out.startTag(null, "services");
485 for (Map.Entry<V, Integer> service : mPersistentServices.entrySet()) {
486 out.startTag(null, "service");
487 out.attribute(null, "uid", Integer.toString(service.getValue()));
488 mSerializerAndParser.writeAsXml(service.getKey(), out);
489 out.endTag(null, "service");
490 }
491 out.endTag(null, "services");
492 out.endDocument();
493 mPersistentServicesFile.finishWrite(fos);
494 } catch (java.io.IOException e1) {
495 Log.w(TAG, "Error writing accounts", e1);
496 if (fos != null) {
497 mPersistentServicesFile.failWrite(fos);
498 }
499 }
500 }
501
Fred Quintana97889762009-06-15 12:29:24 -0700502 public abstract V parseServiceAttributes(String packageName, AttributeSet attrs);
Fred Quintana718d8a22009-04-29 17:53:20 -0700503}