Initial implementation of NetworkScoreManager's backing service.

This service will ultimately be responsible for propagating scores
down to lower-level network subsystems. For now, it just keeps scores
in memory and exposes these for debugging purposes via "adb shell
dumpsys network_score".

This change also adds provisioning of a default scorer. When
NetworkScoreService is first initialized, it checks to see if it has
ever set a default scorer; if not, it reads a package name from a
build config property and attempts to set it as the default.

Also add autogenerated equals/hashCode methods to all parcelables.

Bug: 14111427
Bug: 13786258
Change-Id: I02271171653d42e12acd240b73b9e23950744f6b
This commit is contained in:
Jeff Davidson
2014-04-16 17:29:40 -07:00
parent 4660c9e064
commit 6a4b220f12
13 changed files with 365 additions and 20 deletions

View File

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

View File

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

View File

@@ -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. */
@@ -78,6 +86,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) {

View File

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

View File

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

View File

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

View File

@@ -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.
*
@@ -79,6 +81,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 + "]";

View File

@@ -19,6 +19,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -86,6 +87,21 @@ public class WifiKey implements Parcelable {
out.writeString(bssid);
}
@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 + "]";

View File

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

View File

@@ -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" />

View File

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

View File

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

View File

@@ -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;
@@ -642,6 +643,14 @@ public final class SystemServer {
reportWtf("starting Connectivity Service", e);
}
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);
@@ -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;
@@ -1068,6 +1078,11 @@ public final class SystemServer {
} catch (Throwable e) {
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) {