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

Commit dd251ef4 authored by Jeremy Joslin's avatar Jeremy Joslin
Browse files

Have the NetworkScoreService bind to the scorer.

If the current active scorer provides a service that can handle the
android.net.scoring.SCORE_NETWORKS action then the NetworkScoreService
will bind to that service to keep the scorer alive. If no service is
discovered then no attempt to bind will be made.

BUG: 27612145
Change-Id: I3f6ed0cbd83e658f1533c3e37b0cac2692c01761
parent a68fe1e4
Loading
Loading
Loading
Loading
+32 −5
Original line number Diff line number Diff line
@@ -19,11 +19,9 @@ package android.net;
import android.Manifest;
import android.Manifest.permission;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
@@ -69,12 +67,32 @@ public final class NetworkScorerAppManager {
         */
        public final String mConfigurationActivityClassName;

        /**
         * Optional class name of the scoring service we can bind to. Null if none is set.
         */
        public final String mScoringServiceClassName;

        public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName,
                @Nullable String configurationActivityClassName) {
                @Nullable String configurationActivityClassName,
                @Nullable String scoringServiceClassName) {
            mScorerName = scorerName;
            mPackageName = packageName;
            mPackageUid = packageUid;
            mConfigurationActivityClassName = configurationActivityClassName;
            mScoringServiceClassName = scoringServiceClassName;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("NetworkScorerAppData{");
            sb.append("mPackageName='").append(mPackageName).append('\'');
            sb.append(", mPackageUid=").append(mPackageUid);
            sb.append(", mScorerName=").append(mScorerName);
            sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName)
                    .append('\'');
            sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\'');
            sb.append('}');
            return sb.toString();
        }
    }

@@ -128,18 +146,27 @@ public final class NetworkScorerAppManager {
            Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE);
            intent.setPackage(receiverInfo.packageName);
            List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */);
            if (!configActivities.isEmpty()) {
            if (configActivities != null && !configActivities.isEmpty()) {
                ActivityInfo activityInfo = configActivities.get(0).activityInfo;
                if (activityInfo != null) {
                    configurationActivityClassName = activityInfo.name;
                }
            }

            // Find the scoring service class we can bind to, if any.
            String scoringServiceClassName = null;
            Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
            serviceIntent.setPackage(receiverInfo.packageName);
            ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */);
            if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
                scoringServiceClassName = resolveServiceInfo.serviceInfo.name;
            }

            // NOTE: loadLabel will attempt to load the receiver's label and fall back to the
            // app label if none is present.
            scorers.add(new NetworkScorerAppData(receiverInfo.packageName,
                    receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm),
                    configurationActivityClassName));
                    configurationActivityClassName, scoringServiceClassName));
        }

        return scorers;
+63 −19
Original line number Diff line number Diff line
@@ -23,10 +23,10 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
import android.util.Pair;

import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -58,25 +58,26 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase {

    public void testGetAllValidScorers() throws Exception {
        // Package 1 - Valid scorer.
        Pair<ResolveInfo, ResolveInfo> package1 = buildResolveInfo("package1", 1, true, true,
                false);
        ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false);

        // Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission.
        Pair<ResolveInfo, ResolveInfo> package2 = buildResolveInfo("package2", 2, false, true,
                false);
        ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false);

        // Package 3 - App does not have SCORE_NETWORKS permission.
        Pair<ResolveInfo, ResolveInfo> package3 = buildResolveInfo("package3", 3, true, false,
                false);
        ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false);

        // Package 4 - Valid scorer w/ optional config activity.
        Pair<ResolveInfo, ResolveInfo> package4 = buildResolveInfo("package4", 4, true, true, true);
        ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false);

        List<Pair<ResolveInfo, ResolveInfo>> scorers = new ArrayList<>();
        // Package 5 - Valid scorer w/ optional service to bind to.
        ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true);

        List<ResolveInfoHolder> scorers = new ArrayList<>();
        scorers.add(package1);
        scorers.add(package2);
        scorers.add(package3);
        scorers.add(package4);
        scorers.add(package5);
        setScorers(scorers);

        Iterator<NetworkScorerAppData> result =
