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