From 14f1ec05b2add5ee051c0d2e7c7c3b36a6e77b92 Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Tue, 29 Apr 2014 11:58:26 -0700 Subject: [PATCH] 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 --- Android.mk | 1 + core/java/android/net/INetworkScoreCache.aidl | 43 +++++++++ .../android/net/INetworkScoreService.aidl | 18 +++- .../java/android/net/NetworkScoreManager.java | 47 +++++++++- core/java/android/net/RssiCurve.java | 21 +++++ .../src/android/net/RssiCurveTest.java | 41 +++++++++ .../android/server/NetworkScoreService.java | 92 ++++++++++++++++--- 7 files changed, 242 insertions(+), 21 deletions(-) create mode 100644 core/java/android/net/INetworkScoreCache.aidl create mode 100644 core/tests/coretests/src/android/net/RssiCurveTest.java diff --git a/Android.mk b/Android.mk index 232f5bf24dc7b..81d44b6f369bf 100644 --- a/Android.mk +++ b/Android.mk @@ -155,6 +155,7 @@ LOCAL_SRC_FILES += \ 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 0000000000000..35601ce8b138a --- /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. + * + *

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. + * + *

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. + * + *

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 networks); + + void clearScores(); +} + diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl index a72d9a0249767..626bd2af2f2e9 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 @@ interface INetworkScoreService /** * 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 @@ interface INetworkScoreService * 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 6dd56d9fb892f..352512e5f2a0c 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -19,6 +19,7 @@ package android.net; 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 @@ public class NetworkScoreManager { * 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 @@ public class NetworkScoreManager { * * @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 @@ public class NetworkScoreManager { return false; } } + + /** + * Request scoring for networks. + * + *

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 33e81c257e23c..dd744d351fc0a 100644 --- a/core/java/android/net/RssiCurve.java +++ b/core/java/android/net/RssiCurve.java @@ -97,6 +97,27 @@ public class RssiCurve implements Parcelable { out.writeByteArray(rssiBuckets); } + /** + * 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. * 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 0000000000000..d4438df6a3524 --- /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 8a30e50c9aaf7..4f0c9b537ff22 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.Manifest.permission; 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 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private final Context mContext; - // TODO: Delete this temporary class once we have a real place for scores. - private final Map mScoredNetworks; + private final Map 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 @@ public class NetworkScoreService extends INetworkScoreService.Stub { " 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> networksByType = new HashMap<>(); for (ScoredNetwork network : networks) { - mScoredNetworks.put(network.networkKey, network.rssiCurve); + List 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> 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 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** 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 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 @@ public class NetworkScoreService extends INetworkScoreService.Stub { return; } writer.println("Current scorer: " + currentScorer); - if (mScoredNetworks.isEmpty()) { - writer.println("No networks scored."); - } else { - for (Map.Entry 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. + * + *

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 getScoreCaches() { + synchronized (mScoreCaches) { + return new HashSet<>(mScoreCaches.values()); + } + } }