Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58482c55 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Make ServiceWatcher encryption-aware.

This utility class automates the process of binding to the best
matching service, including rebinding as packages change over time.

This design means it's easy to become encryption-aware: we use the
default PackageManager behavior that filters components based on
their encryption-aware status, and we just kick off another
evaluation pass once the user is unlocked.

This change cleans up some of the internal logic so we only rebind
when the implementation switches, and it fixes several bugs along
the way.  For example, we would never trigger a rebind if a service
was refactored to a different ComponentName.  Another subtle bug was
that we'd never fallback to an older implementation if a higher
version was uninstalled/disabled.  And finally, if all implementations
were uninstalled/disabled, we'd leave the last connection bound.

Bug: 26280056
Change-Id: I259af78e6564d61353a772ac03cf5799a398d535
parent b251a2f5
Loading
Loading
Loading
Loading
+122 −97
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server;

import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -32,13 +33,16 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;

/**
 * Find the best Service, and bind to it.
@@ -64,17 +68,21 @@ public class ServiceWatcher implements ServiceConnection {
    private final Runnable mNewServiceWork;
    private final Handler mHandler;

    private Object mLock = new Object();
    private final 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;
    @GuardedBy("mLock")
    private int mCurrentUserId = UserHandle.USER_SYSTEM;

    @GuardedBy("mLock")
    private IBinder mBoundService;
    @GuardedBy("mLock")
    private ComponentName mBoundComponent;
    @GuardedBy("mLock")
    private String mBoundPackageName;
    @GuardedBy("mLock")
    private int mBoundVersion = Integer.MIN_VALUE;
    @GuardedBy("mLock")
    private int mBoundUserId = UserHandle.USER_NULL;

    public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
            List<String> initialPackageNames) {
@@ -84,7 +92,9 @@ public class ServiceWatcher implements ServiceConnection {
            String pkg = initialPackageNames.get(i);
            try {
                HashSet<Signature> set = new HashSet<Signature>();
                Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
                Signature[] sigs = pm.getPackageInfo(pkg,
                        PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE
                                | PackageManager.GET_SIGNATURES).signatures;
                set.addAll(Arrays.asList(sigs));
                sigSets.add(set);
            } catch (NameNotFoundException e) {
@@ -126,20 +136,32 @@ public class ServiceWatcher implements ServiceConnection {
        mSignatureSets = getSignatureSets(context, initialPackageNames);
    }

    /**
     * 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.
     */
    public boolean start() {
        synchronized (mLock) {
            if (!bindBestPackageLocked(mServicePackageName)) return false;
            bindBestPackageLocked(mServicePackageName, false);
        }

        // listen for user change
        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) {
                String action = intent.getAction();
                final String action = intent.getAction();
                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                        UserHandle.USER_NULL);
                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                    switchUser();
                    switchUser(userId);
                } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
                    unlockUser(userId);
                }
            }
        }, UserHandle.ALL, intentFilter, null, mHandler);
