Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2012 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 com.android.server;
     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.ServiceConnection;
     25 import android.content.pm.PackageInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.PackageManager.NameNotFoundException;
     28 import android.content.pm.ResolveInfo;
     29 import android.content.pm.Signature;
     30 import android.content.res.Resources;
     31 import android.os.Handler;
     32 import android.os.IBinder;
     33 import android.os.UserHandle;
     34 import android.util.Log;
     35 
     36 import com.android.internal.content.PackageMonitor;
     37 
     38 import java.util.ArrayList;
     39 import java.util.Arrays;
     40 import java.util.HashSet;
     41 import java.util.List;
     42 
     43 /**
     44  * Find the best Service, and bind to it.
     45  * Handles run-time package changes.
     46  */
     47 public class ServiceWatcher implements ServiceConnection {
     48     private static final boolean D = false;
     49     public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
     50     public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
     51 
     52     private final String mTag;
     53     private final Context mContext;
     54     private final PackageManager mPm;
     55     private final List<HashSet<Signature>> mSignatureSets;
     56     private final String mAction;
     57 
     58     /**
     59      * If mServicePackageName is not null, only this package will be searched for the service that
     60      * implements mAction. When null, all packages in the system that matches one of the signature
     61      * in mSignatureSets are searched.
     62      */
     63     private final String mServicePackageName;
     64     private final Runnable mNewServiceWork;
     65     private final Handler mHandler;
     66 
     67     private Object mLock = new Object();
     68 
     69     // all fields below synchronized on mLock
     70     private IBinder mBinder;   // connected service
     71     private String mPackageName;  // current best package
     72     private int mVersion = Integer.MIN_VALUE;  // current best version
     73     /**
     74      * Whether the currently-connected service is multiuser-aware. This can change at run-time
     75      * when switching from one version of a service to another.
     76      */
     77     private boolean mIsMultiuser = false;
     78 
     79     public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
     80             List<String> initialPackageNames) {
     81         PackageManager pm = context.getPackageManager();
     82         ArrayList<HashSet<Signature>> sigSets = new ArrayList<HashSet<Signature>>();
     83         for (int i = 0, size = initialPackageNames.size(); i < size; i++) {
     84             String pkg = initialPackageNames.get(i);
     85             try {
     86                 HashSet<Signature> set = new HashSet<Signature>();
     87                 Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
     88                 set.addAll(Arrays.asList(sigs));
     89                 sigSets.add(set);
     90             } catch (NameNotFoundException e) {
     91                 Log.w("ServiceWatcher", pkg + " not found");
     92             }
     93         }
     94         return sigSets;
     95     }
     96 
     97     public ServiceWatcher(Context context, String logTag, String action,
     98             int overlaySwitchResId, int defaultServicePackageNameResId,
     99             int initialPackageNamesResId, Runnable newServiceWork,
    100             Handler handler) {
    101         mContext = context;
    102         mTag = logTag;
    103         mAction = action;
    104         mPm = mContext.getPackageManager();
    105         mNewServiceWork = newServiceWork;
    106         mHandler = handler;
    107         Resources resources = context.getResources();
    108 
    109         // Whether to enable service overlay.
    110         boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
    111         ArrayList<String>  initialPackageNames = new ArrayList<String>();
    112         if (enableOverlay) {
    113             // A list of package names used to create the signatures.
    114             String[] pkgs = resources.getStringArray(initialPackageNamesResId);
    115             if (pkgs != null) initialPackageNames.addAll(Arrays.asList(pkgs));
    116             mServicePackageName = null;
    117             if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
    118         } else {
    119             // The default package name that is searched for service implementation when overlay is
    120             // disabled.
    121             String servicePackageName = resources.getString(defaultServicePackageNameResId);
    122             if (servicePackageName != null) initialPackageNames.add(servicePackageName);
    123             mServicePackageName = servicePackageName;
    124             if (D) Log.d(mTag, "Overlay disabled, default package=" + servicePackageName);
    125         }
    126         mSignatureSets = getSignatureSets(context, initialPackageNames);
    127     }
    128 
    129     public boolean start() {
    130         synchronized (mLock) {
    131             if (!bindBestPackageLocked(mServicePackageName)) return false;
    132         }
    133 
    134         // listen for user change
    135         IntentFilter intentFilter = new IntentFilter();
    136         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
    137         mContext.registerReceiverAsUser(new BroadcastReceiver() {
    138             @Override
    139             public void onReceive(Context context, Intent intent) {
    140                 String action = intent.getAction();
    141                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
    142                     switchUser();
    143                 }
    144             }
    145         }, UserHandle.ALL, intentFilter, null, mHandler);
    146 
    147         // listen for relevant package changes if service overlay is enabled.
    148         if (mServicePackageName == null) {
    149             mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
    150         }
    151 
    152         return true;
    153     }
    154 
    155     /**
    156      * Searches and binds to the best package, or do nothing
    157      * if the best package is already bound.
    158      * Only checks the named package, or checks all packages if it
    159      * is null.
    160      * Return true if a new package was found to bind to.
    161      */
    162     private boolean bindBestPackageLocked(String justCheckThisPackage) {
    163         Intent intent = new Intent(mAction);
    164         if (justCheckThisPackage != null) {
    165             intent.setPackage(justCheckThisPackage);
    166         }
    167         List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
    168                 PackageManager.GET_META_DATA, UserHandle.USER_OWNER);
    169         int bestVersion = Integer.MIN_VALUE;
    170         String bestPackage = null;
    171         boolean bestIsMultiuser = false;
    172         if (rInfos != null) {
    173             for (ResolveInfo rInfo : rInfos) {
    174                 String packageName = rInfo.serviceInfo.packageName;
    175 
    176                 // check signature
    177                 try {
    178                     PackageInfo pInfo;
    179                     pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
    180                     if (!isSignatureMatch(pInfo.signatures)) {
    181                         Log.w(mTag, packageName + " resolves service " + mAction
    182                                 + ", but has wrong signature, ignoring");
    183                         continue;
    184                     }
    185                 } catch (NameNotFoundException e) {
    186                     Log.wtf(mTag, e);
    187                     continue;
    188                 }
    189 
    190                 // check metadata
    191                 int version = Integer.MIN_VALUE;
    192                 boolean isMultiuser = false;
    193                 if (rInfo.serviceInfo.metaData != null) {
    194                     version = rInfo.serviceInfo.metaData.getInt(
    195                             EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
    196                     isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
    197                 }
    198 
    199                 if (version > mVersion) {
    200                     bestVersion = version;
    201                     bestPackage = packageName;
    202                     bestIsMultiuser = isMultiuser;
    203                 }
    204             }
    205 
    206             if (D) {
    207                 Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
    208                         (justCheckThisPackage == null ? ""
    209                                 : "(" + justCheckThisPackage + ") "), rInfos.size(),
    210                         (bestPackage == null ? "no new best package"
    211                                 : "new best package: " + bestPackage)));
    212             }
    213         } else {
    214             if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
    215         }
    216         if (bestPackage != null) {
    217             bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
    218             return true;
    219         }
    220         return false;
    221     }
    222 
    223     private void unbindLocked() {
    224         String pkg;
    225         pkg = mPackageName;
    226         mPackageName = null;
    227         mVersion = Integer.MIN_VALUE;
    228         mIsMultiuser = false;
    229         if (pkg != null) {
    230             if (D) Log.d(mTag, "unbinding " + pkg);
    231             mContext.unbindService(this);
    232         }
    233     }
    234 
    235     private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
    236         unbindLocked();
    237         Intent intent = new Intent(mAction);
    238         intent.setPackage(packageName);
    239         mPackageName = packageName;
    240         mVersion = version;
    241         mIsMultiuser = isMultiuser;
    242         if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ") ("
    243                 + (isMultiuser ? "multi" : "single") + "-user)");
    244         mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
    245                 | Context.BIND_NOT_VISIBLE, mIsMultiuser ? UserHandle.OWNER : UserHandle.CURRENT);
    246     }
    247 
    248     public static boolean isSignatureMatch(Signature[] signatures,
    249             List<HashSet<Signature>> sigSets) {
    250         if (signatures == null) return false;
    251 
    252         // build hashset of input to test against
    253         HashSet<Signature> inputSet = new HashSet<Signature>();
    254         for (Signature s : signatures) {
    255             inputSet.add(s);
    256         }
    257 
    258         // test input against each of the signature sets
    259         for (HashSet<Signature> referenceSet : sigSets) {
    260             if (referenceSet.equals(inputSet)) {
    261                 return true;
    262             }
    263         }
    264         return false;
    265     }
    266 
    267     private boolean isSignatureMatch(Signature[] signatures) {
    268         return isSignatureMatch(signatures, mSignatureSets);
    269     }
    270 
    271     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
    272         /**
    273          * Called when package has been reinstalled
    274          */
    275         @Override
    276         public void onPackageUpdateFinished(String packageName, int uid) {
    277             synchronized (mLock) {
    278                 if (packageName.equals(mPackageName)) {
    279                     // package updated, make sure to rebind
    280                     unbindLocked();
    281                 }
    282                 // Need to check all packages because this method is also called when a
    283                 // system app is uninstalled and the stock version in reinstalled.
    284                 bindBestPackageLocked(null);
    285             }
    286         }
    287 
    288         @Override
    289         public void onPackageAdded(String packageName, int uid) {
    290             synchronized (mLock) {
    291                 if (packageName.equals(mPackageName)) {
    292                     // package updated, make sure to rebind
    293                     unbindLocked();
    294                 }
    295                 // check the new package is case it is better
    296                 bindBestPackageLocked(null);
    297             }
    298         }
    299 
    300         @Override
    301         public void onPackageRemoved(String packageName, int uid) {
    302             synchronized (mLock) {
    303                 if (packageName.equals(mPackageName)) {
    304                     unbindLocked();
    305                     // the currently bound package was removed,
    306                     // need to search for a new package
    307                     bindBestPackageLocked(null);
    308                 }
    309             }
    310         }
    311 
    312         @Override
    313         public boolean onPackageChanged(String packageName, int uid, String[] components) {
    314             synchronized (mLock) {
    315                 if (packageName.equals(mPackageName)) {
    316                     // service enabled or disabled, make sure to rebind
    317                     unbindLocked();
    318                 }
    319                 // the service might be disabled, need to search for a new
    320                 // package
    321                 bindBestPackageLocked(null);
    322             }
    323             return super.onPackageChanged(packageName, uid, components);
    324         }
    325     };
    326 
    327     @Override
    328     public void onServiceConnected(ComponentName name, IBinder binder) {
    329         synchronized (mLock) {
    330             String packageName = name.getPackageName();
    331             if (packageName.equals(mPackageName)) {
    332                 if (D) Log.d(mTag, packageName + " connected");
    333                 mBinder = binder;
    334                 if (mHandler !=null && mNewServiceWork != null) {
    335                     mHandler.post(mNewServiceWork);
    336                 }
    337             } else {
    338                 Log.w(mTag, "unexpected onServiceConnected: " + packageName);
    339             }
    340         }
    341     }
    342 
    343     @Override
    344     public void onServiceDisconnected(ComponentName name) {
    345         synchronized (mLock) {
    346             String packageName = name.getPackageName();
    347             if (D) Log.d(mTag, packageName + " disconnected");
    348 
    349             if (packageName.equals(mPackageName)) {
    350                 mBinder = null;
    351             }
    352         }
    353     }
    354 
    355     public String getBestPackageName() {
    356         synchronized (mLock) {
    357             return mPackageName;
    358         }
    359     }
    360 
    361     public int getBestVersion() {
    362         synchronized (mLock) {
    363             return mVersion;
    364         }
    365     }
    366 
    367     public IBinder getBinder() {
    368         synchronized (mLock) {
    369             return mBinder;
    370         }
    371     }
    372 
    373     public void switchUser() {
    374         synchronized (mLock) {
    375             if (!mIsMultiuser) {
    376                 unbindLocked();
    377                 bindBestPackageLocked(mServicePackageName);
    378             }
    379         }
    380     }
    381 }
    382