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