@@ -94,14 +95,20 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
        assertEquals(4, next.mPackageUid);
        assertEquals(".ConfigActivity", next.mConfigurationActivityClassName);

        assertTrue(result.hasNext());
        next = result.next();
        assertEquals("package5", next.mPackageName);
        assertEquals(5, next.mPackageUid);
        assertEquals(".ScoringService", next.mScoringServiceClassName);

        assertFalse(result.hasNext());
    }

    private void setScorers(List<Pair<ResolveInfo, ResolveInfo>> scorers) {
    private void setScorers(List<ResolveInfoHolder> scorers) {
        List<ResolveInfo> receivers = new ArrayList<>();
        for (final Pair<ResolveInfo, ResolveInfo> scorer : scorers) {
            receivers.add(scorer.first);
            if (scorer.second != null) {
        for (final ResolveInfoHolder scorer : scorers) {
            receivers.add(scorer.scorerResolveInfo);
            if (scorer.configActivityResolveInfo != null) {
                // This scorer has a config activity.
                Mockito.when(mMockPm.queryIntentActivities(
                        Mockito.argThat(new ArgumentMatcher<Intent>() {
@@ -110,10 +117,26 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
                                Intent intent = (Intent) object;
                                return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals(
                                        intent.getAction())
                                        && scorer.first.activityInfo.packageName.equals(
                                        && scorer.scorerResolveInfo.activityInfo.packageName.equals(
                                                intent.getPackage());
                            }
                        }), Mockito.eq(0))).thenReturn(
                                Collections.singletonList(scorer.configActivityResolveInfo));
            }

            if (scorer.serviceResolveInfo != null) {
                // This scorer has a service to bind to
                Mockito.when(mMockPm.resolveService(
                        Mockito.argThat(new ArgumentMatcher<Intent>() {
                            @Override
                            public boolean matches(Object object) {
                                Intent intent = (Intent) object;
                                return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(
                                        intent.getAction())
                                        && scorer.scorerResolveInfo.activityInfo.packageName.equals(
                                        intent.getPackage());
                            }
                        }), Mockito.eq(0))).thenReturn(Collections.singletonList(scorer.second));
                        }), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo);
            }
        }

@@ -128,9 +151,9 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
                .thenReturn(receivers);
    }

    private Pair<ResolveInfo, ResolveInfo> buildResolveInfo(String packageName, int packageUid,
            boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity)
            throws Exception {
    private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid,
            boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity,
            boolean hasServiceInfo) throws Exception {
        Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
                .thenReturn(hasScorePermission ?
                        PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
@@ -150,6 +173,27 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
            configActivityInfo.activityInfo = new ActivityInfo();
            configActivityInfo.activityInfo.name = ".ConfigActivity";
        }
        return Pair.create(resolveInfo, configActivityInfo);

        ResolveInfo serviceInfo = null;
        if (hasServiceInfo) {
            serviceInfo = new ResolveInfo();
            serviceInfo.serviceInfo = new ServiceInfo();
            serviceInfo.serviceInfo.name = ".ScoringService";
        }

        return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo);
    }

    private static class ResolveInfoHolder {
        final ResolveInfo scorerResolveInfo;
        final ResolveInfo configActivityResolveInfo;
        final ResolveInfo serviceResolveInfo;

        public ResolveInfoHolder(ResolveInfo scorerResolveInfo,
                ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) {
            this.scorerResolveInfo = scorerResolveInfo;
            this.configActivityResolveInfo = configActivityResolveInfo;
            this.serviceResolveInfo = serviceResolveInfo;
        }
    }
}
+125 −12
Original line number Diff line number Diff line
@@ -18,10 +18,12 @@ package com.android.server;

import android.Manifest.permission;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.INetworkScoreCache;
import android.net.INetworkScoreService;
@@ -30,6 +32,7 @@ import android.net.NetworkScorerAppManager;
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.net.ScoredNetwork;
import android.os.Binder;
import android.os.IBinder;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -55,17 +58,17 @@ import java.util.Set;
 */
public class NetworkScoreService extends INetworkScoreService.Stub {
    private static final String TAG = "NetworkScoreService";
    private static final boolean DBG = false;

    private final Context mContext;

    private final Map<Integer, INetworkScoreCache> mScoreCaches;

    /** Lock used to update mReceiver when scorer package changes occur. */
    private Object mReceiverLock = new Object[0];
    private final Object mReceiverLock = new Object[0];

    /** Clears scores when the active scorer package is no longer valid. */
    @GuardedBy("mReceiverLock")
    private ScorerChangedReceiver mReceiver;
    private ScoringServiceConnection mServiceConnection;

    private class ScorerChangedReceiver extends BroadcastReceiver {
        final String mRegisteredPackage;
@@ -77,14 +80,23 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if ((Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
                    Intent.ACTION_PACKAGE_REPLACED.equals(action) ||
                    Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) &&
                    NetworkScorerAppManager.getActiveScorer(mContext) == null) {
            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
                    || Intent.ACTION_PACKAGE_REPLACED.equals(action)
                    || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                NetworkScorerAppData activeScorer =
                        NetworkScorerAppManager.getActiveScorer(mContext);
                if (activeScorer == null) {
                    // Package change has invalidated a scorer.
                    Log.i(TAG, "Package " + mRegisteredPackage +
                        " is no longer valid, disabling scoring");
                            " is no longer valid, disabling scoring.");
                    setScorerInternal(null);
                } else if (activeScorer.mScoringServiceClassName == null) {
                    // The scoring service is not available, make sure it's unbound.
                    unbindFromScoringServiceIfNeeded();
                } else {
                    // The scoring service may have changed or been added.
                    bindToScoringServiceIfNeeded(activeScorer);
                }
            }
        }
    }
