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 \
--- /dev/null
+/**
+ * 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);
+}
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. */
}
@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:
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.
* <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
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;
+ }
}
}
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;
*
* @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.
* @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;
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.
*
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();
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.
*
}
@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 + "]";
}
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
}
@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 + "]";
}
<!-- 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>
<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" />
import java.util.Iterator;
-public class NetworkScorerApplicationTest extends InstrumentationTestCase {
+public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
@Mock private Context mMockContext;
@Mock private PackageManager mMockPm;
setScorers(package1, package2, package3);
Iterator<String> result =
- NetworkScorerApplication.getAllValidScorers(mMockContext).iterator();
+ NetworkScorerAppManager.getAllValidScorers(mMockContext).iterator();
assertTrue(result.hasNext());
assertEquals("package1", result.next());
--- /dev/null
+/*
+ * 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());
+ }
+ }
+ }
+}
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
+ NetworkScoreService networkScore = null;
NsdService serviceDiscovery= null;
IPackageManager pm = null;
WindowManagerService wm = null;
}
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(
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;
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);