Java程序  |  462行  |  16.67 KB

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