Merge changes I8e2ed73d,I42aeb522
* changes: Implement the request and recommend calls. Implement the discovery of a network recommendation provider.
This commit is contained in:
committed by
Android (Google) Code Review
commit
7062f3be35
@@ -183,7 +183,7 @@ public class NetworkScoreManager {
|
||||
if (app == null) {
|
||||
return null;
|
||||
}
|
||||
return app.mPackageName;
|
||||
return app.packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,19 +272,11 @@ public class NetworkScoreManager {
|
||||
* @hide
|
||||
*/
|
||||
public boolean requestScores(NetworkKey[] networks) throws SecurityException {
|
||||
String activeScorer = getActiveScorerPackage();
|
||||
if (activeScorer == null) {
|
||||
return false;
|
||||
try {
|
||||
return mService.requestScores(networks);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
Intent intent = new Intent(ACTION_SCORE_NETWORKS);
|
||||
intent.setPackage(activeScorer);
|
||||
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
||||
intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks);
|
||||
// A scorer should never become active if its package doesn't hold SCORE_NETWORKS, but
|
||||
// ensure the package still holds it to be extra safe.
|
||||
// TODO: http://b/23422763
|
||||
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, Manifest.permission.SCORE_NETWORKS);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,6 +336,8 @@ public class NetworkScoreManager {
|
||||
/**
|
||||
* Request a recommendation for which network to connect to.
|
||||
*
|
||||
* <p>It is not safe to call this method from the main thread.
|
||||
*
|
||||
* @param request a {@link RecommendationRequest} instance containing additional
|
||||
* request details
|
||||
* @return a {@link RecommendationResult} instance containing the recommended network
|
||||
|
||||
@@ -19,160 +19,176 @@ package android.net;
|
||||
import android.Manifest;
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentResolver;
|
||||
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.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Internal class for managing the primary network scorer application.
|
||||
*
|
||||
* TODO: Rename this to something more generic.
|
||||
* Internal class for discovering and managing the network scorer/recommendation application.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NetworkScorerAppManager {
|
||||
private static final String TAG = "NetworkScorerAppManager";
|
||||
|
||||
private static final Intent SCORE_INTENT =
|
||||
new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
|
||||
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
|
||||
private final Context mContext;
|
||||
|
||||
public NetworkScorerAppManager(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds metadata about a discovered network scorer/recommendation application.
|
||||
*/
|
||||
public static class NetworkScorerAppData {
|
||||
/** Package name of this scorer app. */
|
||||
public final String mPackageName;
|
||||
public final String packageName;
|
||||
|
||||
/** UID of the scorer app. */
|
||||
public final int mPackageUid;
|
||||
|
||||
/** Name of this scorer app for display. */
|
||||
public final CharSequence mScorerName;
|
||||
public final int packageUid;
|
||||
|
||||
/**
|
||||
* Optional class name of a configuration activity. Null if none is set.
|
||||
*
|
||||
* @see NetworkScoreManager#ACTION_CUSTOM_ENABLE
|
||||
* Name of the recommendation service we can bind to.
|
||||
*/
|
||||
public final String mConfigurationActivityClassName;
|
||||
public final String recommendationServiceClassName;
|
||||
|
||||
/**
|
||||
* Optional class name of the scoring service we can bind to. Null if none is set.
|
||||
*/
|
||||
public final String mScoringServiceClassName;
|
||||
|
||||
public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName,
|
||||
@Nullable String configurationActivityClassName,
|
||||
@Nullable String scoringServiceClassName) {
|
||||
mScorerName = scorerName;
|
||||
mPackageName = packageName;
|
||||
mPackageUid = packageUid;
|
||||
mConfigurationActivityClassName = configurationActivityClassName;
|
||||
mScoringServiceClassName = scoringServiceClassName;
|
||||
public NetworkScorerAppData(String packageName, int packageUid,
|
||||
String recommendationServiceClassName) {
|
||||
this.packageName = packageName;
|
||||
this.packageUid = packageUid;
|
||||
this.recommendationServiceClassName = recommendationServiceClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("NetworkScorerAppData{");
|
||||
sb.append("mPackageName='").append(mPackageName).append('\'');
|
||||
sb.append(", mPackageUid=").append(mPackageUid);
|
||||
sb.append(", mScorerName=").append(mScorerName);
|
||||
sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName)
|
||||
.append('\'');
|
||||
sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\'');
|
||||
sb.append("mPackageName='").append(packageName).append('\'');
|
||||
sb.append(", packageUid=").append(packageUid);
|
||||
sb.append(", recommendationServiceClassName='")
|
||||
.append(recommendationServiceClassName).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available scorer apps.
|
||||
* @return A {@link NetworkScorerAppData} instance containing information about the
|
||||
* best configured network recommendation provider installed or {@code null}
|
||||
* if none of the configured packages can recommend networks.
|
||||
*
|
||||
* <p>A network scorer is any application which:
|
||||
* <p>A network recommendation provider is any application which:
|
||||
* <ul>
|
||||
* <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config.
|
||||
* <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
|
||||
* <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the
|
||||
* {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
|
||||
* <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}.
|
||||
* </ul>
|
||||
*
|
||||
* @return the list of scorers, or the empty list if there are no valid scorers.
|
||||
*/
|
||||
public Collection<NetworkScorerAppData> getAllValidScorers() {
|
||||
// Network scorer apps can only run as the primary user so exit early if we're not the
|
||||
// primary user.
|
||||
public NetworkScorerAppData getNetworkRecommendationProviderData() {
|
||||
// Network recommendation apps can only run as the primary user right now.
|
||||
// http://b/23422763
|
||||
if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<String> potentialPkgs = getPotentialRecommendationProviderPackages();
|
||||
if (potentialPkgs.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No Network Recommendation Providers specified.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
for (int i = 0; i < potentialPkgs.size(); i++) {
|
||||
final String potentialPkg = potentialPkgs.get(i);
|
||||
|
||||
// Look for the recommendation service class and required receiver.
|
||||
final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg);
|
||||
if (resolveServiceInfo != null) {
|
||||
return new NetworkScorerAppData(potentialPkg,
|
||||
resolveServiceInfo.serviceInfo.applicationInfo.uid,
|
||||
resolveServiceInfo.serviceInfo.name);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, potentialPkg + " does not have the required components, skipping.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// None of the configured packages are valid.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A priority order list of package names that have been granted the
|
||||
* permission needed for them to act as a network recommendation provider.
|
||||
* The packages in the returned list may not contain the other required
|
||||
* network recommendation provider components so additional checks are required
|
||||
* before making a package the network recommendation provider.
|
||||
*/
|
||||
public List<String> getPotentialRecommendationProviderPackages() {
|
||||
final String[] packageArray = mContext.getResources().getStringArray(
|
||||
R.array.config_networkRecommendationPackageNames);
|
||||
if (packageArray == null || packageArray.length == 0) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No Network Recommendation Providers specified.");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<NetworkScorerAppData> scorers = new ArrayList<>();
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
// Only apps installed under the primary user of the device can be scorers.
|
||||
// TODO: http://b/23422763
|
||||
List<ResolveInfo> receivers =
|
||||
pm.queryBroadcastReceiversAsUser(SCORE_INTENT, 0 /* flags */, UserHandle.USER_SYSTEM);
|
||||
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_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) {
|
||||
// Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED 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;
|
||||
}
|
||||
|
||||
// Optionally, this package may specify a configuration activity.
|
||||
String configurationActivityClassName = null;
|
||||
Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE);
|
||||
intent.setPackage(receiverInfo.packageName);
|
||||
List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */);
|
||||
if (configActivities != null && !configActivities.isEmpty()) {
|
||||
ActivityInfo activityInfo = configActivities.get(0).activityInfo;
|
||||
if (activityInfo != null) {
|
||||
configurationActivityClassName = activityInfo.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the scoring service class we can bind to, if any.
|
||||
String scoringServiceClassName = null;
|
||||
Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
|
||||
serviceIntent.setPackage(receiverInfo.packageName);
|
||||
ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */);
|
||||
if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
|
||||
scoringServiceClassName = resolveServiceInfo.serviceInfo.name;
|
||||
}
|
||||
|
||||
// NOTE: loadLabel will attempt to load the receiver's label and fall back to the
|
||||
// app label if none is present.
|
||||
scorers.add(new NetworkScorerAppData(receiverInfo.packageName,
|
||||
receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm),
|
||||
configurationActivityClassName, scoringServiceClassName));
|
||||
if (VERBOSE) {
|
||||
Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray));
|
||||
}
|
||||
|
||||
return scorers;
|
||||
List<String> packages = new ArrayList<>();
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
for (String potentialPkg : packageArray) {
|
||||
if (pm.checkPermission(permission.SCORE_NETWORKS, potentialPkg)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
packages.add(potentialPkg);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, potentialPkg + " has not been granted " + permission.SCORE_NETWORKS
|
||||
+ ", skipping.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
private ResolveInfo findRecommendationService(String packageName) {
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
final int resolveFlags = 0;
|
||||
|
||||
final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
|
||||
serviceIntent.setPackage(packageName);
|
||||
final ResolveInfo resolveServiceInfo =
|
||||
pm.resolveService(serviceIntent, resolveFlags);
|
||||
|
||||
if (VERBOSE) {
|
||||
Log.d(TAG, "Resolved " + serviceIntent + " to " + resolveServiceInfo);
|
||||
}
|
||||
|
||||
if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
|
||||
return resolveServiceInfo;
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
Log.v(TAG, packageName + " does not have a service for " + serviceIntent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,10 +198,15 @@ public class NetworkScorerAppManager {
|
||||
* selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
|
||||
* it was disabled or uninstalled).
|
||||
*/
|
||||
@Nullable
|
||||
public NetworkScorerAppData getActiveScorer() {
|
||||
String scorerPackage = Settings.Global.getString(mContext.getContentResolver(),
|
||||
Settings.Global.NETWORK_SCORER_APP);
|
||||
return getScorer(scorerPackage);
|
||||
if (isNetworkRecommendationsDisabled()) {
|
||||
// If recommendations are disabled then there can't be an active scorer.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise return the recommendation provider (which may be null).
|
||||
return getNetworkRecommendationProviderData();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,33 +216,13 @@ public class NetworkScorerAppManager {
|
||||
*
|
||||
* @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.
|
||||
* @return true if the scorer was changed, or false if the package is not a valid scorer or
|
||||
* a valid network recommendation provider exists.
|
||||
* @deprecated Scorers are now selected from a configured list.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean setActiveScorer(String packageName) {
|
||||
String oldPackageName = Settings.Global.getString(mContext.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(mContext.getContentResolver(),
|
||||
Settings.Global.NETWORK_SCORER_APP, null);
|
||||
return true;
|
||||
} else {
|
||||
// We only make the change if the new package is valid.
|
||||
if (getScorer(packageName) != null) {
|
||||
Settings.Global.putString(mContext.getContentResolver(),
|
||||
Settings.Global.NETWORK_SCORER_APP, packageName);
|
||||
return true;
|
||||
} else {
|
||||
Log.w(TAG, "Requested network scorer is not valid: " + packageName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Determine whether the application with the given UID is the enabled scorer. */
|
||||
@@ -230,7 +231,7 @@ public class NetworkScorerAppManager {
|
||||
if (defaultApp == null) {
|
||||
return false;
|
||||
}
|
||||
if (callingUid != defaultApp.mPackageUid) {
|
||||
if (callingUid != defaultApp.packageUid) {
|
||||
return false;
|
||||
}
|
||||
// To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always
|
||||
@@ -239,17 +240,9 @@ public class NetworkScorerAppManager {
|
||||
PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */
|
||||
public NetworkScorerAppData getScorer(String packageName) {
|
||||
if (TextUtils.isEmpty(packageName)) {
|
||||
return null;
|
||||
}
|
||||
Collection<NetworkScorerAppData> applications = getAllValidScorers();
|
||||
for (NetworkScorerAppData app : applications) {
|
||||
if (packageName.equals(app.mPackageName)) {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
private boolean isNetworkRecommendationsDisabled() {
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
// A value of 1 indicates enabled.
|
||||
return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,32 +16,33 @@
|
||||
|
||||
package android.net;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import com.android.internal.R;
|
||||
import java.util.List;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
|
||||
@Mock private Context mMockContext;
|
||||
@Mock private PackageManager mMockPm;
|
||||
|
||||
@Mock private Resources mResources;
|
||||
@Mock private ContentResolver mContentResolver;
|
||||
private Context mTargetContext;
|
||||
private NetworkScorerAppManager mNetworkScorerAppManager;
|
||||
|
||||
@Override
|
||||
@@ -49,154 +50,161 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
|
||||
super.setUp();
|
||||
|
||||
// Configuration needed to make mockito/dexcache work.
|
||||
System.setProperty("dexmaker.dexcache",
|
||||
getInstrumentation().getTargetContext().getCacheDir().getPath());
|
||||
mTargetContext = getInstrumentation().getTargetContext();
|
||||
System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath());
|
||||
ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(newClassLoader);
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm);
|
||||
when(mMockContext.getPackageManager()).thenReturn(mMockPm);
|
||||
when(mMockContext.getResources()).thenReturn(mResources);
|
||||
when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver());
|
||||
mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext);
|
||||
}
|
||||
|
||||
public void testGetAllValidScorers() throws Exception {
|
||||
// Package 1 - Valid scorer.
|
||||
ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false);
|
||||
|
||||
// Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission.
|
||||
ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false);
|
||||
|
||||
// Package 3 - App does not have SCORE_NETWORKS permission.
|
||||
ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false);
|
||||
|
||||
// Package 4 - Valid scorer w/ optional config activity.
|
||||
ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false);
|
||||
|
||||
// Package 5 - Valid scorer w/ optional service to bind to.
|
||||
ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true);
|
||||
|
||||
List<ResolveInfoHolder> scorers = new ArrayList<>();
|
||||
scorers.add(package1);
|
||||
scorers.add(package2);
|
||||
scorers.add(package3);
|
||||
scorers.add(package4);
|
||||
scorers.add(package5);
|
||||
setScorers(scorers);
|
||||
|
||||
Iterator<NetworkScorerAppData> result =
|
||||
mNetworkScorerAppManager.getAllValidScorers().iterator();
|
||||
|
||||
assertTrue(result.hasNext());
|
||||
NetworkScorerAppData next = result.next();
|
||||
assertEquals("package1", next.mPackageName);
|
||||
assertEquals(1, next.mPackageUid);
|
||||
assertNull(next.mConfigurationActivityClassName);
|
||||
|
||||
assertTrue(result.hasNext());
|
||||
next = result.next();
|
||||
assertEquals("package4", next.mPackageName);
|
||||
assertEquals(4, next.mPackageUid);
|
||||
assertEquals(".ConfigActivity", next.mConfigurationActivityClassName);
|
||||
|
||||
assertTrue(result.hasNext());
|
||||
next = result.next();
|
||||
assertEquals("package5", next.mPackageName);
|
||||
assertEquals(5, next.mPackageUid);
|
||||
assertEquals(".ScoringService", next.mScoringServiceClassName);
|
||||
|
||||
assertFalse(result.hasNext());
|
||||
public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception {
|
||||
setNetworkRecommendationPackageNames(/*no configured packages*/);
|
||||
assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
|
||||
}
|
||||
|
||||
private void setScorers(List<ResolveInfoHolder> scorers) {
|
||||
List<ResolveInfo> receivers = new ArrayList<>();
|
||||
for (final ResolveInfoHolder scorer : scorers) {
|
||||
receivers.add(scorer.scorerResolveInfo);
|
||||
if (scorer.configActivityResolveInfo != null) {
|
||||
// This scorer has a config activity.
|
||||
Mockito.when(mMockPm.queryIntentActivities(
|
||||
Mockito.argThat(new ArgumentMatcher<Intent>() {
|
||||
@Override
|
||||
public boolean matches(Object object) {
|
||||
Intent intent = (Intent) object;
|
||||
return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals(
|
||||
intent.getAction())
|
||||
&& scorer.scorerResolveInfo.activityInfo.packageName.equals(
|
||||
intent.getPackage());
|
||||
}
|
||||
}), Mockito.eq(0))).thenReturn(
|
||||
Collections.singletonList(scorer.configActivityResolveInfo));
|
||||
}
|
||||
public void testGetPotentialRecommendationProviderPackages_permissionNotGranted()
|
||||
throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksDenied("package1");
|
||||
|
||||
if (scorer.serviceResolveInfo != null) {
|
||||
// This scorer has a service to bind to
|
||||
Mockito.when(mMockPm.resolveService(
|
||||
Mockito.argThat(new ArgumentMatcher<Intent>() {
|
||||
@Override
|
||||
public boolean matches(Object object) {
|
||||
Intent intent = (Intent) object;
|
||||
return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(
|
||||
intent.getAction())
|
||||
&& scorer.scorerResolveInfo.activityInfo.packageName.equals(
|
||||
intent.getPackage());
|
||||
}
|
||||
}), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo);
|
||||
}
|
||||
assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
|
||||
}
|
||||
|
||||
public void testGetPotentialRecommendationProviderPackages_permissionGranted()
|
||||
throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksGranted("package1");
|
||||
|
||||
List<String> potentialProviderPackages =
|
||||
mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
|
||||
|
||||
assertFalse(potentialProviderPackages.isEmpty());
|
||||
assertEquals("package1", potentialProviderPackages.get(0));
|
||||
}
|
||||
|
||||
public void testGetPotentialRecommendationProviderPackages_multipleConfigured()
|
||||
throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1", "package2");
|
||||
mockScoreNetworksDenied("package1");
|
||||
mockScoreNetworksGranted("package2");
|
||||
|
||||
List<String> potentialProviderPackages =
|
||||
mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
|
||||
|
||||
assertEquals(1, potentialProviderPackages.size());
|
||||
assertEquals("package2", potentialProviderPackages.get(0));
|
||||
}
|
||||
|
||||
public void testGetNetworkRecommendationProviderData_noPotentialPackages() throws Exception {
|
||||
setNetworkRecommendationPackageNames(/*no configured packages*/);
|
||||
assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
|
||||
}
|
||||
|
||||
public void testGetNetworkRecommendationProviderData_serviceMissing() throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksGranted("package1");
|
||||
|
||||
assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
|
||||
}
|
||||
|
||||
public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted()
|
||||
throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksDenied("package1");
|
||||
mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
|
||||
|
||||
assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
|
||||
}
|
||||
|
||||
public void testGetNetworkRecommendationProviderData_available() throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksGranted("package1");
|
||||
mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
|
||||
|
||||
NetworkScorerAppData appData =
|
||||
mNetworkScorerAppManager.getNetworkRecommendationProviderData();
|
||||
assertNotNull(appData);
|
||||
assertEquals("package1", appData.packageName);
|
||||
assertEquals(924, appData.packageUid);
|
||||
assertEquals(".RecommendationService", appData.recommendationServiceClassName);
|
||||
}
|
||||
|
||||
public void testGetActiveScorer_providerAvailable() throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksGranted("package1");
|
||||
mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
|
||||
|
||||
ContentResolver cr = mTargetContext.getContentResolver();
|
||||
Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
|
||||
|
||||
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
|
||||
assertNotNull(activeScorer);
|
||||
assertEquals("package1", activeScorer.packageName);
|
||||
assertEquals(924, activeScorer.packageUid);
|
||||
assertEquals(".RecommendationService", activeScorer.recommendationServiceClassName);
|
||||
}
|
||||
|
||||
public void testGetActiveScorer_providerNotAvailable()
|
||||
throws Exception {
|
||||
ContentResolver cr = mTargetContext.getContentResolver();
|
||||
Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
|
||||
|
||||
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
|
||||
assertNull(activeScorer);
|
||||
}
|
||||
|
||||
public void testGetActiveScorer_recommendationsDisabled() throws Exception {
|
||||
setNetworkRecommendationPackageNames("package1");
|
||||
mockScoreNetworksGranted("package1");
|
||||
mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
|
||||
ContentResolver cr = mTargetContext.getContentResolver();
|
||||
Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0);
|
||||
|
||||
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
|
||||
assertNull(activeScorer);
|
||||
}
|
||||
|
||||
private void setNetworkRecommendationPackageNames(String... names) {
|
||||
if (names == null) {
|
||||
names = new String[0];
|
||||
}
|
||||
when(mResources.getStringArray(R.array.config_networkRecommendationPackageNames))
|
||||
.thenReturn(names);
|
||||
}
|
||||
|
||||
Mockito.when(mMockPm.queryBroadcastReceiversAsUser(
|
||||
private void mockScoreNetworksGranted(String packageName) {
|
||||
when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
|
||||
.thenReturn(PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
|
||||
private void mockScoreNetworksDenied(String packageName) {
|
||||
when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
private void mockRecommendationServiceAvailable(final String packageName, int packageUid) {
|
||||
final ResolveInfo serviceInfo = new ResolveInfo();
|
||||
serviceInfo.serviceInfo = new ServiceInfo();
|
||||
serviceInfo.serviceInfo.name = ".RecommendationService";
|
||||
serviceInfo.serviceInfo.packageName = packageName;
|
||||
serviceInfo.serviceInfo.applicationInfo = new ApplicationInfo();
|
||||
serviceInfo.serviceInfo.applicationInfo.uid = packageUid;
|
||||
|
||||
final int flags = 0;
|
||||
when(mMockPm.resolveService(
|
||||
Mockito.argThat(new ArgumentMatcher<Intent>() {
|
||||
@Override
|
||||
public boolean matches(Object object) {
|
||||
Intent intent = (Intent) object;
|
||||
return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction());
|
||||
return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS
|
||||
.equals(intent.getAction())
|
||||
&& packageName.equals(intent.getPackage());
|
||||
}
|
||||
}), Mockito.eq(0), Mockito.eq(UserHandle.USER_SYSTEM)))
|
||||
.thenReturn(receivers);
|
||||
}
|
||||
|
||||
private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid,
|
||||
boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity,
|
||||
boolean hasServiceInfo) 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;
|
||||
resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
|
||||
resolveInfo.activityInfo.applicationInfo.uid = packageUid;
|
||||
if (hasReceiverPermission) {
|
||||
resolveInfo.activityInfo.permission = permission.BROADCAST_NETWORK_PRIVILEGED;
|
||||
}
|
||||
|
||||
ResolveInfo configActivityInfo = null;
|
||||
if (hasConfigActivity) {
|
||||
configActivityInfo = new ResolveInfo();
|
||||
configActivityInfo.activityInfo = new ActivityInfo();
|
||||
configActivityInfo.activityInfo.name = ".ConfigActivity";
|
||||
}
|
||||
|
||||
ResolveInfo serviceInfo = null;
|
||||
if (hasServiceInfo) {
|
||||
serviceInfo = new ResolveInfo();
|
||||
serviceInfo.serviceInfo = new ServiceInfo();
|
||||
serviceInfo.serviceInfo.name = ".ScoringService";
|
||||
}
|
||||
|
||||
return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo);
|
||||
}
|
||||
|
||||
private static class ResolveInfoHolder {
|
||||
final ResolveInfo scorerResolveInfo;
|
||||
final ResolveInfo configActivityResolveInfo;
|
||||
final ResolveInfo serviceResolveInfo;
|
||||
|
||||
public ResolveInfoHolder(ResolveInfo scorerResolveInfo,
|
||||
ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) {
|
||||
this.scorerResolveInfo = scorerResolveInfo;
|
||||
this.configActivityResolveInfo = configActivityResolveInfo;
|
||||
this.serviceResolveInfo = serviceResolveInfo;
|
||||
}
|
||||
}), Mockito.eq(flags))).thenReturn(serviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
|
||||
import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
@@ -25,27 +29,29 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.INetworkRecommendationProvider;
|
||||
import android.net.INetworkScoreCache;
|
||||
import android.net.INetworkScoreService;
|
||||
import android.net.NetworkKey;
|
||||
import android.net.NetworkScoreManager;
|
||||
import android.net.NetworkScorerAppManager;
|
||||
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
|
||||
import android.net.RecommendationRequest;
|
||||
import android.net.RecommendationResult;
|
||||
import android.net.ScoredNetwork;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.provider.Settings.Global;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.TimedRemoteCaller;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
@@ -59,6 +65,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -67,17 +74,20 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
private static final String TAG = "NetworkScoreService";
|
||||
private static final boolean DBG = false;
|
||||
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final Context mContext;
|
||||
private final NetworkScorerAppManager mNetworkScorerAppManager;
|
||||
private final RequestRecommendationCaller mRequestRecommendationCaller;
|
||||
@GuardedBy("mScoreCaches")
|
||||
private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
|
||||
/** Lock used to update mPackageMonitor when scorer package changes occur. */
|
||||
private final Object mPackageMonitorLock = new Object[0];
|
||||
private final Object mServiceConnectionLock = new Object[0];
|
||||
|
||||
@GuardedBy("mPackageMonitorLock")
|
||||
private NetworkScorerPackageMonitor mPackageMonitor;
|
||||
@GuardedBy("mServiceConnectionLock")
|
||||
private ScoringServiceConnection mServiceConnection;
|
||||
|
||||
private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
|
||||
@@ -99,10 +109,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
* manages the service connection.
|
||||
*/
|
||||
private class NetworkScorerPackageMonitor extends PackageMonitor {
|
||||
final String mRegisteredPackage;
|
||||
final List<String> mPackagesToWatch;
|
||||
|
||||
private NetworkScorerPackageMonitor(String mRegisteredPackage) {
|
||||
this.mRegisteredPackage = mRegisteredPackage;
|
||||
private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
|
||||
mPackagesToWatch = packagesToWatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,7 +146,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
}
|
||||
|
||||
private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
|
||||
if (mRegisteredPackage.equals(scorerPackageName)) {
|
||||
if (mPackagesToWatch.contains(scorerPackageName)) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Evaluating binding for: " + scorerPackageName
|
||||
+ ", forceUnbind=" + forceUnbind);
|
||||
@@ -146,13 +156,14 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
if (activeScorer == null) {
|
||||
// Package change has invalidated a scorer, this will also unbind any service
|
||||
// connection.
|
||||
Log.i(TAG, "Package " + mRegisteredPackage +
|
||||
" is no longer valid, disabling scoring.");
|
||||
setScorerInternal(null);
|
||||
} else if (activeScorer.mScoringServiceClassName == null) {
|
||||
// The scoring service is not available, make sure it's unbound.
|
||||
if (DBG) Log.d(TAG, "No active scorers available.");
|
||||
unbindFromScoringServiceIfNeeded();
|
||||
} else { // The scoring service changed in some way.
|
||||
} else if (activeScorer.packageName.equals(scorerPackageName)) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Possible change to the active scorer: "
|
||||
+ activeScorer.packageName);
|
||||
}
|
||||
// The scoring service changed in some way.
|
||||
if (forceUnbind) {
|
||||
unbindFromScoringServiceIfNeeded();
|
||||
}
|
||||
@@ -162,6 +173,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reevaluates the service binding when the Settings toggle is changed.
|
||||
*/
|
||||
private class SettingsObserver extends ContentObserver {
|
||||
|
||||
public SettingsObserver() {
|
||||
super(null /*handler*/);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
onChange(selfChange, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
|
||||
bindToScoringServiceIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkScoreService(Context context) {
|
||||
this(context, new NetworkScorerAppManager(context));
|
||||
}
|
||||
@@ -176,24 +208,16 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
mContext.registerReceiverAsUser(
|
||||
mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
|
||||
null /* scheduler */);
|
||||
// TODO(jjoslin): 12/15/16 - Make timeout configurable.
|
||||
mRequestRecommendationCaller =
|
||||
new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
/** Called when the system is ready to run third-party code but before it actually does so. */
|
||||
void systemReady() {
|
||||
if (DBG) Log.d(TAG, "systemReady");
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
|
||||
// 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)) {
|
||||
mNetworkScorerAppManager.setActiveScorer(defaultPackage);
|
||||
}
|
||||
Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
|
||||
}
|
||||
|
||||
registerPackageMonitorIfNeeded();
|
||||
registerRecommendationSettingObserverIfNeeded();
|
||||
}
|
||||
|
||||
/** Called when the system is ready for us to start third-party code. */
|
||||
@@ -207,29 +231,40 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
bindToScoringServiceIfNeeded();
|
||||
}
|
||||
|
||||
private void registerRecommendationSettingObserverIfNeeded() {
|
||||
final List<String> providerPackages =
|
||||
mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
|
||||
if (!providerPackages.isEmpty()) {
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
|
||||
resolver.registerContentObserver(uri, false, new SettingsObserver());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPackageMonitorIfNeeded() {
|
||||
if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
|
||||
NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer();
|
||||
final List<String> providerPackages =
|
||||
mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
|
||||
synchronized (mPackageMonitorLock) {
|
||||
// Unregister the current monitor if needed.
|
||||
if (mPackageMonitor != null) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Unregistering package monitor for "
|
||||
+ mPackageMonitor.mRegisteredPackage);
|
||||
+ mPackageMonitor.mPackagesToWatch);
|
||||
}
|
||||
mPackageMonitor.unregister();
|
||||
mPackageMonitor = null;
|
||||
}
|
||||
|
||||
// Create and register the monitor if a scorer is active.
|
||||
if (scorer != null) {
|
||||
mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName);
|
||||
// Create and register the monitor if there are packages that could be providers.
|
||||
if (!providerPackages.isEmpty()) {
|
||||
mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
|
||||
// TODO: Need to update when we support per-user scorers. http://b/23422763
|
||||
mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
|
||||
false /* externalStorage */);
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Registered package monitor for "
|
||||
+ mPackageMonitor.mRegisteredPackage);
|
||||
+ mPackageMonitor.mPackagesToWatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,22 +278,24 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
|
||||
private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
|
||||
if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
|
||||
if (scorerData != null && scorerData.mScoringServiceClassName != null) {
|
||||
ComponentName componentName =
|
||||
new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
|
||||
// If we're connected to a different component then drop it.
|
||||
if (mServiceConnection != null
|
||||
&& !mServiceConnection.mComponentName.equals(componentName)) {
|
||||
unbindFromScoringServiceIfNeeded();
|
||||
}
|
||||
if (scorerData != null && scorerData.recommendationServiceClassName != null) {
|
||||
ComponentName componentName = new ComponentName(scorerData.packageName,
|
||||
scorerData.recommendationServiceClassName);
|
||||
synchronized (mServiceConnectionLock) {
|
||||
// If we're connected to a different component then drop it.
|
||||
if (mServiceConnection != null
|
||||
&& !mServiceConnection.mComponentName.equals(componentName)) {
|
||||
unbindFromScoringServiceIfNeeded();
|
||||
}
|
||||
|
||||
// If we're not connected at all then create a new connection.
|
||||
if (mServiceConnection == null) {
|
||||
mServiceConnection = new ScoringServiceConnection(componentName);
|
||||
}
|
||||
// If we're not connected at all then create a new connection.
|
||||
if (mServiceConnection == null) {
|
||||
mServiceConnection = new ScoringServiceConnection(componentName);
|
||||
}
|
||||
|
||||
// Make sure the connection is connected (idempotent)
|
||||
mServiceConnection.connect(mContext);
|
||||
// Make sure the connection is connected (idempotent)
|
||||
mServiceConnection.connect(mContext);
|
||||
}
|
||||
} else { // otherwise make sure it isn't bound.
|
||||
unbindFromScoringServiceIfNeeded();
|
||||
}
|
||||
@@ -266,10 +303,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
|
||||
private void unbindFromScoringServiceIfNeeded() {
|
||||
if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
|
||||
if (mServiceConnection != null) {
|
||||
mServiceConnection.disconnect(mContext);
|
||||
synchronized (mServiceConnectionLock) {
|
||||
if (mServiceConnection != null) {
|
||||
mServiceConnection.disconnect(mContext);
|
||||
}
|
||||
mServiceConnection = null;
|
||||
}
|
||||
mServiceConnection = null;
|
||||
clearInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,7 +389,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
// mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
|
||||
mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
|
||||
|
||||
return setScorerInternal(packageName);
|
||||
// Scorers (recommendation providers) are selected and no longer set.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -359,56 +400,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
|
||||
mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
// The return value is discarded here because at this point, the call should always
|
||||
// succeed. The only reason for failure is if the new package is not a valid scorer, but
|
||||
// we're disabling scoring altogether here.
|
||||
setScorerInternal(null /* packageName */);
|
||||
// no-op for now but we could write to the setting if needed.
|
||||
} else {
|
||||
throw new SecurityException(
|
||||
"Caller is neither the active scorer nor the scorer manager.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
|
||||
private boolean setScorerInternal(String packageName) {
|
||||
if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
|
||||
long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
unbindFromScoringServiceIfNeeded();
|
||||
// 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();
|
||||
// Get the scorer that is about to be replaced, if any, so we can notify it directly.
|
||||
NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer();
|
||||
boolean result = mNetworkScorerAppManager.setActiveScorer(packageName);
|
||||
// Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
|
||||
// then we'll attempt to restore the previous binding (if any), otherwise an attempt
|
||||
// will be made to bind to the new scorer.
|
||||
bindToScoringServiceIfNeeded();
|
||||
if (result) { // new scorer successfully set
|
||||
registerPackageMonitorIfNeeded();
|
||||
|
||||
Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
|
||||
if (prevScorer != null) { // Directly notify the old scorer.
|
||||
intent.setPackage(prevScorer.mPackageName);
|
||||
// TODO: Need to update when we support per-user scorers. http://b/23422763
|
||||
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
|
||||
}
|
||||
|
||||
if (packageName != null) { // Then notify the new scorer
|
||||
intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
|
||||
intent.setPackage(packageName);
|
||||
// TODO: Need to update when we support per-user scorers. http://b/23422763
|
||||
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear scores. Callers are responsible for checking permissions as appropriate. */
|
||||
private void clearInternal() {
|
||||
sendCallback(new Consumer<INetworkScoreCache>() {
|
||||
@@ -464,7 +462,22 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
|
||||
@Override
|
||||
public RecommendationResult requestRecommendation(RecommendationRequest request) {
|
||||
// TODO(jjoslin): 11/25/16 - Update with real impl.
|
||||
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
|
||||
throwIfCalledOnMainThread();
|
||||
final INetworkRecommendationProvider provider = getRecommendationProvider();
|
||||
if (provider != null) {
|
||||
try {
|
||||
return mRequestRecommendationCaller.getRecommendationResult(provider, request);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.w(TAG, "Failed to request a recommendation.", e);
|
||||
// TODO(jjoslin): 12/15/16 - Keep track of failures.
|
||||
}
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Returning the default network recommendation.");
|
||||
}
|
||||
|
||||
WifiConfiguration selectedConfig = null;
|
||||
if (request != null) {
|
||||
selectedConfig = request.getCurrentSelectedConfig();
|
||||
@@ -474,7 +487,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
|
||||
@Override
|
||||
public boolean requestScores(NetworkKey[] networks) {
|
||||
// TODO(jjoslin): 12/13/16 - Implement
|
||||
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
|
||||
final INetworkRecommendationProvider provider = getRecommendationProvider();
|
||||
if (provider != null) {
|
||||
try {
|
||||
provider.requestScores(networks);
|
||||
// TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to prevent
|
||||
// repeated requests for the same scores.
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to request scores.", e);
|
||||
// TODO(jjoslin): 12/15/16 - Keep track of failures.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -486,7 +511,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
writer.println("Scoring is disabled.");
|
||||
return;
|
||||
}
|
||||
writer.println("Current scorer: " + currentScorer.mPackageName);
|
||||
writer.println("Current scorer: " + currentScorer.packageName);
|
||||
|
||||
sendCallback(new Consumer<INetworkScoreCache>() {
|
||||
@Override
|
||||
@@ -499,10 +524,12 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
}
|
||||
}, getScoreCacheLists());
|
||||
|
||||
if (mServiceConnection != null) {
|
||||
mServiceConnection.dump(fd, writer, args);
|
||||
} else {
|
||||
writer.println("ScoringServiceConnection: null");
|
||||
synchronized (mServiceConnectionLock) {
|
||||
if (mServiceConnection != null) {
|
||||
mServiceConnection.dump(fd, writer, args);
|
||||
} else {
|
||||
writer.println("ScoringServiceConnection: null");
|
||||
}
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
@@ -535,10 +562,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void throwIfCalledOnMainThread() {
|
||||
if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
|
||||
throw new RuntimeException("Cannot invoke on the main thread");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private INetworkRecommendationProvider getRecommendationProvider() {
|
||||
synchronized (mServiceConnectionLock) {
|
||||
if (mServiceConnection != null) {
|
||||
return mServiceConnection.getRecommendationProvider();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class ScoringServiceConnection implements ServiceConnection {
|
||||
private final ComponentName mComponentName;
|
||||
private boolean mBound = false;
|
||||
private boolean mConnected = false;
|
||||
private volatile boolean mBound = false;
|
||||
private volatile boolean mConnected = false;
|
||||
private volatile INetworkRecommendationProvider mRecommendationProvider;
|
||||
|
||||
ScoringServiceConnection(ComponentName componentName) {
|
||||
mComponentName = componentName;
|
||||
@@ -569,12 +613,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Unbind failed.", e);
|
||||
}
|
||||
|
||||
mRecommendationProvider = null;
|
||||
}
|
||||
|
||||
INetworkRecommendationProvider getRecommendationProvider() {
|
||||
return mRecommendationProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
|
||||
mConnected = true;
|
||||
mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -583,6 +634,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
|
||||
}
|
||||
mConnected = false;
|
||||
mRecommendationProvider = null;
|
||||
}
|
||||
|
||||
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
@@ -590,4 +642,43 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
|
||||
+ ", connected: " + mConnected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the async requestRecommendation() call with a timeout.
|
||||
*/
|
||||
private static final class RequestRecommendationCaller
|
||||
extends TimedRemoteCaller<RecommendationResult> {
|
||||
private final IRemoteCallback mCallback;
|
||||
|
||||
RequestRecommendationCaller(long callTimeoutMillis) {
|
||||
super(callTimeoutMillis);
|
||||
mCallback = new IRemoteCallback.Stub() {
|
||||
@Override
|
||||
public void sendResult(Bundle data) throws RemoteException {
|
||||
final RecommendationResult result =
|
||||
data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
|
||||
final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
|
||||
onRemoteMethodResult(result, sequence);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
|
||||
* instance.
|
||||
*
|
||||
* @param target the {@link INetworkRecommendationProvider} to request a recommendation
|
||||
* from
|
||||
* @param request the {@link RecommendationRequest} from the calling client
|
||||
* @return a {@link RecommendationResult} from the provider
|
||||
* @throws RemoteException if the call failed
|
||||
* @throws TimeoutException if the call took longer than the set timeout
|
||||
*/
|
||||
RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
|
||||
RecommendationRequest request) throws RemoteException, TimeoutException {
|
||||
final int sequence = onBeforeRemoteCall();
|
||||
target.requestRecommendation(request, mCallback, sequence);
|
||||
return getResultTimed(sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
|
||||
import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
|
||||
import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
@@ -28,12 +31,15 @@ import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyListOf;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest.permission;
|
||||
@@ -44,37 +50,42 @@ import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.net.INetworkRecommendationProvider;
|
||||
import android.net.INetworkScoreCache;
|
||||
import android.net.NetworkKey;
|
||||
import android.net.NetworkScoreManager;
|
||||
import android.net.NetworkScorerAppManager;
|
||||
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
|
||||
import android.net.RecommendationRequest;
|
||||
import android.net.RecommendationResult;
|
||||
import android.net.ScoredNetwork;
|
||||
import android.net.WifiKey;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.MediumTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.devicepolicy.MockUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link NetworkScoreService}.
|
||||
@@ -85,12 +96,8 @@ public class NetworkScoreServiceTest {
|
||||
private static final ScoredNetwork SCORED_NETWORK =
|
||||
new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
|
||||
null /* rssiCurve*/);
|
||||
private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData(
|
||||
"prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */,
|
||||
"prevScoringServiceClass");
|
||||
private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
|
||||
"newPackageName", 1, "newScorerName", null /* configurationActivityClassName */,
|
||||
"newScoringServiceClass");
|
||||
private static final NetworkScorerAppData NEW_SCORER =
|
||||
new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass");
|
||||
|
||||
@Mock private PackageManager mPackageManager;
|
||||
@Mock private NetworkScorerAppManager mNetworkScorerAppManager;
|
||||
@@ -98,10 +105,12 @@ public class NetworkScoreServiceTest {
|
||||
@Mock private Resources mResources;
|
||||
@Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
|
||||
@Mock private IBinder mIBinder, mIBinder2;
|
||||
@Mock private INetworkRecommendationProvider mRecommendationProvider;
|
||||
@Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
|
||||
|
||||
private ContentResolver mContentResolver;
|
||||
private NetworkScoreService mNetworkScoreService;
|
||||
private RecommendationRequest mRecommendationRequest;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -112,44 +121,9 @@ public class NetworkScoreServiceTest {
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemReady_networkScorerProvisioned() throws Exception {
|
||||
Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1);
|
||||
|
||||
mNetworkScoreService.systemReady();
|
||||
|
||||
verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception {
|
||||
Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
|
||||
|
||||
when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
|
||||
.thenReturn(NEW_SCORER.mPackageName);
|
||||
|
||||
mNetworkScoreService.systemReady();
|
||||
|
||||
verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName);
|
||||
assertEquals(1,
|
||||
Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception {
|
||||
Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
|
||||
|
||||
when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
|
||||
.thenReturn(null);
|
||||
|
||||
mNetworkScoreService.systemReady();
|
||||
|
||||
verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
|
||||
assertEquals(1,
|
||||
Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
|
||||
WifiConfiguration configuration = new WifiConfiguration();
|
||||
mRecommendationRequest = new RecommendationRequest.Builder()
|
||||
.setCurrentRecommendedWifiConfig(configuration).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,12 +133,125 @@ public class NetworkScoreServiceTest {
|
||||
mNetworkScoreService.systemRunning();
|
||||
|
||||
verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
|
||||
new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
|
||||
new ComponentName(NEW_SCORER.packageName,
|
||||
NEW_SCORER.recommendationServiceClassName))),
|
||||
any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.SYSTEM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestScores_noPermission() throws Exception {
|
||||
doThrow(new SecurityException()).when(mContext)
|
||||
.enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
|
||||
anyString());
|
||||
try {
|
||||
mNetworkScoreService.requestScores(null);
|
||||
fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
|
||||
} catch (SecurityException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestScores_providerNotConnected() throws Exception {
|
||||
assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
|
||||
verifyZeroInteractions(mRecommendationProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestScores_providerThrowsRemoteException() throws Exception {
|
||||
injectProvider();
|
||||
doThrow(new RemoteException()).when(mRecommendationProvider)
|
||||
.requestScores(any(NetworkKey[].class));
|
||||
|
||||
assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestScores_providerAvailable() throws Exception {
|
||||
injectProvider();
|
||||
|
||||
final NetworkKey[] networks = new NetworkKey[0];
|
||||
assertTrue(mNetworkScoreService.requestScores(networks));
|
||||
verify(mRecommendationProvider).requestScores(networks);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendation_noPermission() throws Exception {
|
||||
doThrow(new SecurityException()).when(mContext)
|
||||
.enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
|
||||
anyString());
|
||||
try {
|
||||
mNetworkScoreService.requestRecommendation(mRecommendationRequest);
|
||||
fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
|
||||
} catch (SecurityException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendation_mainThread() throws Exception {
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.myLooper());
|
||||
try {
|
||||
mNetworkScoreService.requestRecommendation(mRecommendationRequest);
|
||||
fail("requestRecommendation run on main thread.");
|
||||
} catch (RuntimeException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendation_providerNotConnected() throws Exception {
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||
|
||||
final RecommendationResult result =
|
||||
mNetworkScoreService.requestRecommendation(mRecommendationRequest);
|
||||
assertNotNull(result);
|
||||
assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
|
||||
result.getWifiConfiguration());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendation_providerThrowsRemoteException() throws Exception {
|
||||
injectProvider();
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||
doThrow(new RemoteException()).when(mRecommendationProvider)
|
||||
.requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
|
||||
anyInt());
|
||||
|
||||
final RecommendationResult result =
|
||||
mNetworkScoreService.requestRecommendation(mRecommendationRequest);
|
||||
assertNotNull(result);
|
||||
assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
|
||||
result.getWifiConfiguration());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestRecommendation_resultReturned() throws Exception {
|
||||
injectProvider();
|
||||
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
|
||||
final WifiConfiguration wifiConfiguration = new WifiConfiguration();
|
||||
wifiConfiguration.SSID = "testRequestRecommendation_resultReturned";
|
||||
final RecommendationResult providerResult =
|
||||
new RecommendationResult(wifiConfiguration);
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult);
|
||||
doAnswer(invocation -> {
|
||||
bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class));
|
||||
invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle);
|
||||
return null;
|
||||
}).when(mRecommendationProvider)
|
||||
.requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
|
||||
anyInt());
|
||||
|
||||
final RecommendationResult result =
|
||||
mNetworkScoreService.requestRecommendation(mRecommendationRequest);
|
||||
assertNotNull(result);
|
||||
assertEquals(providerResult.getWifiConfiguration().SSID,
|
||||
result.getWifiConfiguration().SSID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateScores_notActiveScorer() {
|
||||
when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
|
||||
@@ -287,45 +374,6 @@ public class NetworkScoreServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetActiveScorer_failure() throws RemoteException {
|
||||
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER);
|
||||
when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false);
|
||||
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
|
||||
CACHE_FILTER_NONE);
|
||||
|
||||
boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
|
||||
|
||||
assertFalse(success);
|
||||
verify(mNetworkScoreCache).clearScores();
|
||||
verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
|
||||
new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))),
|
||||
any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.SYSTEM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetActiveScorer_success() throws RemoteException {
|
||||
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER);
|
||||
when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true);
|
||||
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
|
||||
CACHE_FILTER_NONE);
|
||||
|
||||
boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
|
||||
|
||||
assertTrue(success);
|
||||
verify(mNetworkScoreCache).clearScores();
|
||||
verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
|
||||
new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
|
||||
any(ServiceConnection.class),
|
||||
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
|
||||
eq(UserHandle.SYSTEM));
|
||||
verify(mContext, times(2)).sendBroadcastAsUser(
|
||||
MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED),
|
||||
eq(UserHandle.SYSTEM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() {
|
||||
when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
|
||||
@@ -338,48 +386,6 @@ public class NetworkScoreServiceTest {
|
||||
} catch (SecurityException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableScoring_activeScorer() throws RemoteException {
|
||||
when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
|
||||
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
|
||||
when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
|
||||
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
|
||||
CACHE_FILTER_NONE);
|
||||
|
||||
mNetworkScoreService.disableScoring();
|
||||
|
||||
verify(mNetworkScoreCache).clearScores();
|
||||
verify(mContext).sendBroadcastAsUser(
|
||||
MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
|
||||
.setPackage(PREV_SCORER.mPackageName)),
|
||||
eq(UserHandle.SYSTEM));
|
||||
verify(mContext, never()).bindServiceAsUser(any(Intent.class),
|
||||
any(ServiceConnection.class), anyInt(), any(UserHandle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission()
|
||||
throws RemoteException {
|
||||
when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
|
||||
when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
|
||||
.thenReturn(PackageManager.PERMISSION_GRANTED);
|
||||
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
|
||||
when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
|
||||
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
|
||||
CACHE_FILTER_NONE);
|
||||
|
||||
mNetworkScoreService.disableScoring();
|
||||
|
||||
verify(mNetworkScoreCache).clearScores();
|
||||
verify(mContext).sendBroadcastAsUser(
|
||||
MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
|
||||
.setPackage(PREV_SCORER.mPackageName)),
|
||||
eq(UserHandle.SYSTEM));
|
||||
verify(mContext, never()).bindServiceAsUser(any(Intent.class),
|
||||
any(ServiceConnection.class), anyInt(), any(UserHandle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -434,4 +440,24 @@ public class NetworkScoreServiceTest {
|
||||
|
||||
assertFalse(stringWriter.toString().isEmpty());
|
||||
}
|
||||
|
||||
// "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
|
||||
private void injectProvider() {
|
||||
final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
|
||||
NEW_SCORER.recommendationServiceClassName);
|
||||
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
|
||||
when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(),
|
||||
isA(UserHandle.class))).thenAnswer(new Answer<Boolean>() {
|
||||
@Override
|
||||
public Boolean answer(InvocationOnMock invocation) throws Throwable {
|
||||
IBinder mockBinder = mock(IBinder.class);
|
||||
when(mockBinder.queryLocalInterface(anyString()))
|
||||
.thenReturn(mRecommendationProvider);
|
||||
invocation.getArgumentAt(1, ServiceConnection.class)
|
||||
.onServiceConnected(componentName, mockBinder);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mNetworkScoreService.systemRunning();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user