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.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>>();
     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 BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
    144         @Override
    145         public void onReceive(Context context, Intent intent) {
    146             final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
    147             if (uid != -1) {
    148                 generateServicesMap(UserHandle.getUserId(uid));
    149             }
    150         }
    151     };
    152 
    153     private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() {
    154         @Override
    155         public void onReceive(Context context, Intent intent) {
    156             // External apps can't coexist with multi-user, so scan owner
    157             generateServicesMap(UserHandle.USER_OWNER);
    158         }
    159     };
    160 
    161     public void invalidateCache(int userId) {
    162         synchronized (mServicesLock) {
    163             final UserServices<V> user = findOrCreateUserLocked(userId);
    164             user.services = null;
    165         }
    166     }
    167 
    168     public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) {
    169         synchronized (mServicesLock) {
    170             final UserServices<V> user = findOrCreateUserLocked(userId);
    171             if (user.services != null) {
    172                 fout.println("RegisteredServicesCache: " + user.services.size() + " services");
    173                 for (ServiceInfo<?> info : user.services.values()) {
    174                     fout.println("  " + info);
    175                 }
    176             } else {
    177                 fout.println("RegisteredServicesCache: services not loaded");
    178             }
    179         }
    180     }
    181 
    182     public RegisteredServicesCacheListener<V> getListener() {
    183         synchronized (this) {
    184             return mListener;
    185         }
    186     }
    187 
    188     public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
    189         if (handler == null) {
    190             handler = new Handler(mContext.getMainLooper());
    191         }
    192         synchronized (this) {
    193             mHandler = handler;
    194             mListener = listener;
    195         }
    196     }
    197 
    198     private void notifyListener(final V type, final int userId, final boolean removed) {
    199         if (DEBUG) {
    200             Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
    201         }
    202         RegisteredServicesCacheListener<V> listener;
    203         Handler handler;
    204         synchronized (this) {
    205             listener = mListener;
    206             handler = mHandler;
    207         }
    208         if (listener == null) {
    209             return;
    210         }
    211 
    212         final RegisteredServicesCacheListener<V> listener2 = listener;
    213         handler.post(new Runnable() {
    214             public void run() {
    215                 listener2.onServiceChanged(type, userId, removed);
    216             }
    217         });
    218     }
    219 
    220     /**
    221      * Value type that describes a Service. The information within can be used
    222      * to bind to the service.
    223      */
    224     public static class ServiceInfo<V> {
    225         public final V type;
    226         public final ComponentName componentName;
    227         public final int uid;
    228 
    229         /** @hide */
    230         public ServiceInfo(V type, ComponentName componentName, int uid) {
    231             this.type = type;
    232             this.componentName = componentName;
    233             this.uid = uid;
    234         }
    235 
    236         @Override
    237         public String toString() {
    238             return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
    239         }
    240     }
    241 
    242     /**
    243      * Accessor for the registered authenticators.
    244      * @param type the account type of the authenticator
    245      * @return the AuthenticatorInfo that matches the account type or null if none is present
    246      */
    247     public ServiceInfo<V> getServiceInfo(V type, int userId) {
    248         synchronized (mServicesLock) {
    249             // Find user and lazily populate cache
    250             final UserServices<V> user = findOrCreateUserLocked(userId);
    251             if (user.services == null) {
    252                 generateServicesMap(userId);
    253             }
    254             return user.services.get(type);
    255         }
    256     }
    257 
    258     /**
    259      * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
    260      * registered authenticators.
    261      */
    262     public Collection<ServiceInfo<V>> getAllServices(int userId) {
    263         synchronized (mServicesLock) {
    264             // Find user and lazily populate cache
    265             final UserServices<V> user = findOrCreateUserLocked(userId);
    266             if (user.services == null) {
    267                 generateServicesMap(userId);
    268             }
    269             return Collections.unmodifiableCollection(
    270                     new ArrayList<ServiceInfo<V>>(user.services.values()));
    271         }
    272     }
    273 
    274     private boolean inSystemImage(int callerUid) {
    275         String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
    276         for (String name : packages) {
    277             try {
    278                 PackageInfo packageInfo =
    279                         mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
    280                 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    281                     return true;
    282                 }
    283             } catch (PackageManager.NameNotFoundException e) {
    284                 return false;
    285             }
    286         }
    287         return false;
    288     }
    289 
    290     /**
    291      * Populate {@link UserServices#services} by scanning installed packages for
    292      * given {@link UserHandle}.
    293      */
    294     private void generateServicesMap(int userId) {
    295         if (DEBUG) {
    296             Slog.d(TAG, "generateServicesMap() for " + userId);
    297         }
    298 
    299         final PackageManager pm = mContext.getPackageManager();
    300         final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
    301         final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(
    302                 new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
    303         for (ResolveInfo resolveInfo : resolveInfos) {
    304             try {
    305                 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
    306                 if (info == null) {
    307                     Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
    308                     continue;
    309                 }
    310                 serviceInfos.add(info);
    311             } catch (XmlPullParserException e) {
    312                 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
    313             } catch (IOException e) {
    314                 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
    315             }
    316         }
    317 
    318         synchronized (mServicesLock) {
    319             final UserServices<V> user = findOrCreateUserLocked(userId);
    320             final boolean firstScan = user.services == null;
    321             if (firstScan) {
    322                 user.services = Maps.newHashMap();
    323             } else {
    324                 user.services.clear();
    325             }
    326 
    327             StringBuilder changes = new StringBuilder();
    328             boolean changed = false;
    329             for (ServiceInfo<V> info : serviceInfos) {
    330                 // four cases:
    331                 // - doesn't exist yet
    332                 //   - add, notify user that it was added
    333                 // - exists and the UID is the same
    334                 //   - replace, don't notify user
    335                 // - exists, the UID is different, and the new one is not a system package
    336                 //   - ignore
    337                 // - exists, the UID is different, and the new one is a system package
    338                 //   - add, notify user that it was added
    339                 Integer previousUid = user.persistentServices.get(info.type);
    340                 if (previousUid == null) {
    341                     if (DEBUG) {
    342                         changes.append("  New service added: ").append(info).append("\n");
    343                     }
    344                     changed = true;
    345                     user.services.put(info.type, info);
    346                     user.persistentServices.put(info.type, info.uid);
    347                     if (!(mPersistentServicesFileDidNotExist && firstScan)) {
    348                         notifyListener(info.type, userId, false /* removed */);
    349                     }
    350                 } else if (previousUid == info.uid) {
    351                     if (DEBUG) {
    352                         changes.append("  Existing service (nop): ").append(info).append("\n");
    353                     }
    354                     user.services.put(info.type, info);
    355                 } else if (inSystemImage(info.uid)
    356                         || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
    357                     if (DEBUG) {
    358                         if (inSystemImage(info.uid)) {
    359                             changes.append("  System service replacing existing: ").append(info)
    360                                     .append("\n");
    361                         } else {
    362                             changes.append("  Existing service replacing a removed service: ")
    363                                     .append(info).append("\n");
    364                         }
    365                     }
    366                     changed = true;
    367                     user.services.put(info.type, info);
    368                     user.persistentServices.put(info.type, info.uid);
    369                     notifyListener(info.type, userId, false /* removed */);
    370                 } else {
    371                     // ignore
    372                     if (DEBUG) {
    373                         changes.append("  Existing service with new uid ignored: ").append(info)
    374                                 .append("\n");
    375                     }
    376                 }
    377             }
    378 
    379             ArrayList<V> toBeRemoved = Lists.newArrayList();
    380             for (V v1 : user.persistentServices.keySet()) {
    381                 if (!containsType(serviceInfos, v1)) {
    382                     toBeRemoved.add(v1);
    383                 }
    384             }
    385             for (V v1 : toBeRemoved) {
    386                 if (DEBUG) {
    387                     changes.append("  Service removed: ").append(v1).append("\n");
    388                 }
    389                 changed = true;
    390                 user.persistentServices.remove(v1);
    391                 notifyListener(v1, userId, true /* removed */);
    392             }
    393             if (DEBUG) {
    394                 if (changes.length() > 0) {
    395                     Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
    396                             serviceInfos.size() + " services:\n" + changes);
    397                 } else {
    398                     Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
    399                             serviceInfos.size() + " services unchanged");
    400                 }
    401             }
    402             if (changed) {
    403                 writePersistentServicesLocked();
    404             }
    405         }
    406     }
    407 
    408     private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
    409         for (int i = 0, N = serviceInfos.size(); i < N; i++) {
    410             if (serviceInfos.get(i).type.equals(type)) {
    411                 return true;
    412             }
    413         }
    414 
    415         return false;
    416     }
    417 
    418     private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
    419         for (int i = 0, N = serviceInfos.size(); i < N; i++) {
    420             final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
    421             if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
    422                 return true;
    423             }
    424         }
    425 
    426         return false;
    427     }
    428 
    429     private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
    430             throws XmlPullParserException, IOException {
    431         android.content.pm.ServiceInfo si = service.serviceInfo;
    432         ComponentName componentName = new ComponentName(si.packageName, si.name);
    433 
    434         PackageManager pm = mContext.getPackageManager();
    435 
    436         XmlResourceParser parser = null;
    437         try {
    438             parser = si.loadXmlMetaData(pm, mMetaDataName);
    439             if (parser == null) {
    440                 throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
    441             }
    442 
    443             AttributeSet attrs = Xml.asAttributeSet(parser);
    444 
    445             int type;
    446             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    447                     && type != XmlPullParser.START_TAG) {
    448             }
    449 
    450             String nodeName = parser.getName();
    451             if (!mAttributesName.equals(nodeName)) {
    452                 throw new XmlPullParserException(
    453                         "Meta-data does not start with " + mAttributesName +  " tag");
    454             }
    455 
    456             V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
    457                     si.packageName, attrs);
    458             if (v == null) {
    459                 return null;
    460             }
    461             final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
    462             final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
    463             final int uid = applicationInfo.uid;
    464             return new ServiceInfo<V>(v, componentName, uid);
    465         } catch (NameNotFoundException e) {
    466             throw new XmlPullParserException(
    467                     "Unable to load resources for pacakge " + si.packageName);
    468         } finally {
    469             if (parser != null) parser.close();
    470         }
    471     }
    472 
    473     /**
    474      * Read all sync status back in to the initial engine state.
    475      */
    476     private void readPersistentServicesLocked() {
    477         mUserServices.clear();
    478         if (mSerializerAndParser == null) {
    479             return;
    480         }
    481         FileInputStream fis = null;
    482         try {
    483             mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
    484             if (mPersistentServicesFileDidNotExist) {
    485                 return;
    486             }
    487             fis = mPersistentServicesFile.openRead();
    488             XmlPullParser parser = Xml.newPullParser();
    489             parser.setInput(fis, null);
    490             int eventType = parser.getEventType();
    491             while (eventType != XmlPullParser.START_TAG
    492                     && eventType != XmlPullParser.END_DOCUMENT) {
    493                 eventType = parser.next();
    494             }
    495             String tagName = parser.getName();
    496             if ("services".equals(tagName)) {
    497                 eventType = parser.next();
    498                 do {
    499                     if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
    500                         tagName = parser.getName();
    501                         if ("service".equals(tagName)) {
    502                             V service = mSerializerAndParser.createFromXml(parser);
    503                             if (service == null) {
    504                                 break;
    505                             }
    506                             String uidString = parser.getAttributeValue(null, "uid");
    507                             final int uid = Integer.parseInt(uidString);
    508                             final int userId = UserHandle.getUserId(uid);
    509                             final UserServices<V> user = findOrCreateUserLocked(userId);
    510                             user.persistentServices.put(service, uid);
    511                         }
    512                     }
    513                     eventType = parser.next();
    514                 } while (eventType != XmlPullParser.END_DOCUMENT);
    515             }
    516         } catch (Exception e) {
    517             Log.w(TAG, "Error reading persistent services, starting from scratch", e);
    518         } finally {
    519             if (fis != null) {
    520                 try {
    521                     fis.close();
    522                 } catch (java.io.IOException e1) {
    523                 }
    524             }
    525         }
    526     }
    527 
    528     /**
    529      * Write all sync status to the sync status file.
    530      */
    531     private void writePersistentServicesLocked() {
    532         if (mSerializerAndParser == null) {
    533             return;
    534         }
    535         FileOutputStream fos = null;
    536         try {
    537             fos = mPersistentServicesFile.startWrite();
    538             XmlSerializer out = new FastXmlSerializer();
    539             out.setOutput(fos, "utf-8");
    540             out.startDocument(null, true);
    541             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    542             out.startTag(null, "services");
    543             for (int i = 0; i < mUserServices.size(); i++) {
    544                 final UserServices<V> user = mUserServices.valueAt(i);
    545                 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
    546                     out.startTag(null, "service");
    547                     out.attribute(null, "uid", Integer.toString(service.getValue()));
    548                     mSerializerAndParser.writeAsXml(service.getKey(), out);
    549                     out.endTag(null, "service");
    550                 }
    551             }
    552             out.endTag(null, "services");
    553             out.endDocument();
    554             mPersistentServicesFile.finishWrite(fos);
    555         } catch (java.io.IOException e1) {
    556             Log.w(TAG, "Error writing accounts", e1);
    557             if (fos != null) {
    558                 mPersistentServicesFile.failWrite(fos);
    559             }
    560         }
    561     }
    562 
    563     public abstract V parseServiceAttributes(Resources res,
    564             String packageName, AttributeSet attrs);
    565 }
    566