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
This commit is contained in:
Jeremy Joslin
2016-03-14 11:17:41 -07:00
parent a68fe1e4d3
commit dd251ef495
4 changed files with 226 additions and 36 deletions

View File

@ -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;

View File

@ -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.second));
}), 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(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;
}
}
}

View File

@ -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) {
// Package change has invalidated a scorer.
Log.i(TAG, "Package " + mRegisteredPackage +
" is no longer valid, disabling scoring");
setScorerInternal(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.");
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);
}
}
}

View File

@ -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);
}
});