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