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 17package com.android.server; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.content.pm.PackageInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.PackageManager.NameNotFoundException; 26import android.content.pm.ResolveInfo; 27import android.content.pm.Signature; 28import android.os.Handler; 29import android.os.IBinder; 30import android.os.UserHandle; 31import android.util.Log; 32 33import com.android.internal.content.PackageMonitor; 34 35import java.util.ArrayList; 36import java.util.Arrays; 37import java.util.HashSet; 38import java.util.List; 39 40/** 41 * Find the best Service, and bind to it. 42 * Handles run-time package changes. 43 */ 44public class ServiceWatcher implements ServiceConnection { 45 private static final boolean D = false; 46 public static final String EXTRA_SERVICE_VERSION = "serviceVersion"; 47 48 private final String mTag; 49 private final Context mContext; 50 private final PackageManager mPm; 51 private final List<HashSet<Signature>> mSignatureSets; 52 private final String mAction; 53 private final Runnable mNewServiceWork; 54 private final Handler mHandler; 55 56 private Object mLock = new Object(); 57 58 // all fields below synchronized on mLock 59 private IBinder mBinder; // connected service 60 private String mPackageName; // current best package 61 private int mVersion = Integer.MIN_VALUE; // current best version 62 private int mCurrentUserId; 63 64 public static ArrayList<HashSet<Signature>> getSignatureSets(Context context, 65 List<String> initialPackageNames) { 66 PackageManager pm = context.getPackageManager(); 67 ArrayList<HashSet<Signature>> sigSets = new ArrayList<HashSet<Signature>>(); 68 for (int i = 0, size = initialPackageNames.size(); i < size; i++) { 69 String pkg = initialPackageNames.get(i); 70 try { 71 HashSet<Signature> set = new HashSet<Signature>(); 72 Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures; 73 set.addAll(Arrays.asList(sigs)); 74 sigSets.add(set); 75 } catch (NameNotFoundException e) { 76 Log.w("ServiceWatcher", pkg + " not found"); 77 } 78 } 79 return sigSets; 80 } 81 82 public ServiceWatcher(Context context, String logTag, String action, 83 List<String> initialPackageNames, Runnable newServiceWork, Handler handler, int userId) { 84 mContext = context; 85 mTag = logTag; 86 mAction = action; 87 mPm = mContext.getPackageManager(); 88 mNewServiceWork = newServiceWork; 89 mHandler = handler; 90 mCurrentUserId = userId; 91 92 mSignatureSets = getSignatureSets(context, initialPackageNames); 93 } 94 95 public boolean start() { 96 synchronized (mLock) { 97 if (!bindBestPackageLocked(null)) return false; 98 } 99 100 mPackageMonitor.register(mContext, null, UserHandle.ALL, true); 101 return true; 102 } 103 104 /** 105 * Searches and binds to the best package, or do nothing 106 * if the best package is already bound. 107 * Only checks the named package, or checks all packages if it 108 * is null. 109 * Return true if a new package was found to bind to. 110 */ 111 private boolean bindBestPackageLocked(String justCheckThisPackage) { 112 Intent intent = new Intent(mAction); 113 if (justCheckThisPackage != null) { 114 intent.setPackage(justCheckThisPackage); 115 } 116 List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(new Intent(mAction), 117 PackageManager.GET_META_DATA, mCurrentUserId); 118 int bestVersion = Integer.MIN_VALUE; 119 String bestPackage = null; 120 for (ResolveInfo rInfo : rInfos) { 121 String packageName = rInfo.serviceInfo.packageName; 122 123 // check signature 124 try { 125 PackageInfo pInfo; 126 pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); 127 if (!isSignatureMatch(pInfo.signatures)) { 128 Log.w(mTag, packageName + " resolves service " + mAction + 129 ", but has wrong signature, ignoring"); 130 continue; 131 } 132 } catch (NameNotFoundException e) { 133 Log.wtf(mTag, e); 134 continue; 135 } 136 137 // check version 138 int version = 0; 139 if (rInfo.serviceInfo.metaData != null) { 140 version = rInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, 0); 141 } 142 143 if (version > mVersion) { 144 bestVersion = version; 145 bestPackage = packageName; 146 } 147 } 148 149 if (D) Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction, 150 (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "), 151 rInfos.size(), 152 (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage))); 153 154 if (bestPackage != null) { 155 bindToPackageLocked(bestPackage, bestVersion); 156 return true; 157 } 158 return false; 159 } 160 161 private void unbindLocked() { 162 String pkg; 163 pkg = mPackageName; 164 mPackageName = null; 165 mVersion = Integer.MIN_VALUE; 166 if (pkg != null) { 167 if (D) Log.d(mTag, "unbinding " + pkg); 168 mContext.unbindService(this); 169 } 170 } 171 172 private void bindToPackageLocked(String packageName, int version) { 173 unbindLocked(); 174 Intent intent = new Intent(mAction); 175 intent.setPackage(packageName); 176 mPackageName = packageName; 177 mVersion = version; 178 if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")"); 179 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND 180 | Context.BIND_NOT_VISIBLE, mCurrentUserId); 181 } 182 183 public static boolean isSignatureMatch(Signature[] signatures, 184 List<HashSet<Signature>> sigSets) { 185 if (signatures == null) return false; 186 187 // build hashset of input to test against 188 HashSet<Signature> inputSet = new HashSet<Signature>(); 189 for (Signature s : signatures) { 190 inputSet.add(s); 191 } 192 193 // test input against each of the signature sets 194 for (HashSet<Signature> referenceSet : sigSets) { 195 if (referenceSet.equals(inputSet)) { 196 return true; 197 } 198 } 199 return false; 200 } 201 202 private boolean isSignatureMatch(Signature[] signatures) { 203 return isSignatureMatch(signatures, mSignatureSets); 204 } 205 206 private final PackageMonitor mPackageMonitor = new PackageMonitor() { 207 /** 208 * Called when package has been reinstalled 209 */ 210 @Override 211 public void onPackageUpdateFinished(String packageName, int uid) { 212 synchronized (mLock) { 213 if (packageName.equals(mPackageName)) { 214 // package updated, make sure to rebind 215 unbindLocked(); 216 } 217 // check the updated package in case it is better 218 bindBestPackageLocked(packageName); 219 } 220 } 221 222 @Override 223 public void onPackageAdded(String packageName, int uid) { 224 synchronized (mLock) { 225 if (packageName.equals(mPackageName)) { 226 // package updated, make sure to rebind 227 unbindLocked(); 228 } 229 // check the new package is case it is better 230 bindBestPackageLocked(packageName); 231 } 232 } 233 234 @Override 235 public void onPackageRemoved(String packageName, int uid) { 236 synchronized (mLock) { 237 if (packageName.equals(mPackageName)) { 238 unbindLocked(); 239 // the currently bound package was removed, 240 // need to search for a new package 241 bindBestPackageLocked(null); 242 } 243 } 244 } 245 }; 246 247 @Override 248 public void onServiceConnected(ComponentName name, IBinder binder) { 249 synchronized (mLock) { 250 String packageName = name.getPackageName(); 251 if (packageName.equals(mPackageName)) { 252 if (D) Log.d(mTag, packageName + " connected"); 253 mBinder = binder; 254 if (mHandler !=null && mNewServiceWork != null) { 255 mHandler.post(mNewServiceWork); 256 } 257 } else { 258 Log.w(mTag, "unexpected onServiceConnected: " + packageName); 259 } 260 } 261 } 262 263 @Override 264 public void onServiceDisconnected(ComponentName name) { 265 synchronized (mLock) { 266 String packageName = name.getPackageName(); 267 if (D) Log.d(mTag, packageName + " disconnected"); 268 269 if (packageName.equals(mPackageName)) { 270 mBinder = null; 271 } 272 } 273 } 274 275 public String getBestPackageName() { 276 synchronized (mLock) { 277 return mPackageName; 278 } 279 } 280 281 public int getBestVersion() { 282 synchronized (mLock) { 283 return mVersion; 284 } 285 } 286 287 public IBinder getBinder() { 288 synchronized (mLock) { 289 return mBinder; 290 } 291 } 292 293 public void switchUser(int userId) { 294 synchronized (mLock) { 295 unbindLocked(); 296 mCurrentUserId = userId; 297 bindBestPackageLocked(null); 298 } 299 } 300} 301