From 6a4b220f1263d95fdefe6361c2bc87bbb04bbed0 Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Wed, 16 Apr 2014 17:29:40 -0700 Subject: 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 --- Android.mk | 1 + core/java/android/net/INetworkScoreService.aidl | 49 ++++++ core/java/android/net/NetworkKey.java | 23 +++ core/java/android/net/NetworkScoreManager.java | 73 +++++++-- core/java/android/net/NetworkScorerAppManager.java | 166 +++++++++++++++++++++ .../java/android/net/NetworkScorerApplication.java | 156 ------------------- core/java/android/net/RssiCurve.java | 27 ++++ core/java/android/net/ScoredNetwork.java | 18 +++ core/java/android/net/WifiKey.java | 16 ++ core/res/res/values/config.xml | 2 + core/res/res/values/symbols.xml | 1 + .../android/net/NetworkScorerAppManagerTest.java | 101 +++++++++++++ .../android/net/NetworkScorerApplicationTest.java | 101 ------------- .../com/android/server/NetworkScoreService.java | 136 +++++++++++++++++ services/java/com/android/server/SystemServer.java | 15 ++ 15 files changed, 615 insertions(+), 270 deletions(-) create mode 100644 core/java/android/net/INetworkScoreService.aidl create mode 100644 core/java/android/net/NetworkScorerAppManager.java delete mode 100644 core/java/android/net/NetworkScorerApplication.java create mode 100644 core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java delete mode 100644 core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java create mode 100644 services/core/java/com/android/server/NetworkScoreService.java diff --git a/Android.mk b/Android.mk index c2910fd04172..be7e055b2ec8 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/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 000000000000..a72d9a024976 --- /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 cc3ad3ea6374..bc1965874f5e 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. */ @@ -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) { diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 343054712c7b..5e61613f89bb 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; *

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

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

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

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

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/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java new file mode 100644 index 000000000000..726208afc87e --- /dev/null +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -0,0 +1,166 @@ +/* + * 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.Manifest.permission; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +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; +import java.util.List; + +/** + * Internal class for managing the primary network scorer application. + * + * @hide + */ +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 NetworkScorerAppManager() {} + + /** + * Returns the list of available scorer app package names. + * + *

A network scorer is any application which: + *

+ * + * @return the list of scorers, or the empty list if there are no valid scorers. + */ + public static Collection getAllValidScorers(Context context) { + List scorers = new ArrayList<>(); + + PackageManager pm = context.getPackageManager(); + List receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */); + for (ResolveInfo receiver : receivers) { + // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo + final ActivityInfo receiverInfo = receiver.activityInfo; + if (receiverInfo == null) { + // Should never happen with queryBroadcastReceivers, but invalid nonetheless. + continue; + } + if (!permission.BROADCAST_SCORE_NETWORKS.equals(receiverInfo.permission)) { + // Receiver doesn't require the BROADCAST_SCORE_NETWORKS permission, which means + // anyone could trigger network scoring and flood the framework with score requests. + continue; + } + if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != + PackageManager.PERMISSION_GRANTED) { + // Application doesn't hold the SCORE_NETWORKS permission, so the user never + // approved it as a network scorer. + continue; + } + scorers.add(receiverInfo.packageName); + } + + return scorers; + } + + /** + * Get the application package name to use for scoring networks. + * + * @return the scorer package or null if scoring is disabled (including if no scorer was ever + * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because + * it was disabled or uninstalled). + */ + public static String getActiveScorer(Context context) { + String scorerPackage = Settings.Global.getString(context.getContentResolver(), + Global.NETWORK_SCORER_APP); + Collection applications = getAllValidScorers(context); + if (isPackageValidScorer(applications, scorerPackage)) { + return scorerPackage; + } else { + return null; + } + } + + /** + * Set the specified package as the default scorer application. + * + *

The caller must have permission to write to {@link Settings.Global}. + * + * @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 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 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 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 isCallerActiveScorer(Context context, int callingUid) { + String defaultApp = getActiveScorer(context); + if (defaultApp == null) { + return false; + } + AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + try { + appOpsMgr.checkPackage(callingUid, defaultApp); + return true; + } catch (SecurityException e) { + return false; + } + } + + /** Returns true if the given package is a valid scorer. */ + private static boolean isPackageValidScorer(Collection scorerPackageNames, + String packageName) { + return packageName != null && scorerPackageNames.contains(packageName); + } +} diff --git a/core/java/android/net/NetworkScorerApplication.java b/core/java/android/net/NetworkScorerApplication.java deleted file mode 100644 index b137ad31ebf0..000000000000 --- a/core/java/android/net/NetworkScorerApplication.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.Manifest.permission; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.provider.Settings; -import android.provider.Settings.Global; -import android.text.TextUtils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Internal class for managing the primary network scorer application. - * - * @hide - */ -public final class NetworkScorerApplication { - - private static final Intent SCORE_INTENT = - new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); - - /** This class cannot be instantiated. */ - private NetworkScorerApplication() {} - - /** - * Returns the list of available scorer app package names. - * - *

A network scorer is any application which: - *

    - *
  • Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. - *
  • Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the - * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission. - *
- * - * @return the list of scorers, or the empty list if there are no valid scorers. - */ - public static Collection getAllValidScorers(Context context) { - List scorers = new ArrayList<>(); - - PackageManager pm = context.getPackageManager(); - List receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */); - for (ResolveInfo receiver : receivers) { - // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo - final ActivityInfo receiverInfo = receiver.activityInfo; - if (receiverInfo == null) { - // Should never happen with queryBroadcastReceivers, but invalid nonetheless. - continue; - } - if (!permission.BROADCAST_SCORE_NETWORKS.equals(receiverInfo.permission)) { - // Receiver doesn't require the BROADCAST_SCORE_NETWORKS permission, which means - // anyone could trigger network scoring and flood the framework with score requests. - continue; - } - if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != - PackageManager.PERMISSION_GRANTED) { - // Application doesn't hold the SCORE_NETWORKS permission, so the user never - // approved it as a network scorer. - continue; - } - scorers.add(receiverInfo.packageName); - } - - return scorers; - } - - /** - * Get the application package name to use for scoring networks. - * - * @return the scorer package or null if scoring is disabled (including if no scorer was ever - * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because - * it was disabled or uninstalled). - */ - public static String getActiveScorer(Context context) { - String scorerPackage = Settings.Global.getString(context.getContentResolver(), - Global.NETWORK_SCORER_APP); - Collection applications = getAllValidScorers(context); - if (isPackageValidScorer(applications, scorerPackage)) { - return scorerPackage; - } else { - return null; - } - } - - /** - * Set the specified package as the default scorer application. - * - *

The caller must have permission to write to {@link Settings.Global}. - * - * @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. - */ - public static void 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; - } - - if (packageName == null) { - Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP, - null); - } else { - // We only make the change if the new package is valid. - Collection applications = getAllValidScorers(context); - if (isPackageValidScorer(applications, packageName)) { - Settings.Global.putString(context.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, packageName); - } - } - } - - /** Determine whether the application with the given UID is the enabled scorer. */ - public static boolean isCallerDefaultScorer(Context context, int callingUid) { - String defaultApp = getActiveScorer(context); - if (defaultApp == null) { - return false; - } - AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - try { - appOpsMgr.checkPackage(callingUid, defaultApp); - return true; - } catch (SecurityException e) { - return false; - } - } - - /** Returns true if the given package is a valid scorer. */ - private static boolean isPackageValidScorer(Collection scorerPackageNames, - String packageName) { - return packageName != null && scorerPackageNames.contains(packageName); - } -} diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java index 7af7998f2650..33e81c257e23 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. + * + *

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 8af3c3c42de9..790231399b4e 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. * @@ -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 + "]"; diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java index ffcd85aa1b9a..9e92e89b9687 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; /** @@ -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 + "]"; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2df5dc12df82..c610146b7afd 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1432,4 +1432,6 @@ false + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 03c617ac52ca..26efe36a5a7a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1625,6 +1625,7 @@ + diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java new file mode 100644 index 000000000000..cac6b931bf30 --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java @@ -0,0 +1,101 @@ +/* + * 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.Manifest.permission; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.test.InstrumentationTestCase; + +import com.google.android.collect.Lists; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.Iterator; + +public class NetworkScorerAppManagerTest extends InstrumentationTestCase { + @Mock private Context mMockContext; + @Mock private PackageManager mMockPm; + + @Override + public void setUp() throws Exception { + super.setUp(); + + // Configuration needed to make mockito/dexcache work. + System.setProperty("dexmaker.dexcache", + getInstrumentation().getTargetContext().getCacheDir().getPath()); + ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); + Thread.currentThread().setContextClassLoader(newClassLoader); + + MockitoAnnotations.initMocks(this); + Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm); + } + + public void testGetAllValidScorers() throws Exception { + // Package 1 - Valid scorer. + ResolveInfo package1 = buildResolveInfo("package1", true, true); + + // Package 2 - Receiver does not have BROADCAST_SCORE_NETWORKS permission. + ResolveInfo package2 = buildResolveInfo("package2", false, true); + + // Package 3 - App does not have SCORE_NETWORKS permission. + ResolveInfo package3 = buildResolveInfo("package3", true, false); + + setScorers(package1, package2, package3); + + Iterator result = + NetworkScorerAppManager.getAllValidScorers(mMockContext).iterator(); + + assertTrue(result.hasNext()); + assertEquals("package1", result.next()); + + assertFalse(result.hasNext()); + } + + private void setScorers(ResolveInfo... scorers) { + Mockito.when(mMockPm.queryBroadcastReceivers( + Mockito.argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction()); + } + }), Mockito.eq(0))) + .thenReturn(Lists.newArrayList(scorers)); + } + + private ResolveInfo buildResolveInfo(String packageName, + boolean hasReceiverPermission, boolean hasScorePermission) throws Exception { + Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) + .thenReturn(hasScorePermission ? + PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); + + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.packageName = packageName; + if (hasReceiverPermission) { + resolveInfo.activityInfo.permission = permission.BROADCAST_SCORE_NETWORKS; + } + return resolveInfo; + } +} diff --git a/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java b/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java deleted file mode 100644 index 6d5ede8e8f80..000000000000 --- a/core/tests/coretests/src/android/net/NetworkScorerApplicationTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.Manifest.permission; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.test.InstrumentationTestCase; - -import com.google.android.collect.Lists; - -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.Iterator; - -public class NetworkScorerApplicationTest extends InstrumentationTestCase { - @Mock private Context mMockContext; - @Mock private PackageManager mMockPm; - - @Override - public void setUp() throws Exception { - super.setUp(); - - // Configuration needed to make mockito/dexcache work. - System.setProperty("dexmaker.dexcache", - getInstrumentation().getTargetContext().getCacheDir().getPath()); - ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); - Thread.currentThread().setContextClassLoader(newClassLoader); - - MockitoAnnotations.initMocks(this); - Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm); - } - - public void testGetAllValidScorers() throws Exception { - // Package 1 - Valid scorer. - ResolveInfo package1 = buildResolveInfo("package1", true, true); - - // Package 2 - Receiver does not have BROADCAST_SCORE_NETWORKS permission. - ResolveInfo package2 = buildResolveInfo("package2", false, true); - - // Package 3 - App does not have SCORE_NETWORKS permission. - ResolveInfo package3 = buildResolveInfo("package3", true, false); - - setScorers(package1, package2, package3); - - Iterator result = - NetworkScorerApplication.getAllValidScorers(mMockContext).iterator(); - - assertTrue(result.hasNext()); - assertEquals("package1", result.next()); - - assertFalse(result.hasNext()); - } - - private void setScorers(ResolveInfo... scorers) { - Mockito.when(mMockPm.queryBroadcastReceivers( - Mockito.argThat(new ArgumentMatcher() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction()); - } - }), Mockito.eq(0))) - .thenReturn(Lists.newArrayList(scorers)); - } - - private ResolveInfo buildResolveInfo(String packageName, - boolean hasReceiverPermission, boolean hasScorePermission) throws Exception { - Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) - .thenReturn(hasScorePermission ? - PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); - - ResolveInfo resolveInfo = new ResolveInfo(); - resolveInfo.activityInfo = new ActivityInfo(); - resolveInfo.activityInfo.packageName = packageName; - if (hasReceiverPermission) { - resolveInfo.activityInfo.permission = permission.BROADCAST_SCORE_NETWORKS; - } - return resolveInfo; - } -} 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 000000000000..8a30e50c9aaf --- /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 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 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 912ac4da5e76..f08d69f3b825 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; @@ -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) { -- cgit v1.2.3-59-g8ed1b