summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/ServiceWatcher.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/ServiceWatcher.java')
-rw-r--r--services/java/com/android/server/ServiceWatcher.java274
1 files changed, 274 insertions, 0 deletions
diff --git a/services/java/com/android/server/ServiceWatcher.java b/services/java/com/android/server/ServiceWatcher.java
new file mode 100644
index 0000000..0dfaa05
--- /dev/null
+++ b/services/java/com/android/server/ServiceWatcher.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.content.PackageMonitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Find the best Service, and bind to it.
+ * Handles run-time package changes.
+ */
+public class ServiceWatcher implements ServiceConnection {
+ private static final boolean D = false;
+ private static final String EXTRA_VERSION = "version";
+
+ private final String mTag;
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final List<HashSet<Signature>> mSignatureSets;
+ private final String mAction;
+ private final Runnable mNewServiceWork;
+ private final Handler mHandler;
+
+ private Object mLock = new Object();
+
+ // all fields below synchronized on mLock
+ private IBinder mBinder; // connected service
+ private String mPackageName; // current best package
+ private int mVersion; // current best version
+
+ public ServiceWatcher(Context context, String logTag, String action,
+ List<String> initialPackageNames, Runnable newServiceWork, Handler handler) {
+ mContext = context;
+ mTag = logTag;
+ mAction = action;
+ mPm = mContext.getPackageManager();
+ mNewServiceWork = newServiceWork;
+ mHandler = handler;
+
+ mSignatureSets = new ArrayList<HashSet<Signature>>();
+ for (int i=0; i < initialPackageNames.size(); i++) {
+ String pkg = initialPackageNames.get(i);
+ HashSet<Signature> set = new HashSet<Signature>();
+ try {
+ Signature[] sigs =
+ mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
+ set.addAll(Arrays.asList(sigs));
+ mSignatureSets.add(set);
+ } catch (NameNotFoundException e) {
+ Log.w(logTag, pkg + " not found");
+ }
+ }
+
+ }
+
+ public boolean start() {
+ if (!bindBestPackage(null)) return false;
+
+ mPackageMonitor.register(mContext, null, true);
+ return true;
+ }
+
+ /**
+ * Searches and binds to the best package, or do nothing
+ * if the best package is already bound.
+ * Only checks the named package, or checks all packages if it
+ * is null.
+ * Return true if a new package was found to bind to.
+ */
+ private boolean bindBestPackage(String justCheckThisPackage) {
+ Intent intent = new Intent(mAction);
+ if (justCheckThisPackage != null) {
+ intent.setPackage(justCheckThisPackage);
+ }
+ List<ResolveInfo> rInfos = mPm.queryIntentServices(new Intent(mAction),
+ PackageManager.GET_META_DATA);
+ int bestVersion = Integer.MIN_VALUE;
+ String bestPackage = null;
+ for (ResolveInfo rInfo : rInfos) {
+ String packageName = rInfo.serviceInfo.packageName;
+
+ // check signature
+ try {
+ PackageInfo pInfo;
+ pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ if (!isSignatureMatch(pInfo.signatures)) {
+ Log.w(mTag, packageName + " resolves service " + mAction +
+ ", but has wrong signature, ignoring");
+ continue;
+ }
+ } catch (NameNotFoundException e) {
+ Log.wtf(mTag, e);
+ continue;
+ }
+
+ // check version
+ int version = 0;
+ if (rInfo.serviceInfo.metaData != null) {
+ version = rInfo.serviceInfo.metaData.getInt(EXTRA_VERSION, 0);
+ }
+ if (version > mVersion) {
+ bestVersion = version;
+ bestPackage = packageName;
+ }
+ }
+
+ if (D) Log.d(mTag, String.format("bindBestPackage %s found %d, %s",
+ (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "),
+ rInfos.size(),
+ (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage)));
+
+ if (bestPackage != null) {
+ bindToPackage(bestPackage, bestVersion);
+ return true;
+ }
+ return false;
+ }
+
+ private void unbind() {
+ String pkg;
+ synchronized (mLock) {
+ pkg = mPackageName;
+ mPackageName = null;
+ mVersion = Integer.MIN_VALUE;
+ }
+ if (pkg != null) {
+ if (D) Log.d(mTag, "unbinding " + pkg);
+ mContext.unbindService(this);
+ }
+ }
+
+ private void bindToPackage(String packageName, int version) {
+ unbind();
+ Intent intent = new Intent(mAction);
+ intent.setPackage(packageName);
+ synchronized (mLock) {
+ mPackageName = packageName;
+ mVersion = version;
+ }
+ if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")");
+ mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT);
+ }
+
+ private boolean isSignatureMatch(Signature[] signatures) {
+ if (signatures == null) return false;
+
+ // build hashset of input to test against
+ HashSet<Signature> inputSet = new HashSet<Signature>();
+ for (Signature s : signatures) {
+ inputSet.add(s);
+ }
+
+ // test input against each of the signature sets
+ for (HashSet<Signature> referenceSet : mSignatureSets) {
+ if (referenceSet.equals(inputSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ /**
+ * Called when package has been reinstalled
+ */
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ if (packageName.equals(mPackageName)) {
+ // package updated, make sure to rebind
+ unbind();
+ }
+ // check the updated package in case it is better
+ bindBestPackage(packageName);
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ if (packageName.equals(mPackageName)) {
+ // package updated, make sure to rebind
+ unbind();
+ }
+ // check the new package is case it is better
+ bindBestPackage(packageName);
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ if (packageName.equals(mPackageName)) {
+ unbind();
+ // the currently bound package was removed,
+ // need to search for a new package
+ bindBestPackage(null);
+ }
+ }
+ };
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ synchronized (mLock) {
+ String packageName = name.getPackageName();
+ if (packageName.equals(mPackageName)) {
+ if (D) Log.d(mTag, packageName + " connected");
+ mBinder = binder;
+ if (mHandler !=null && mNewServiceWork != null) {
+ mHandler.post(mNewServiceWork);
+ }
+ } else {
+ Log.w(mTag, "unexpected onServiceConnected: " + packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ String packageName = name.getPackageName();
+ if (D) Log.d(mTag, packageName + " disconnected");
+
+ if (packageName.equals(mPackageName)) {
+ mBinder = null;
+ }
+ }
+ }
+
+ public String getBestPackageName() {
+ synchronized (mLock) {
+ return mPackageName;
+ }
+ }
+
+ public int getBestVersion() {
+ synchronized (mLock) {
+ return mVersion;
+ }
+ }
+
+ public IBinder getBinder() {
+ synchronized (mLock) {
+ return mBinder;
+ }
+ }
+}