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

Commit b770ed13 authored by Jeremy Joslin's avatar Jeremy Joslin Committed by Android (Google) Code Review
Browse files

Merge "Have the NetworkScoreService bind to the scorer." into nyc-dev

parents c05a7a04 dd251ef4
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
@@ -1424,6 +1424,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);
            }
        });