/* * 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); } } } }