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
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 879bb6f..2a78f90 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -18,10 +18,12 @@
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.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 @@
*/
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 @@
@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 @@
/** 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 @@
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 @@
}
}
+ 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 @@
/** 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 @@
// 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 @@
return;
}
writer.println("Current scorer: " + currentScorer.mPackageName);
- writer.flush();
for (INetworkScoreCache scoreCache : getScoreCaches()) {
try {
@@ -307,6 +368,12 @@
}
}
}
+ if (mServiceConnection != null) {
+ mServiceConnection.dump(fd, writer, args);
+ } else {
+ writer.println("ScoringServiceConnection: null");
+ }
+ writer.flush();
}
/**
@@ -320,4 +387,50 @@
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);
+ }
+ }
}