/*
|
* 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.annotation.Nullable;
|
import android.app.ActivityManager;
|
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.Bundle;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.Looper;
|
import android.os.RemoteException;
|
import android.os.UserHandle;
|
import android.util.Log;
|
import android.util.Slog;
|
|
import com.android.internal.content.PackageMonitor;
|
import com.android.internal.util.Preconditions;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Objects;
|
import java.util.concurrent.Callable;
|
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.FutureTask;
|
|
/**
|
* Find the best Service, and bind to it.
|
* Handles run-time package changes.
|
*/
|
public class ServiceWatcher implements ServiceConnection {
|
|
private static final String TAG = "ServiceWatcher";
|
private static final boolean D = false;
|
|
public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
|
public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
|
|
|
/** Function to run on binder interface. */
|
public interface BinderRunner {
|
/** Called to run client code with the binder. */
|
void run(IBinder binder) throws RemoteException;
|
}
|
|
/**
|
* Function to run on binder interface.
|
* @param <T> Type to return.
|
*/
|
public interface BlockingBinderRunner<T> {
|
/** Called to run client code with the binder. */
|
T run(IBinder binder) throws RemoteException;
|
}
|
|
public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
|
String... packageNames) {
|
PackageManager pm = context.getPackageManager();
|
|
ArrayList<HashSet<Signature>> signatureSets = new ArrayList<>(packageNames.length);
|
for (String packageName : packageNames) {
|
try {
|
Signature[] signatures = pm.getPackageInfo(packageName,
|
PackageManager.MATCH_SYSTEM_ONLY
|
| PackageManager.GET_SIGNATURES).signatures;
|
|
HashSet<Signature> set = new HashSet<>();
|
Collections.addAll(set, signatures);
|
signatureSets.add(set);
|
} catch (NameNotFoundException e) {
|
Log.w(TAG, packageName + " not found");
|
}
|
}
|
return signatureSets;
|
}
|
|
/** Checks if signatures match. */
|
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<>();
|
Collections.addAll(inputSet, signatures);
|
|
// test input against each of the signature sets
|
for (HashSet<Signature> referenceSet : sigSets) {
|
if (referenceSet.equals(inputSet)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private final Context mContext;
|
private final String mTag;
|
private final String mAction;
|
private final String mServicePackageName;
|
private final List<HashSet<Signature>> mSignatureSets;
|
|
private final Handler mHandler;
|
|
// read/write from handler thread
|
private IBinder mBestService;
|
private int mCurrentUserId;
|
|
// read from any thread, write from handler thread
|
private volatile ComponentName mBestComponent;
|
private volatile int mBestVersion;
|
private volatile int mBestUserId;
|
|
public ServiceWatcher(Context context, String logTag, String action,
|
int overlaySwitchResId, int defaultServicePackageNameResId,
|
int initialPackageNamesResId, Handler handler) {
|
Resources resources = context.getResources();
|
|
mContext = context;
|
mTag = logTag;
|
mAction = action;
|
|
boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
|
if (enableOverlay) {
|
String[] pkgs = resources.getStringArray(initialPackageNamesResId);
|
mServicePackageName = null;
|
mSignatureSets = getSignatureSets(context, pkgs);
|
if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
|
} else {
|
mServicePackageName = resources.getString(defaultServicePackageNameResId);
|
mSignatureSets = getSignatureSets(context, mServicePackageName);
|
if (D) Log.d(mTag, "Overlay disabled, default package=" + mServicePackageName);
|
}
|
|
mHandler = handler;
|
|
mBestComponent = null;
|
mBestVersion = Integer.MIN_VALUE;
|
mBestUserId = UserHandle.USER_NULL;
|
|
mBestService = null;
|
}
|
|
protected void onBind() {}
|
|
protected void onUnbind() {}
|
|
/**
|
* Start this watcher, including binding to the current best match and
|
* re-binding to any better matches down the road.
|
* <p>
|
* Note that if there are no matching encryption-aware services, we may not
|
* bind to a real service until after the current user is unlocked.
|
*
|
* @return {@code true} if a potential service implementation was found.
|
*/
|
public final boolean start() {
|
// if we have to return false, do it before registering anything
|
if (isServiceMissing()) return false;
|
|
// listen for relevant package changes if service overlay is enabled on handler
|
if (mServicePackageName == null) {
|
new PackageMonitor() {
|
@Override
|
public void onPackageUpdateFinished(String packageName, int uid) {
|
bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
|
}
|
|
@Override
|
public void onPackageAdded(String packageName, int uid) {
|
bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
|
}
|
|
@Override
|
public void onPackageRemoved(String packageName, int uid) {
|
bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
|
}
|
|
@Override
|
public boolean onPackageChanged(String packageName, int uid, String[] components) {
|
bindBestPackage(Objects.equals(packageName, getCurrentPackageName()));
|
return super.onPackageChanged(packageName, uid, components);
|
}
|
}.register(mContext, UserHandle.ALL, true, mHandler);
|
}
|
|
// listen for user change on handler
|
IntentFilter intentFilter = new IntentFilter();
|
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
|
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
|
mContext.registerReceiverAsUser(new BroadcastReceiver() {
|
@Override
|
public void onReceive(Context context, Intent intent) {
|
final String action = intent.getAction();
|
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
|
UserHandle.USER_NULL);
|
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
|
mCurrentUserId = userId;
|
bindBestPackage(false);
|
} else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
|
if (userId == mCurrentUserId) {
|
bindBestPackage(false);
|
}
|
}
|
}
|
}, UserHandle.ALL, intentFilter, null, mHandler);
|
|
mCurrentUserId = ActivityManager.getCurrentUser();
|
|
mHandler.post(() -> bindBestPackage(false));
|
return true;
|
}
|
|
/** Returns the name of the currently connected package or null. */
|
@Nullable
|
public String getCurrentPackageName() {
|
ComponentName bestComponent = mBestComponent;
|
return bestComponent == null ? null : bestComponent.getPackageName();
|
}
|
|
private boolean isServiceMissing() {
|
return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction),
|
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
|
UserHandle.USER_SYSTEM).isEmpty();
|
}
|
|
private void bindBestPackage(boolean forceRebind) {
|
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
|
|
Intent intent = new Intent(mAction);
|
if (mServicePackageName != null) {
|
intent.setPackage(mServicePackageName);
|
}
|
|
List<ResolveInfo> rInfos = mContext.getPackageManager().queryIntentServicesAsUser(intent,
|
PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AUTO,
|
mCurrentUserId);
|
if (rInfos == null) {
|
rInfos = Collections.emptyList();
|
}
|
|
ComponentName bestComponent = null;
|
int bestVersion = Integer.MIN_VALUE;
|
boolean bestIsMultiuser = false;
|
|
for (ResolveInfo rInfo : rInfos) {
|
ComponentName component = rInfo.serviceInfo.getComponentName();
|
String packageName = component.getPackageName();
|
|
// check signature
|
try {
|
PackageInfo pInfo = mContext.getPackageManager().getPackageInfo(packageName,
|
PackageManager.GET_SIGNATURES
|
| PackageManager.MATCH_DIRECT_BOOT_AUTO);
|
if (!isSignatureMatch(pInfo.signatures, mSignatureSets)) {
|
Log.w(mTag, packageName + " resolves service " + mAction
|
+ ", but has wrong signature, ignoring");
|
continue;
|
}
|
} catch (NameNotFoundException e) {
|
Log.wtf(mTag, e);
|
continue;
|
}
|
|
// check metadata
|
Bundle metadata = rInfo.serviceInfo.metaData;
|
int version = Integer.MIN_VALUE;
|
boolean isMultiuser = false;
|
if (metadata != null) {
|
version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
|
isMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
|
}
|
|
if (version > bestVersion) {
|
bestComponent = component;
|
bestVersion = version;
|
bestIsMultiuser = isMultiuser;
|
}
|
}
|
|
if (D) {
|
Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
|
(mServicePackageName == null ? ""
|
: "(" + mServicePackageName + ") "), rInfos.size(),
|
(bestComponent == null ? "no new best component"
|
: "new best component: " + bestComponent)));
|
}
|
|
if (bestComponent == null) {
|
Slog.w(mTag, "Odd, no component found for service " + mAction);
|
unbind();
|
return;
|
}
|
|
int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
|
boolean alreadyBound = Objects.equals(bestComponent, mBestComponent)
|
&& bestVersion == mBestVersion && userId == mBestUserId;
|
if (forceRebind || !alreadyBound) {
|
unbind();
|
bind(bestComponent, bestVersion, userId);
|
}
|
}
|
|
private void bind(ComponentName component, int version, int userId) {
|
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
|
|
Intent intent = new Intent(mAction);
|
intent.setComponent(component);
|
|
mBestComponent = component;
|
mBestVersion = version;
|
mBestUserId = userId;
|
|
if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")");
|
mContext.bindServiceAsUser(intent, this,
|
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
|
UserHandle.of(userId));
|
}
|
|
private void unbind() {
|
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
|
|
if (mBestComponent != null) {
|
if (D) Log.d(mTag, "unbinding " + mBestComponent);
|
mContext.unbindService(this);
|
}
|
|
mBestComponent = null;
|
mBestVersion = Integer.MIN_VALUE;
|
mBestUserId = UserHandle.USER_NULL;
|
}
|
|
/**
|
* Runs the given function asynchronously if currently connected. Suppresses any RemoteException
|
* thrown during execution.
|
*/
|
public final void runOnBinder(BinderRunner runner) {
|
runOnHandler(() -> {
|
if (mBestService == null) {
|
return;
|
}
|
|
try {
|
runner.run(mBestService);
|
} catch (RuntimeException e) {
|
// the code being run is privileged, but may be outside the system server, and thus
|
// we cannot allow runtime exceptions to crash the system server
|
Log.e(TAG, "exception while while running " + runner + " on " + mBestService
|
+ " from " + this, e);
|
} catch (RemoteException e) {
|
// do nothing
|
}
|
});
|
}
|
|
/**
|
* Runs the given function synchronously if currently connected, and returns the default value
|
* if not currently connected or if any exception is thrown.
|
*
|
* @deprecated Using this function is an indication that your AIDL API is broken. Calls from
|
* system server to outside MUST be one-way, and so cannot return any result, and this
|
* method should not be needed or used. Use a separate callback interface to allow outside
|
* components to return results back to the system server.
|
*/
|
@Deprecated
|
public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) {
|
try {
|
return runOnHandlerBlocking(() -> {
|
if (mBestService == null) {
|
return defaultValue;
|
}
|
|
try {
|
return runner.run(mBestService);
|
} catch (RemoteException e) {
|
return defaultValue;
|
}
|
});
|
} catch (InterruptedException e) {
|
return defaultValue;
|
}
|
}
|
|
@Override
|
public final void onServiceConnected(ComponentName component, IBinder binder) {
|
runOnHandler(() -> {
|
if (D) Log.d(mTag, component + " connected");
|
mBestService = binder;
|
onBind();
|
});
|
}
|
|
@Override
|
public final void onServiceDisconnected(ComponentName component) {
|
runOnHandler(() -> {
|
if (D) Log.d(mTag, component + " disconnected");
|
mBestService = null;
|
onUnbind();
|
});
|
}
|
|
@Override
|
public String toString() {
|
ComponentName bestComponent = mBestComponent;
|
return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion;
|
}
|
|
private void runOnHandler(Runnable r) {
|
if (Looper.myLooper() == mHandler.getLooper()) {
|
r.run();
|
} else {
|
mHandler.post(r);
|
}
|
}
|
|
private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException {
|
if (Looper.myLooper() == mHandler.getLooper()) {
|
try {
|
return c.call();
|
} catch (Exception e) {
|
// Function cannot throw exception, this should never happen
|
throw new IllegalStateException(e);
|
}
|
} else {
|
FutureTask<T> task = new FutureTask<>(c);
|
mHandler.post(task);
|
try {
|
return task.get();
|
} catch (ExecutionException e) {
|
// Function cannot throw exception, this should never happen
|
throw new IllegalStateException(e);
|
}
|
}
|
}
|
}
|