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