OSDN Git Service

4a743a50261f7506b700d369b6521a14542d3822
[android-x86/frameworks-base.git] / core / java / android / content / pm / RegisteredServicesCache.java
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
17 package android.content.pm;
18
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.content.res.Resources;
26 import android.content.res.XmlResourceParser;
27 import android.os.Environment;
28 import android.os.Handler;
29 import android.os.UserHandle;
30 import android.util.AtomicFile;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.util.Slog;
34 import android.util.SparseArray;
35 import android.util.Xml;
36
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.util.FastXmlSerializer;
39 import com.google.android.collect.Lists;
40 import com.google.android.collect.Maps;
41
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 import org.xmlpull.v1.XmlSerializer;
45
46 import java.io.File;
47 import java.io.FileDescriptor;
48 import java.io.FileInputStream;
49 import java.io.FileOutputStream;
50 import java.io.IOException;
51 import java.io.PrintWriter;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.List;
56 import java.util.Map;
57
58 /**
59  * Cache of registered services. This cache is lazily built by interrogating
60  * {@link PackageManager} on a per-user basis. It's updated as packages are
61  * added, removed and changed. Users are responsible for calling
62  * {@link #invalidateCache(int)} when a user is started, since
63  * {@link PackageManager} broadcasts aren't sent for stopped users.
64  * <p>
65  * The services are referred to by type V and are made available via the
66  * {@link #getServiceInfo} method.
67  * 
68  * @hide
69  */
70 public abstract class RegisteredServicesCache<V> {
71     private static final String TAG = "PackageManager";
72     private static final boolean DEBUG = false;
73
74     public final Context mContext;
75     private final String mInterfaceName;
76     private final String mMetaDataName;
77     private final String mAttributesName;
78     private final XmlSerializerAndParser<V> mSerializerAndParser;
79
80     private final Object mServicesLock = new Object();
81
82     @GuardedBy("mServicesLock")
83     private boolean mPersistentServicesFileDidNotExist;
84     @GuardedBy("mServicesLock")
85     private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
86
87     private static class UserServices<V> {
88         @GuardedBy("mServicesLock")
89         public final Map<V, Integer> persistentServices = Maps.newHashMap();
90         @GuardedBy("mServicesLock")
91         public Map<V, ServiceInfo<V>> services = null;
92     }
93
94     private UserServices<V> findOrCreateUserLocked(int userId) {
95         UserServices<V> services = mUserServices.get(userId);
96         if (services == null) {
97             services = new UserServices<V>();
98             mUserServices.put(userId, services);
99         }
100         return services;
101     }
102
103     /**
104      * This file contains the list of known services. We would like to maintain this forever
105      * so we store it as an XML file.
106      */
107     private final AtomicFile mPersistentServicesFile;
108
109     // the listener and handler are synchronized on "this" and must be updated together
110     private RegisteredServicesCacheListener<V> mListener;
111     private Handler mHandler;
112
113     public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
114             String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
115         mContext = context;
116         mInterfaceName = interfaceName;
117         mMetaDataName = metaDataName;
118         mAttributesName = attributeName;
119         mSerializerAndParser = serializerAndParser;
120
121         File dataDir = Environment.getDataDirectory();
122         File systemDir = new File(dataDir, "system");
123         File syncDir = new File(systemDir, "registered_services");
124         mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
125
126         // Load persisted services from disk
127         readPersistentServicesLocked();
128
129         IntentFilter intentFilter = new IntentFilter();
130         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
131         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
132         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
133         intentFilter.addDataScheme("package");
134         mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null);
135
136         // Register for events related to sdcard installation.
137         IntentFilter sdFilter = new IntentFilter();
138         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
139         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
140         mContext.registerReceiver(mExternalReceiver, sdFilter);
141     }
142
143     private final void handlePackageEvent(Intent intent, int userId) {
144         // Don't regenerate the services map when the package is removed or its
145         // ASEC container unmounted as a step in replacement.  The subsequent
146         // _ADDED / _AVAILABLE call will regenerate the map in the final state.
147         final String action = intent.getAction();
148         // it's a new-component action if it isn't some sort of removal
149         final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action)
150                 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action);
151         // if it's a removal, is it part of an update-in-place step?
152         final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
153
154         if (isRemoval && replacing) {
155             // package is going away, but it's the middle of an upgrade: keep the current
156             // state and do nothing here.  This clause is intentionally empty.
157         } else {
158             // either we're adding/changing, or it's a removal without replacement, so
159             // we need to recalculate the set of available services
160             generateServicesMap(userId);
161         }
162     }
163
164     private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
165         @Override
166         public void onReceive(Context context, Intent intent) {
167             final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
168             if (uid != -1) {
169                 handlePackageEvent(intent, UserHandle.getUserId(uid));
170             }
171         }
172     };
173
174     private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() {
175         @Override
176         public void onReceive(Context context, Intent intent) {
177             // External apps can't coexist with multi-user, so scan owner
178             handlePackageEvent(intent, UserHandle.USER_OWNER);
179         }
180     };
181
182     public void invalidateCache(int userId) {
183         synchronized (mServicesLock) {
184             final UserServices<V> user = findOrCreateUserLocked(userId);
185             user.services = null;
186         }
187     }
188
189     public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) {
190         synchronized (mServicesLock) {
191             final UserServices<V> user = findOrCreateUserLocked(userId);
192             if (user.services != null) {
193                 fout.println("RegisteredServicesCache: " + user.services.size() + " services");
194                 for (ServiceInfo<?> info : user.services.values()) {
195                     fout.println("  " + info);
196                 }
197             } else {
198                 fout.println("RegisteredServicesCache: services not loaded");
199             }
200         }
201     }
202
203     public RegisteredServicesCacheListener<V> getListener() {
204         synchronized (this) {
205             return mListener;
206         }
207     }
208
209     public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
210         if (handler == null) {
211             handler = new Handler(mContext.getMainLooper());
212         }
213         synchronized (this) {
214             mHandler = handler;
215             mListener = listener;
216         }
217     }
218
219     private void notifyListener(final V type, final int userId, final boolean removed) {
220         if (DEBUG) {
221             Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
222         }
223         RegisteredServicesCacheListener<V> listener;
224         Handler handler; 
225         synchronized (this) {
226             listener = mListener;
227             handler = mHandler;
228         }
229         if (listener == null) {
230             return;
231         }
232         
233         final RegisteredServicesCacheListener<V> listener2 = listener;
234         handler.post(new Runnable() {
235             public void run() {
236                 listener2.onServiceChanged(type, userId, removed);
237             }
238         });
239     }
240
241     /**
242      * Value type that describes a Service. The information within can be used
243      * to bind to the service.
244      */
245     public static class ServiceInfo<V> {
246         public final V type;
247         public final ComponentName componentName;
248         public final int uid;
249
250         /** @hide */
251         public ServiceInfo(V type, ComponentName componentName, int uid) {
252             this.type = type;
253             this.componentName = componentName;
254             this.uid = uid;
255         }
256
257         @Override
258         public String toString() {
259             return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
260         }
261     }
262
263     /**
264      * Accessor for the registered authenticators.
265      * @param type the account type of the authenticator
266      * @return the AuthenticatorInfo that matches the account type or null if none is present
267      */
268     public ServiceInfo<V> getServiceInfo(V type, int userId) {
269         synchronized (mServicesLock) {
270             // Find user and lazily populate cache
271             final UserServices<V> user = findOrCreateUserLocked(userId);
272             if (user.services == null) {
273                 generateServicesMap(userId);
274             }
275             return user.services.get(type);
276         }
277     }
278
279     /**
280      * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
281      * registered authenticators.
282      */
283     public Collection<ServiceInfo<V>> getAllServices(int userId) {
284         synchronized (mServicesLock) {
285             // Find user and lazily populate cache
286             final UserServices<V> user = findOrCreateUserLocked(userId);
287             if (user.services == null) {
288                 generateServicesMap(userId);
289             }
290             return Collections.unmodifiableCollection(
291                     new ArrayList<ServiceInfo<V>>(user.services.values()));
292         }
293     }
294
295     private boolean inSystemImage(int callerUid) {
296         String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
297         for (String name : packages) {
298             try {
299                 PackageInfo packageInfo =
300                         mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
301                 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
302                     return true;
303                 }
304             } catch (PackageManager.NameNotFoundException e) {
305                 return false;
306             }
307         }
308         return false;
309     }
310
311     /**
312      * Populate {@link UserServices#services} by scanning installed packages for
313      * given {@link UserHandle}.
314      */
315     private void generateServicesMap(int userId) {
316         if (DEBUG) {
317             Slog.d(TAG, "generateServicesMap() for " + userId);
318         }
319
320         final PackageManager pm = mContext.getPackageManager();
321         final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
322         final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(
323                 new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
324         for (ResolveInfo resolveInfo : resolveInfos) {
325             try {
326                 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
327                 if (info == null) {
328                     Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
329                     continue;
330                 }
331                 serviceInfos.add(info);
332             } catch (XmlPullParserException e) {
333                 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
334             } catch (IOException e) {
335                 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
336             }
337         }
338
339         synchronized (mServicesLock) {
340             final UserServices<V> user = findOrCreateUserLocked(userId);
341             final boolean firstScan = user.services == null;
342             if (firstScan) {
343                 user.services = Maps.newHashMap();
344             } else {
345                 user.services.clear();
346             }
347
348             StringBuilder changes = new StringBuilder();
349             boolean changed = false;
350             for (ServiceInfo<V> info : serviceInfos) {
351                 // four cases:
352                 // - doesn't exist yet
353                 //   - add, notify user that it was added
354                 // - exists and the UID is the same
355                 //   - replace, don't notify user
356                 // - exists, the UID is different, and the new one is not a system package
357                 //   - ignore
358                 // - exists, the UID is different, and the new one is a system package
359                 //   - add, notify user that it was added
360                 Integer previousUid = user.persistentServices.get(info.type);
361                 if (previousUid == null) {
362                     if (DEBUG) {
363                         changes.append("  New service added: ").append(info).append("\n");
364                     }
365                     changed = true;
366                     user.services.put(info.type, info);
367                     user.persistentServices.put(info.type, info.uid);
368                     if (!(mPersistentServicesFileDidNotExist && firstScan)) {
369                         notifyListener(info.type, userId, false /* removed */);
370                     }
371                 } else if (previousUid == info.uid) {
372                     if (DEBUG) {
373                         changes.append("  Existing service (nop): ").append(info).append("\n");
374                     }
375                     user.services.put(info.type, info);
376                 } else if (inSystemImage(info.uid)
377                         || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
378                     if (DEBUG) {
379                         if (inSystemImage(info.uid)) {
380                             changes.append("  System service replacing existing: ").append(info)
381                                     .append("\n");
382                         } else {
383                             changes.append("  Existing service replacing a removed service: ")
384                                     .append(info).append("\n");
385                         }
386                     }
387                     changed = true;
388                     user.services.put(info.type, info);
389                     user.persistentServices.put(info.type, info.uid);
390                     notifyListener(info.type, userId, false /* removed */);
391                 } else {
392                     // ignore
393                     if (DEBUG) {
394                         changes.append("  Existing service with new uid ignored: ").append(info)
395                                 .append("\n");
396                     }
397                 }
398             }
399
400             ArrayList<V> toBeRemoved = Lists.newArrayList();
401             for (V v1 : user.persistentServices.keySet()) {
402                 if (!containsType(serviceInfos, v1)) {
403                     toBeRemoved.add(v1);
404                 }
405             }
406             for (V v1 : toBeRemoved) {
407                 if (DEBUG) {
408                     changes.append("  Service removed: ").append(v1).append("\n");
409                 }
410                 changed = true;
411                 user.persistentServices.remove(v1);
412                 notifyListener(v1, userId, true /* removed */);
413             }
414             if (DEBUG) {
415                 if (changes.length() > 0) {
416                     Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
417                             serviceInfos.size() + " services:\n" + changes);
418                 } else {
419                     Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
420                             serviceInfos.size() + " services unchanged");
421                 }
422             }
423             if (changed) {
424                 writePersistentServicesLocked();
425             }
426         }
427     }
428
429     private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
430         for (int i = 0, N = serviceInfos.size(); i < N; i++) {
431             if (serviceInfos.get(i).type.equals(type)) {
432                 return true;
433             }
434         }
435
436         return false;
437     }
438
439     private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
440         for (int i = 0, N = serviceInfos.size(); i < N; i++) {
441             final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
442             if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
443                 return true;
444             }
445         }
446
447         return false;
448     }
449
450     private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
451             throws XmlPullParserException, IOException {
452         android.content.pm.ServiceInfo si = service.serviceInfo;
453         ComponentName componentName = new ComponentName(si.packageName, si.name);
454
455         PackageManager pm = mContext.getPackageManager();
456
457         XmlResourceParser parser = null;
458         try {
459             parser = si.loadXmlMetaData(pm, mMetaDataName);
460             if (parser == null) {
461                 throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
462             }
463
464             AttributeSet attrs = Xml.asAttributeSet(parser);
465
466             int type;
467             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
468                     && type != XmlPullParser.START_TAG) {
469             }
470
471             String nodeName = parser.getName();
472             if (!mAttributesName.equals(nodeName)) {
473                 throw new XmlPullParserException(
474                         "Meta-data does not start with " + mAttributesName +  " tag");
475             }
476
477             V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
478                     si.packageName, attrs);
479             if (v == null) {
480                 return null;
481             }
482             final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
483             final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
484             final int uid = applicationInfo.uid;
485             return new ServiceInfo<V>(v, componentName, uid);
486         } catch (NameNotFoundException e) {
487             throw new XmlPullParserException(
488                     "Unable to load resources for pacakge " + si.packageName);
489         } finally {
490             if (parser != null) parser.close();
491         }
492     }
493
494     /**
495      * Read all sync status back in to the initial engine state.
496      */
497     private void readPersistentServicesLocked() {
498         mUserServices.clear();
499         if (mSerializerAndParser == null) {
500             return;
501         }
502         FileInputStream fis = null;
503         try {
504             mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
505             if (mPersistentServicesFileDidNotExist) {
506                 return;
507             }
508             fis = mPersistentServicesFile.openRead();
509             XmlPullParser parser = Xml.newPullParser();
510             parser.setInput(fis, null);
511             int eventType = parser.getEventType();
512             while (eventType != XmlPullParser.START_TAG
513                     && eventType != XmlPullParser.END_DOCUMENT) {
514                 eventType = parser.next();
515             }
516             String tagName = parser.getName();
517             if ("services".equals(tagName)) {
518                 eventType = parser.next();
519                 do {
520                     if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
521                         tagName = parser.getName();
522                         if ("service".equals(tagName)) {
523                             V service = mSerializerAndParser.createFromXml(parser);
524                             if (service == null) {
525                                 break;
526                             }
527                             String uidString = parser.getAttributeValue(null, "uid");
528                             final int uid = Integer.parseInt(uidString);
529                             final int userId = UserHandle.getUserId(uid);
530                             final UserServices<V> user = findOrCreateUserLocked(userId);
531                             user.persistentServices.put(service, uid);
532                         }
533                     }
534                     eventType = parser.next();
535                 } while (eventType != XmlPullParser.END_DOCUMENT);
536             }
537         } catch (Exception e) {
538             Log.w(TAG, "Error reading persistent services, starting from scratch", e);
539         } finally {
540             if (fis != null) {
541                 try {
542                     fis.close();
543                 } catch (java.io.IOException e1) {
544                 }
545             }
546         }
547     }
548
549     /**
550      * Write all sync status to the sync status file.
551      */
552     private void writePersistentServicesLocked() {
553         if (mSerializerAndParser == null) {
554             return;
555         }
556         FileOutputStream fos = null;
557         try {
558             fos = mPersistentServicesFile.startWrite();
559             XmlSerializer out = new FastXmlSerializer();
560             out.setOutput(fos, "utf-8");
561             out.startDocument(null, true);
562             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
563             out.startTag(null, "services");
564             for (int i = 0; i < mUserServices.size(); i++) {
565                 final UserServices<V> user = mUserServices.valueAt(i);
566                 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
567                     out.startTag(null, "service");
568                     out.attribute(null, "uid", Integer.toString(service.getValue()));
569                     mSerializerAndParser.writeAsXml(service.getKey(), out);
570                     out.endTag(null, "service");
571                 }
572             }
573             out.endTag(null, "services");
574             out.endDocument();
575             mPersistentServicesFile.finishWrite(fos);
576         } catch (java.io.IOException e1) {
577             Log.w(TAG, "Error writing accounts", e1);
578             if (fos != null) {
579                 mPersistentServicesFile.failWrite(fos);
580             }
581         }
582     }
583
584     public abstract V parseServiceAttributes(Resources res,
585             String packageName, AttributeSet attrs);
586 }