@@ -96,6 +108,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

    /** Called when the system is ready to run third-party code but before it actually does so. */
    void systemReady() {
        if (DBG) Log.d(TAG, "systemReady");
        ContentResolver cr = mContext.getContentResolver();
        if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
            // On first run, we try to initialize the scorer to the one configured at build time.
@@ -111,7 +124,14 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        registerPackageReceiverIfNeeded();
    }

    /** Called when the system is ready for us to start third-party code. */
    void systemRunning() {
        if (DBG) Log.d(TAG, "systemRunning");
        bindToScoringServiceIfNeeded();
    }

    private void registerPackageReceiverIfNeeded() {
        if (DBG) Log.d(TAG, "registerPackageReceiverIfNeeded");
        NetworkScorerAppData scorer = NetworkScorerAppManager.getActiveScorer(mContext);
        synchronized (mReceiverLock) {
            // Unregister the receiver if the current scorer has changed since last registration.
@@ -142,6 +162,41 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
        }
    }

    private void bindToScoringServiceIfNeeded() {
        if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded");
        NetworkScorerAppData scorerData = NetworkScorerAppManager.getActiveScorer(mContext);
        bindToScoringServiceIfNeeded(scorerData);
    }

    private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
        if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
        if (scorerData != null && scorerData.mScoringServiceClassName != null) {
            ComponentName componentName =
                    new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
            // If we're connected to a different component then drop it.
            if (mServiceConnection != null
                    && !mServiceConnection.mComponentName.equals(componentName)) {
                unbindFromScoringServiceIfNeeded();
            }

            // If we're not connected at all then create a new connection.
            if (mServiceConnection == null) {
                mServiceConnection = new ScoringServiceConnection(componentName);
            }

            // Make sure the connection is connected (idempotent)
            mServiceConnection.connect(mContext);
        }
    }

    private void unbindFromScoringServiceIfNeeded() {
        if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
        if (mServiceConnection != null) {
            mServiceConnection.disconnect(mContext);
        }
        mServiceConnection = null;
    }

    @Override
    public boolean updateScores(ScoredNetwork[] networks) {
        if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) {
@@ -228,8 +283,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub {

    /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
    private boolean setScorerInternal(String packageName) {
        if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
        long token = Binder.clearCallingIdentity();
        try {
            unbindFromScoringServiceIfNeeded();
            // Preemptively clear scores even though the set operation could fail. We do this for
            // safety as scores should never be compared across apps; in practice, Settings should
            // only be allowing valid apps to be set as scorers, so failure here should be rare.
@@ -237,8 +294,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
            // Get the scorer that is about to be replaced, if any, so we can notify it directly.
            NetworkScorerAppData prevScorer = NetworkScorerAppManager.getActiveScorer(mContext);
            boolean result = NetworkScorerAppManager.setActiveScorer(mContext, packageName);
            // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
            // then we'll attempt to restore the previous binding (if any), otherwise an attempt
            // will be made to bind to the new scorer.
            bindToScoringServiceIfNeeded();
            if (result) { // new scorer successfully set
                registerPackageReceiverIfNeeded();

                Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
                if (prevScorer != null) { // Directly notify the old scorer.
                    intent.setPackage(prevScorer.mPackageName);
@@ -295,7 +357,6 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
            return;
        }
        writer.println("Current scorer: " + currentScorer.mPackageName);
        writer.flush();

        for (INetworkScoreCache scoreCache : getScoreCaches()) {
            try {
@@ -307,6 +368,12 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
                }
            }
        }
        if (mServiceConnection != null) {
            mServiceConnection.dump(fd, writer, args);
        } else {
            writer.println("ScoringServiceConnection: null");
        }
        writer.flush();
    }

    /**
@@ -320,4 +387,50 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
            return new HashSet<>(mScoreCaches.values());
        }
    }

    private static class ScoringServiceConnection implements ServiceConnection {
        private final ComponentName mComponentName;
        private boolean mBound = false;

        ScoringServiceConnection(ComponentName componentName) {
            mComponentName = componentName;
        }

        void connect(Context context) {
            disconnect(context);
            Intent service = new Intent();
            service.setComponent(mComponentName);
            mBound = context.bindServiceAsUser(service, this,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
                    UserHandle.SYSTEM);
            if (!mBound) {
                Log.w(TAG, "Bind call failed for " + service);
            }
        }

        void disconnect(Context context) {
            try {
                if (mBound) {
                    mBound = false;
                    context.unbindService(this);
                }
            } catch (RuntimeException e) {
                Log.e(TAG, "Unbind failed.", e);
            }
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            if (DBG) Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
        }

        public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
            writer.println("ScoringServiceConnection: " + mComponentName + ", bound: " + mBound);
        }
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -1423,6 +1423,12 @@ public final class SystemServer {
                } catch (Throwable e) {
                    reportWtf("Notifying MmsService running", e);
                }

                try {
                    if (networkScoreF != null) networkScoreF.systemRunning();
                } catch (Throwable e) {
                    reportWtf("Notifying NetworkScoreService running", e);
                }
                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
            }
        });