@@ -153,30 +175,36 @@ public class ServiceWatcher implements ServiceConnection {
    }

    /**
     * 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.
     * Searches and binds to the best package, or do nothing if the best package
     * is already bound, unless force rebinding is requested.
     *
     * @param justCheckThisPackage Only consider this package, or consider all
     *            packages if it is {@code null}.
     * @param forceRebind Force a rebinding to the best package if it's already
     *            bound.
     * @return {@code true} if a valid package was found to bind to.
     */
    private boolean bindBestPackageLocked(String justCheckThisPackage) {
    private boolean bindBestPackageLocked(String justCheckThisPackage, boolean forceRebind) {
        Intent intent = new Intent(mAction);
        if (justCheckThisPackage != null) {
            intent.setPackage(justCheckThisPackage);
        }
        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
                PackageManager.GET_META_DATA, UserHandle.USER_SYSTEM);
        final List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
                PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                mCurrentUserId);
        int bestVersion = Integer.MIN_VALUE;
        String bestPackage = null;
        ComponentName bestComponent = null;
        boolean bestIsMultiuser = false;
        if (rInfos != null) {
            for (ResolveInfo rInfo : rInfos) {
                String packageName = rInfo.serviceInfo.packageName;
                final ComponentName component = rInfo.serviceInfo.getComponentName();
                final String packageName = component.getPackageName();

                // check signature
                try {
                    PackageInfo pInfo;
                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES
                            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
                    if (!isSignatureMatch(pInfo.signatures)) {
                        Log.w(mTag, packageName + " resolves service " + mAction
                                + ", but has wrong signature, ignoring");
@@ -196,9 +224,9 @@ public class ServiceWatcher implements ServiceConnection {
                    isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
                }

                if (version > mVersion) {
                if (version > bestVersion) {
                    bestVersion = version;
                    bestPackage = packageName;
                    bestComponent = component;
                    bestIsMultiuser = isMultiuser;
                }
            }
@@ -207,42 +235,53 @@ public class ServiceWatcher implements ServiceConnection {
                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)));
                        (bestComponent == null ? "no new best component"
                                : "new best component: " + bestComponent)));
            }
        } else {
            if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
        }
        if (bestPackage != null) {
            bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
            return true;
        }

        if (bestComponent == null) {
            Slog.w(mTag, "Odd, no component found for service " + mAction);
            unbindLocked();
            return false;
        }

        final int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
        final boolean alreadyBound = Objects.equals(bestComponent, mBoundComponent)
                && bestVersion == mBoundVersion && userId == mBoundUserId;
        if (forceRebind || !alreadyBound) {
            unbindLocked();
            bindToPackageLocked(bestComponent, bestVersion, userId);
        }
        return true;
    }

    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);
        ComponentName component;
        component = mBoundComponent;
        mBoundComponent = null;
        mBoundPackageName = null;
        mBoundVersion = Integer.MIN_VALUE;
        mBoundUserId = UserHandle.USER_NULL;
        if (component != null) {
            if (D) Log.d(mTag, "unbinding " + component);
            mContext.unbindService(this);
        }
    }

    private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
        unbindLocked();
    private void bindToPackageLocked(ComponentName component, int version, int userId) {
        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.SYSTEM : UserHandle.CURRENT);
        intent.setComponent(component);
        mBoundComponent = component;
        mBoundPackageName = component.getPackageName();
        mBoundVersion = version;
        mBoundUserId = 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,
                new UserHandle(userId));
    }

    public static boolean isSignatureMatch(Signature[] signatures,
@@ -275,106 +314,92 @@ public class ServiceWatcher implements ServiceConnection {
        @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);
                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
                bindBestPackageLocked(null, forceRebind);
            }
        }

        @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);
                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
                bindBestPackageLocked(null, forceRebind);
            }
        }

        @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);
                }
                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
                bindBestPackageLocked(null, forceRebind);
            }
        }

        @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);
                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
                bindBestPackageLocked(null, forceRebind);
            }
            return super.onPackageChanged(packageName, uid, components);
        }
    };

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
    public void onServiceConnected(ComponentName component, IBinder binder) {
        synchronized (mLock) {
            String packageName = name.getPackageName();
            if (packageName.equals(mPackageName)) {
                if (D) Log.d(mTag, packageName + " connected");
                mBinder = binder;
            if (component.equals(mBoundComponent)) {
                if (D) Log.d(mTag, component + " connected");
                mBoundService = binder;
                if (mHandler !=null && mNewServiceWork != null) {
                    mHandler.post(mNewServiceWork);
                }
            } else {
                Log.w(mTag, "unexpected onServiceConnected: " + packageName);
                Log.w(mTag, "unexpected onServiceConnected: " + component);
            }
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    public void onServiceDisconnected(ComponentName component) {
        synchronized (mLock) {
            String packageName = name.getPackageName();
            if (D) Log.d(mTag, packageName + " disconnected");
            if (D) Log.d(mTag, component + " disconnected");

            if (packageName.equals(mPackageName)) {
                mBinder = null;
            if (component.equals(mBoundComponent)) {
                mBoundService = null;
            }
        }
    }

    public String getBestPackageName() {
    public @Nullable String getBestPackageName() {
        synchronized (mLock) {
            return mPackageName;
            return mBoundPackageName;
        }
    }

    public int getBestVersion() {
        synchronized (mLock) {
            return mVersion;
            return mBoundVersion;
        }
    }

    public IBinder getBinder() {
    public @Nullable IBinder getBinder() {
        synchronized (mLock) {
            return mBinder;
            return mBoundService;
        }
    }

    public void switchUser() {
    public void switchUser(int userId) {
        synchronized (mLock) {
            if (!mIsMultiuser) {
                unbindLocked();
                bindBestPackageLocked(mServicePackageName);
            mCurrentUserId = userId;
            bindBestPackageLocked(mServicePackageName, false);
        }
    }

    public void unlockUser(int userId) {
        synchronized (mLock) {
            if (userId == mCurrentUserId) {
                bindBestPackageLocked(mServicePackageName, false);
            }
        }
    }