summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/ServiceWatcher.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/ServiceWatcher.java')
-rw-r--r--services/core/java/com/android/server/ServiceWatcher.java381
1 files changed, 381 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
new file mode 100644
index 0000000..9274295
--- /dev/null
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -0,0 +1,381 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+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;
+ public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+ public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+ private final String mTag;
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final List<HashSet<Signature>> mSignatureSets;
+ private final String mAction;
+
+ /**
+ * If mServicePackageName is not null, only this package will be searched for the service that
+ * implements mAction. When null, all packages in the system that matches one of the signature
+ * in mSignatureSets are searched.
+ */
+ private final String mServicePackageName;
+ 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 = Integer.MIN_VALUE; // current best version
+ /**
+ * Whether the currently-connected service is multiuser-aware. This can change at run-time
+ * when switching from one version of a service to another.
+ */
+ private boolean mIsMultiuser = false;
+
+ public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
+ List<String> initialPackageNames) {
+ PackageManager pm = context.getPackageManager();
+ ArrayList<HashSet<Signature>> sigSets = new ArrayList<HashSet<Signature>>();
+ for (int i = 0, size = initialPackageNames.size(); i < size; i++) {
+ String pkg = initialPackageNames.get(i);
+ try {
+ HashSet<Signature> set = new HashSet<Signature>();
+ Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
+ set.addAll(Arrays.asList(sigs));
+ sigSets.add(set);
+ } catch (NameNotFoundException e) {
+ Log.w("ServiceWatcher", pkg + " not found");
+ }
+ }
+ return sigSets;
+ }
+
+ public ServiceWatcher(Context context, String logTag, String action,
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Runnable newServiceWork,
+ Handler handler) {
+ mContext = context;
+ mTag = logTag;
+ mAction = action;
+ mPm = mContext.getPackageManager();
+ mNewServiceWork = newServiceWork;
+ mHandler = handler;
+ Resources resources = context.getResources();
+
+ // Whether to enable service overlay.
+ boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
+ ArrayList<String> initialPackageNames = new ArrayList<String>();
+ if (enableOverlay) {
+ // A list of package names used to create the signatures.
+ String[] pkgs = resources.getStringArray(initialPackageNamesResId);
+ if (pkgs != null) initialPackageNames.addAll(Arrays.asList(pkgs));
+ mServicePackageName = null;
+ if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
+ } else {
+ // The default package name that is searched for service implementation when overlay is
+ // disabled.
+ String servicePackageName = resources.getString(defaultServicePackageNameResId);
+ if (servicePackageName != null) initialPackageNames.add(servicePackageName);
+ mServicePackageName = servicePackageName;
+ if (D) Log.d(mTag, "Overlay disabled, default package=" + servicePackageName);
+ }
+ mSignatureSets = getSignatureSets(context, initialPackageNames);
+ }
+
+ public boolean start() {
+ synchronized (mLock) {
+ if (!bindBestPackageLocked(mServicePackageName)) return false;
+ }
+
+ // listen for user change
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ switchUser();
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, mHandler);
+
+ // listen for relevant package changes if service overlay is enabled.
+ if (mServicePackageName == null) {
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, 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 bindBestPackageLocked(String justCheckThisPackage) {
+ Intent intent = new Intent(mAction);
+ if (justCheckThisPackage != null) {
+ intent.setPackage(justCheckThisPackage);
+ }
+ List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
+ PackageManager.GET_META_DATA, UserHandle.USER_OWNER);
+ int bestVersion = Integer.MIN_VALUE;
+ String bestPackage = null;
+ boolean bestIsMultiuser = false;
+ if (rInfos != 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 metadata
+ int version = Integer.MIN_VALUE;
+ boolean isMultiuser = false;
+ if (rInfo.serviceInfo.metaData != null) {
+ version = rInfo.serviceInfo.metaData.getInt(
+ EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
+ isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
+ }
+
+ if (version > mVersion) {
+ bestVersion = version;
+ bestPackage = packageName;
+ bestIsMultiuser = isMultiuser;
+ }
+ }
+
+ if (D) {
+ Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
+ (justCheckThisPackage == null ? ""
+ : "(" + justCheckThisPackage + ") "), rInfos.size(),
+ (bestPackage == null ? "no new best package"
+ : "new best package: " + bestPackage)));
+ }
+ } else {
+ if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
+ }
+ if (bestPackage != null) {
+ bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
+ return true;
+ }
+ return false;
+ }
+
+ private void unbindLocked() {
+ String pkg;
+ pkg = mPackageName;
+ mPackageName = null;
+ mVersion = Integer.MIN_VALUE;
+ mIsMultiuser = false;
+ if (pkg != null) {
+ if (D) Log.d(mTag, "unbinding " + pkg);
+ mContext.unbindService(this);
+ }
+ }
+
+ private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
+ unbindLocked();
+ Intent intent = new Intent(mAction);
+ intent.setPackage(packageName);
+ mPackageName = packageName;
+ mVersion = version;
+ mIsMultiuser = isMultiuser;
+ if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ") ("
+ + (isMultiuser ? "multi" : "single") + "-user)");
+ mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_NOT_VISIBLE, mIsMultiuser ? UserHandle.OWNER : UserHandle.CURRENT);
+ }
+
+ public static boolean isSignatureMatch(Signature[] signatures,
+ List<HashSet<Signature>> sigSets) {
+ 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 : sigSets) {
+ if (referenceSet.equals(inputSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSignatureMatch(Signature[] signatures) {
+ return isSignatureMatch(signatures, mSignatureSets);
+ }
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ /**
+ * Called when package has been reinstalled
+ */
+ @Override
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ synchronized (mLock) {
+ if (packageName.equals(mPackageName)) {
+ // package updated, make sure to rebind
+ unbindLocked();
+ }
+ // Need to check all packages because this method is also called when a
+ // system app is uninstalled and the stock version in reinstalled.
+ bindBestPackageLocked(null);
+ }
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ synchronized (mLock) {
+ if (packageName.equals(mPackageName)) {
+ // package updated, make sure to rebind
+ unbindLocked();
+ }
+ // check the new package is case it is better
+ bindBestPackageLocked(null);
+ }
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ synchronized (mLock) {
+ if (packageName.equals(mPackageName)) {
+ unbindLocked();
+ // the currently bound package was removed,
+ // need to search for a new package
+ bindBestPackageLocked(null);
+ }
+ }
+ }
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ synchronized (mLock) {
+ if (packageName.equals(mPackageName)) {
+ // service enabled or disabled, make sure to rebind
+ unbindLocked();
+ }
+ // the service might be disabled, need to search for a new
+ // package
+ bindBestPackageLocked(null);
+ }
+ return super.onPackageChanged(packageName, uid, components);
+ }
+ };
+
+ @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;
+ }
+ }
+
+ public void switchUser() {
+ synchronized (mLock) {
+ if (!mIsMultiuser) {
+ unbindLocked();
+ bindBestPackageLocked(mServicePackageName);
+ }
+ }
+ }
+}