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