diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | core/java/android/net/INetworkScoreService.aidl | 49 | ||||
-rw-r--r-- | core/java/android/net/NetworkKey.java | 23 | ||||
-rw-r--r-- | core/java/android/net/NetworkScoreManager.java | 73 | ||||
-rw-r--r-- | core/java/android/net/NetworkScorerAppManager.java (renamed from core/java/android/net/NetworkScorerApplication.java) | 20 | ||||
-rw-r--r-- | core/java/android/net/RssiCurve.java | 27 | ||||
-rw-r--r-- | core/java/android/net/ScoredNetwork.java | 18 | ||||
-rw-r--r-- | core/java/android/net/WifiKey.java | 16 | ||||
-rw-r--r-- | core/res/res/values/config.xml | 2 | ||||
-rw-r--r-- | core/res/res/values/symbols.xml | 1 | ||||
-rw-r--r-- | core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java (renamed from core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java) | 4 | ||||
-rw-r--r-- | services/core/java/com/android/server/NetworkScoreService.java | 136 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 15 |
13 files changed, 365 insertions, 20 deletions
@@ -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/INetworkScoreService.aidl \ core/java/android/net/INetworkStatsService.aidl \ core/java/android/net/INetworkStatsSession.aidl \ core/java/android/net/nsd/INsdManager.aidl \ diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl new file mode 100644 index 0000000..a72d9a0 --- /dev/null +++ b/core/java/android/net/INetworkScoreService.aidl @@ -0,0 +1,49 @@ +/** + * 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 for updating network scores from a network scorer application. + * @hide + */ +interface INetworkScoreService +{ + /** + * Update scores. + * @return whether the update was successful. + * @throws SecurityException if the caller is not the current active scorer. + */ + boolean updateScores(in ScoredNetwork[] networks); + + /** + * Clear all scores. + * @return whether the clear was successful. + * @throws SecurityException if the caller is neither the current active scorer nor the scorer + * manager. + */ + boolean clearScores(); + + /** + * 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. + */ + boolean setActiveScorer(in String packageName); +} diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index cc3ad3e..bc19658 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -19,11 +19,19 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Information which identifies a specific network. * * @hide */ +// NOTE: Ideally, we would abstract away the details of what identifies a network of a specific +// type, so that all networks appear the same and can be scored without concern to the network type +// itself. However, because no such cross-type identifier currently exists in the Android framework, +// and because systems might obtain information about networks from sources other than Android +// devices, we need to provide identifying details about each specific network type (wifi, cell, +// etc.) so that clients can pull out these details depending on the type of network. public class NetworkKey implements Parcelable { /** A wifi network, for which {@link #wifiKey} will be populated. */ @@ -79,6 +87,21 @@ public class NetworkKey implements Parcelable { } @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NetworkKey that = (NetworkKey) o; + + return type == that.type && Objects.equals(wifiKey, that.wifiKey); + } + + @Override + public int hashCode() { + return Objects.hash(type, wifiKey); + } + + @Override public String toString() { switch (type) { case TYPE_WIFI: diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 3430547..5e61613 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -19,6 +19,9 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; /** * Class that manages communication between network subsystems and a network scorer. @@ -40,7 +43,7 @@ import android.content.Context; * <p>The system keeps track of a default scorer application; at any time, only this application * will receive {@link #ACTION_SCORE_NETWORKS} broadcasts and will be permitted to call * {@link #updateScores}. Applications may determine the current default scorer with - * {@link #getDefaultScorerPackage()} and request to change the default scorer by sending an + * {@link #getActiveScorerPackage()} and request to change the default scorer by sending an * {@link #ACTION_CHANGE_DEFAULT} broadcast with another scorer. * * @hide @@ -81,38 +84,82 @@ public class NetworkScoreManager { public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore"; private final Context mContext; + private final INetworkScoreService mService; /** @hide */ public NetworkScoreManager(Context context) { mContext = context; + IBinder iBinder = ServiceManager.getService(Context.NETWORK_SCORE_SERVICE); + mService = INetworkScoreService.Stub.asInterface(iBinder); } /** - * Obtain the package name of the current default network scorer. + * Obtain the package name of the current active network scorer. * - * At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS} + * <p>At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS} * broadcasts and be allowed to call {@link #updateScores}. Applications may use this method to * determine the current scorer and offer the user the ability to select a different scorer via * the {@link #ACTION_CHANGE_DEFAULT} intent. - * @return the full package name of the current default scorer, or null if there is no active + * @return the full package name of the current active scorer, or null if there is no active * scorer. */ - public String getDefaultScorerPackage() { - // TODO: Implement. - return null; + public String getActiveScorerPackage() { + return NetworkScorerAppManager.getActiveScorer(mContext); } /** * Update network scores. * - * This may be called at any time to re-score active networks. Scores will generally be updated - * quickly, but if this method is called too frequently, the scores may be held and applied at - * a later time. + * <p>This may be called at any time to re-score active networks. Scores will generally be + * updated quickly, but if this method is called too frequently, the scores may be held and + * applied at a later time. * * @param networks the networks which have been scored by the scorer. - * @throws SecurityException if the caller is not the default scorer. + * @return whether the update was successful. + * @throws SecurityException if the caller is not the active scorer. */ - public void updateScores(ScoredNetwork[] networks) throws SecurityException { - // TODO: Implement. + public boolean updateScores(ScoredNetwork[] networks) throws SecurityException { + try { + return mService.updateScores(networks); + } catch (RemoteException e) { + return false; + } + } + + /** + * Clear network scores. + * + * <p>Should be called when all scores need to be invalidated, i.e. because the scoring + * algorithm has changed and old scores can no longer be compared to future scores. + * + * <p>Note that scores will be cleared automatically when the active scorer changes, as scores + * from one scorer cannot be compared to those from another scorer. + * + * @return whether the clear was successful. + * @throws SecurityException if the caller is not the active scorer or privileged. + */ + public boolean clearScores() throws SecurityException { + try { + return mService.clearScores(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Set the active scorer to a new package and clear existing scores. + * + * @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. + * @hide + */ + public boolean setActiveScorer(String packageName) throws SecurityException { + try { + return mService.setActiveScorer(packageName); + } catch (RemoteException e) { + return false; + } } } diff --git a/core/java/android/net/NetworkScorerApplication.java b/core/java/android/net/NetworkScorerAppManager.java index b137ad3..726208a 100644 --- a/core/java/android/net/NetworkScorerApplication.java +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -26,6 +26,7 @@ import android.content.pm.ResolveInfo; import android.provider.Settings; import android.provider.Settings.Global; import android.text.TextUtils; +import android.util.Log; import java.util.ArrayList; import java.util.Collection; @@ -36,13 +37,14 @@ import java.util.List; * * @hide */ -public final class NetworkScorerApplication { +public final class NetworkScorerAppManager { + private static final String TAG = "NetworkScorerAppManager"; private static final Intent SCORE_INTENT = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); /** This class cannot be instantiated. */ - private NetworkScorerApplication() {} + private NetworkScorerAppManager() {} /** * Returns the list of available scorer app package names. @@ -111,30 +113,38 @@ public final class NetworkScorerApplication { * @param context the context of the calling application * @param packageName the packageName of the new scorer to use. If null, scoring will be * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. + * @return true if the scorer was changed, or false if the package is not a valid scorer. */ - public static void setActiveScorer(Context context, String packageName) { + public static boolean setActiveScorer(Context context, String packageName) { String oldPackageName = Settings.Global.getString(context.getContentResolver(), Settings.Global.NETWORK_SCORER_APP); if (TextUtils.equals(oldPackageName, packageName)) { // No change. - return; + return true; } + Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); + if (packageName == null) { Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP, null); + return true; } else { // We only make the change if the new package is valid. Collection<String> applications = getAllValidScorers(context); if (isPackageValidScorer(applications, packageName)) { Settings.Global.putString(context.getContentResolver(), Settings.Global.NETWORK_SCORER_APP, packageName); + return true; + } else { + Log.w(TAG, "Requested network scorer is not valid: " + packageName); + return false; } } } /** Determine whether the application with the given UID is the enabled scorer. */ - public static boolean isCallerDefaultScorer(Context context, int callingUid) { + public static boolean isCallerActiveScorer(Context context, int callingUid) { String defaultApp = getActiveScorer(context); if (defaultApp == null) { return false; diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java index 7af7998..33e81c2 100644 --- a/core/java/android/net/RssiCurve.java +++ b/core/java/android/net/RssiCurve.java @@ -19,6 +19,9 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; +import java.util.Objects; + /** * A curve defining the network score over a range of RSSI values. * @@ -94,6 +97,30 @@ public class RssiCurve implements Parcelable { out.writeByteArray(rssiBuckets); } + /** + * 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 + * curve is split into two buckets in another. For the purpose of this method, these curves are + * not considered equal to each other. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RssiCurve rssiCurve = (RssiCurve) o; + + return start == rssiCurve.start && + bucketWidth == rssiCurve.bucketWidth && + Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets); + } + + @Override + public int hashCode() { + return Objects.hash(start, bucketWidth, rssiBuckets); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java index 8af3c3c..7902313 100644 --- a/core/java/android/net/ScoredNetwork.java +++ b/core/java/android/net/ScoredNetwork.java @@ -19,6 +19,8 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * A network identifier along with a score for the quality of that network. * @@ -80,6 +82,22 @@ public class ScoredNetwork implements Parcelable { } @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ScoredNetwork that = (ScoredNetwork) o; + + return Objects.equals(networkKey, that.networkKey) && + Objects.equals(rssiCurve, that.rssiCurve); + } + + @Override + public int hashCode() { + return Objects.hash(networkKey, rssiCurve); + } + + @Override public String toString() { return "ScoredNetwork[key=" + networkKey + ",score=" + rssiCurve + "]"; } diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java index ffcd85a..9e92e89 100644 --- a/core/java/android/net/WifiKey.java +++ b/core/java/android/net/WifiKey.java @@ -19,6 +19,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; import java.util.regex.Pattern; /** @@ -87,6 +88,21 @@ public class WifiKey implements Parcelable { } @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WifiKey wifiKey = (WifiKey) o; + + return Objects.equals(ssid, wifiKey.ssid) && Objects.equals(bssid, wifiKey.bssid); + } + + @Override + public int hashCode() { + return Objects.hash(ssid, bssid); + } + + @Override public String toString() { return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]"; } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2df5dc1..c610146 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1432,4 +1432,6 @@ <!-- default window inset isRound property --> <bool name="config_windowIsRound">false</bool> + <!-- Package name for default network scorer app; overridden by product overlays. --> + <string name="config_defaultNetworkScorerPackageName"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 03c617a..26efe36 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1625,6 +1625,7 @@ <java-symbol type="bool" name="config_powerDecoupleAutoSuspendModeFromDisplay" /> <java-symbol type="bool" name="config_powerDecoupleInteractiveModeFromDisplay" /> <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" /> + <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> <java-symbol type="layout" name="resolver_list" /> <java-symbol type="id" name="resolver_list" /> diff --git a/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java index 6d5ede8..cac6b93 100644 --- a/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java +++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java @@ -33,7 +33,7 @@ import org.mockito.MockitoAnnotations; import java.util.Iterator; -public class NetworkScorerApplicationTest extends InstrumentationTestCase { +public class NetworkScorerAppManagerTest extends InstrumentationTestCase { @Mock private Context mMockContext; @Mock private PackageManager mMockPm; @@ -64,7 +64,7 @@ public class NetworkScorerApplicationTest extends InstrumentationTestCase { setScorers(package1, package2, package3); Iterator<String> result = - NetworkScorerApplication.getAllValidScorers(mMockContext).iterator(); + NetworkScorerAppManager.getAllValidScorers(mMockContext).iterator(); assertTrue(result.hasNext()); assertEquals("package1", result.next()); diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java new file mode 100644 index 0000000..8a30e50 --- /dev/null +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -0,0 +1,136 @@ +/* + * 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 com.android.server; + +import android.Manifest.permission; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.INetworkScoreService; +import android.net.NetworkKey; +import android.net.NetworkScorerAppManager; +import android.net.RssiCurve; +import android.net.ScoredNetwork; +import android.text.TextUtils; + +import com.android.internal.R; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * Backing service for {@link android.net.NetworkScoreManager}. + * @hide + */ +public class NetworkScoreService extends INetworkScoreService.Stub { + private static final String TAG = "NetworkScoreService"; + + /** SharedPreference bit set to true after the service is first initialized. */ + private static final String PREF_SCORING_PROVISIONED = "is_provisioned"; + + private final Context mContext; + + // TODO: Delete this temporary class once we have a real place for scores. + private final Map<NetworkKey, RssiCurve> mScoredNetworks; + + public NetworkScoreService(Context context) { + mContext = context; + mScoredNetworks = new HashMap<>(); + } + + /** Called when the system is ready to run third-party code but before it actually does so. */ + void systemReady() { + SharedPreferences prefs = mContext.getSharedPreferences(TAG, Context.MODE_PRIVATE); + if (!prefs.getBoolean(PREF_SCORING_PROVISIONED, false)) { + // On first run, we try to initialize the scorer to the one configured at build time. + // This will be a no-op if the scorer isn't actually valid. + String defaultPackage = mContext.getResources().getString( + R.string.config_defaultNetworkScorerPackageName); + if (!TextUtils.isEmpty(defaultPackage)) { + NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage); + } + prefs.edit().putBoolean(PREF_SCORING_PROVISIONED, true).apply(); + } + } + + @Override + public boolean updateScores(ScoredNetwork[] networks) { + if (!NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid())) { + throw new SecurityException("Caller with UID " + getCallingUid() + + " is not the active scorer."); + } + + // TODO: Propagate these scores down to the network subsystem layer instead of just holding + // them in memory. + for (ScoredNetwork network : networks) { + mScoredNetworks.put(network.networkKey, network.rssiCurve); + } + + return true; + } + + @Override + public boolean clearScores() { + // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETWORKS) should + // be allowed to flush all scores. + if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) || + mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) == + PackageManager.PERMISSION_GRANTED) { + clearInternal(); + return true; + } else { + throw new SecurityException( + "Caller is neither the active scorer nor the scorer manager."); + } + } + + @Override + public boolean setActiveScorer(String packageName) { + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG); + // 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. + clearInternal(); + return NetworkScorerAppManager.setActiveScorer(mContext, packageName); + } + + /** 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(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); + String currentScorer = NetworkScorerAppManager.getActiveScorer(mContext); + if (currentScorer == null) { + writer.println("Scoring is disabled."); + 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()); + } + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 912ac4d..f08d69f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -311,6 +311,7 @@ public final class SystemServer { NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; ConnectivityService connectivity = null; + NetworkScoreService networkScore = null; NsdService serviceDiscovery= null; IPackageManager pm = null; WindowManagerService wm = null; @@ -643,6 +644,14 @@ public final class SystemServer { } try { + Slog.i(TAG, "Network Score Service"); + networkScore = new NetworkScoreService(context); + ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore); + } catch (Throwable e) { + reportWtf("starting Network Score Service", e); + } + + try { Slog.i(TAG, "Network Service Discovery Service"); serviceDiscovery = NsdService.create(context); ServiceManager.addService( @@ -1021,6 +1030,7 @@ public final class SystemServer { final NetworkStatsService networkStatsF = networkStats; final NetworkPolicyManagerService networkPolicyF = networkPolicy; final ConnectivityService connectivityF = connectivity; + final NetworkScoreService networkScoreF = networkScore; final DockObserver dockF = dock; final WallpaperManagerService wallpaperF = wallpaper; final InputMethodManagerService immF = imm; @@ -1069,6 +1079,11 @@ public final class SystemServer { reportWtf("making Battery Service ready", e); } try { + if (networkScoreF != null) networkScoreF.systemReady(); + } catch (Throwable e) { + reportWtf("making Network Score Service ready", e); + } + try { if (networkManagementF != null) networkManagementF.systemReady(); } catch (Throwable e) { reportWtf("making Network Managment Service ready", e); |