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