API for network cache subsystems.
A network subsystem (e.g. Wi-Fi) will be responsible for implementing
INetworkScoreCache. NetworkScoreService will load caches for each
configured network subsystem. When it receives a call to update
scores, it will separate the networks by type and call updateScores on
the appropriate INetworkScoreCache.
Each subsystem will also be responsible for requesting scores for new
networks by calling NetworkScoreManager#requestScores.
Bug: 14408144
Bug: 14137255
Change-Id: I76e5e5205bc359c39d6b9c1782126fb7f465cd96
diff --git a/Android.mk b/Android.mk
index 232f5bf..81d44b6f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -155,6 +155,7 @@
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
+ core/java/android/net/INetworkScoreCache.aidl \
core/java/android/net/INetworkScoreService.aidl \
core/java/android/net/INetworkStatsService.aidl \
core/java/android/net/INetworkStatsSession.aidl \
diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl
new file mode 100644
index 0000000..35601ce
--- /dev/null
+++ b/core/java/android/net/INetworkScoreCache.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.ScoredNetwork;
+
+/**
+ * A service which stores a subset of scored networks from the active network scorer.
+ *
+ * <p>To be implemented by network subsystems (e.g. Wi-Fi). NetworkScoreService will propagate
+ * scores down to each subsystem depending on the network type. Implementations may register for
+ * a given network type by calling NetworkScoreManager.registerNetworkSubsystem.
+ *
+ * <p>A proper implementation should throw SecurityException whenever the caller is not privileged.
+ * It may request scores by calling NetworkScoreManager#requestScores(NetworkKey[]); a call to
+ * updateScores may follow but may not depending on the active scorer's implementation, and in
+ * general this method may be called at any time.
+ *
+ * <p>Implementations should also override dump() so that "adb shell dumpsys network_score" includes
+ * the current scores for each network for debugging purposes.
+ * @hide
+ */
+interface INetworkScoreCache
+{
+ void updateScores(in List<ScoredNetwork> networks);
+
+ void clearScores();
+}
+
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index a72d9a0..626bd2a 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -16,6 +16,7 @@
package android.net;
+import android.net.INetworkScoreCache;
import android.net.ScoredNetwork;
/**
@@ -34,8 +35,7 @@
/**
* Clear all scores.
* @return whether the clear was successful.
- * @throws SecurityException if the caller is neither the current active scorer nor the scorer
- * manager.
+ * @throws SecurityException if the caller is neither the current active scorer nor the system.
*/
boolean clearScores();
@@ -43,7 +43,19 @@
* Set the active scorer and clear existing scores.
* @param packageName the package name of the new scorer to use.
* @return true if the operation succeeded, or false if the new package is not a valid scorer.
- * @throws SecurityException if the caller is not the scorer manager.
+ * @throws SecurityException if the caller is not the system.
*/
boolean setActiveScorer(in String packageName);
+
+ /**
+ * Register a network subsystem for scoring.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller is not the system.
+ * @throws IllegalArgumentException if a score cache is already registed for this type.
+ * @hide
+ */
+ void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
+
}
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 6dd56d9..352512e 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -19,6 +19,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
+import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -101,7 +102,7 @@
* determine the current scorer and offer the user the ability to select a different scorer via
* the {@link #ACTION_CHANGE_ACTIVE} intent.
* @return the full package name of the current active scorer, or null if there is no active
- * scorer.
+ * scorer.
*/
public String getActiveScorerPackage() {
return NetworkScorerAppManager.getActiveScorer(mContext);
@@ -151,8 +152,8 @@
*
* @return true if the operation succeeded, or false if the new package is not a valid scorer.
* @throws SecurityException if the caller does not hold the
- * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating that
- * it can manage scorer applications.
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission indicating
+ * that it can manage scorer applications.
* @hide
*/
public boolean setActiveScorer(String packageName) throws SecurityException {
@@ -162,4 +163,44 @@
return false;
}
}
+
+ /**
+ * Request scoring for networks.
+ *
+ * <p>Note that this is just a helper method to assemble the broadcast, and will run in the
+ * calling process.
+ *
+ * @return true if the broadcast was sent, or false if there is no active scorer.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * @hide
+ */
+ public boolean requestScores(NetworkKey[] networks) throws SecurityException {
+ String activeScorer = getActiveScorerPackage();
+ if (activeScorer == null) {
+ return false;
+ }
+ Intent intent = new Intent(ACTION_SCORE_NETWORKS);
+ intent.setPackage(activeScorer);
+ intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks);
+ mContext.sendBroadcast(intent);
+ return true;
+ }
+
+ /**
+ * Register a network score cache.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission.
+ * @throws IllegalArgumentException if a score cache is already registered for this type.
+ * @hide
+ */
+ public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ try {
+ mService.registerNetworkScoreCache(networkType, scoreCache);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java
index 33e81c2..dd744d3 100644
--- a/core/java/android/net/RssiCurve.java
+++ b/core/java/android/net/RssiCurve.java
@@ -98,6 +98,27 @@
}
/**
+ * Lookup the score for a given RSSI value.
+ *
+ * @param rssi The RSSI to lookup. If the RSSI falls below the start of the curve, the score at
+ * the start of the curve will be returned. If it falls after the end of the curve, the
+ * score at the end of the curve will be returned.
+ * @return the score for the given RSSI.
+ */
+ public byte lookupScore(int rssi) {
+ int index = (rssi - start) / bucketWidth;
+
+ // Snap the index to the closest bucket if it falls outside the curve.
+ if (index < 0) {
+ index = 0;
+ } else if (index > rssiBuckets.length - 1) {
+ index = rssiBuckets.length - 1;
+ }
+
+ return rssiBuckets[index];
+ }
+
+ /**
* Determine if two RSSI curves are defined in the same way.
*
* <p>Note that two curves can be equivalent but defined differently, e.g. if one bucket in one
diff --git a/core/tests/coretests/src/android/net/RssiCurveTest.java b/core/tests/coretests/src/android/net/RssiCurveTest.java
new file mode 100644
index 0000000..d4438df
--- /dev/null
+++ b/core/tests/coretests/src/android/net/RssiCurveTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net;
+
+import junit.framework.TestCase;
+
+public class RssiCurveTest extends TestCase {
+ public void testLookupScore_constantCurve() {
+ RssiCurve curve = new RssiCurve(-100, 200, new byte[] { 10 });
+ assertEquals(10, curve.lookupScore(-200));
+ assertEquals(10, curve.lookupScore(-100));
+ assertEquals(10, curve.lookupScore(0));
+ assertEquals(10, curve.lookupScore(100));
+ assertEquals(10, curve.lookupScore(200));
+ }
+
+ public void testLookupScore_changingCurve() {
+ RssiCurve curve = new RssiCurve(-100, 100, new byte[] { -10, 10 });
+ assertEquals(-10, curve.lookupScore(-200));
+ assertEquals(-10, curve.lookupScore(-100));
+ assertEquals(-10, curve.lookupScore(-50));
+ assertEquals(10, curve.lookupScore(0));
+ assertEquals(10, curve.lookupScore(50));
+ assertEquals(10, curve.lookupScore(100));
+ assertEquals(10, curve.lookupScore(200));
+ }
+}
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 8a30e50..4f0c9b5 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -20,19 +20,24 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.net.INetworkScoreCache;
import android.net.INetworkScoreService;
-import android.net.NetworkKey;
import android.net.NetworkScorerAppManager;
-import android.net.RssiCurve;
import android.net.ScoredNetwork;
+import android.os.RemoteException;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Backing service for {@link android.net.NetworkScoreManager}.
@@ -46,12 +51,11 @@
private final Context mContext;
- // TODO: Delete this temporary class once we have a real place for scores.
- private final Map<NetworkKey, RssiCurve> mScoredNetworks;
+ private final Map<Integer, INetworkScoreCache> mScoreCaches;
public NetworkScoreService(Context context) {
mContext = context;
- mScoredNetworks = new HashMap<>();
+ mScoreCaches = new HashMap<>();
}
/** Called when the system is ready to run third-party code but before it actually does so. */
@@ -76,10 +80,31 @@
" is not the active scorer.");
}
- // TODO: Propagate these scores down to the network subsystem layer instead of just holding
- // them in memory.
+ // Separate networks by type.
+ Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
for (ScoredNetwork network : networks) {
- mScoredNetworks.put(network.networkKey, network.rssiCurve);
+ List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
+ if (networkList == null) {
+ networkList = new ArrayList<>();
+ networksByType.put(network.networkKey.type, networkList);
+ }
+ networkList.add(network);
+ }
+
+ // Pass the scores of each type down to the appropriate network scorer.
+ for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+ INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
+ if (scoreCache != null) {
+ try {
+ scoreCache.updateScores(entry.getValue());
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+ }
+ }
+ } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
+ }
}
return true;
@@ -112,8 +137,29 @@
/** Clear scores. Callers are responsible for checking permissions as appropriate. */
private void clearInternal() {
- // TODO: Propagate the flush down to the network subsystem layer.
- mScoredNetworks.clear();
+ Set<INetworkScoreCache> cachesToClear = getScoreCaches();
+
+ for (INetworkScoreCache scoreCache : cachesToClear) {
+ try {
+ scoreCache.clearScores();
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to clear scores", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG);
+ synchronized (mScoreCaches) {
+ if (mScoreCaches.containsKey(networkType)) {
+ throw new IllegalArgumentException(
+ "Score cache already registered for type " + networkType);
+ }
+ mScoreCaches.put(networkType, scoreCache);
+ }
}
@Override
@@ -125,12 +171,28 @@
return;
}
writer.println("Current scorer: " + currentScorer);
- if (mScoredNetworks.isEmpty()) {
- writer.println("No networks scored.");
- } else {
- for (Map.Entry<NetworkKey, RssiCurve> entry : mScoredNetworks.entrySet()) {
- writer.println(entry.getKey() + ": " + entry.getValue());
+
+ for (INetworkScoreCache scoreCache : getScoreCaches()) {
+ try {
+ scoreCache.asBinder().dump(fd, args);
+ } catch (RemoteException e) {
+ writer.println("Unable to dump score cache");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to dump score cache", e);
+ }
}
}
}
+
+ /**
+ * Returns a set of all score caches that are currently active.
+ *
+ * <p>May be used to perform an action on all score caches without potentially strange behavior
+ * if a new scorer is registered during that action's execution.
+ */
+ private Set<INetworkScoreCache> getScoreCaches() {
+ synchronized (mScoreCaches) {
+ return new HashSet<>(mScoreCaches.values());
+ }
+ }
}