if (app == null) {
return null;
}
- return app.mPackageName;
+ return app.packageName;
}
/**
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 Collections.emptyList();
+ return null;
}
- 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;
+ final List<String> potentialPkgs = getPotentialRecommendationProviderPackages();
+ if (potentialPkgs.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "No Network Recommendation Providers specified.");
}
- 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;
+ 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.");
+ }
}
- 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;
+ }
+
+ // 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();
+ }
+
+ if (VERBOSE) {
+ Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray));
+ }
- // 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;
+ 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.");
}
}
+ }
- // 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;
- }
+ return packages;
+ }
- // 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));
+ 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;
}
- return scorers;
+ if (VERBOSE) {
+ Log.v(TAG, packageName + " does not have a service for " + serviceIntent);
+ }
+ return null;
}
/**
* 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();
}
/**
*
* @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. */
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
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;
}
}
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
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());
- }
-
- 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));
- }
-
- 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);
- }
- }
+ public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception {
+ setNetworkRecommendationPackageNames(/*no configured packages*/);
+ assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
+ }
- Mockito.when(mMockPm.queryBroadcastReceiversAsUser(
- Mockito.argThat(new ArgumentMatcher<Intent>() {
- @Override
- public boolean matches(Object object) {
- Intent intent = (Intent) object;
- return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction());
- }
- }), 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;
- }
+ public void testGetPotentialRecommendationProviderPackages_permissionNotGranted()
+ throws Exception {
+ setNetworkRecommendationPackageNames("package1");
+ mockScoreNetworksDenied("package1");
- ResolveInfo configActivityInfo = null;
- if (hasConfigActivity) {
- configActivityInfo = new ResolveInfo();
- configActivityInfo.activityInfo = new ActivityInfo();
- configActivityInfo.activityInfo.name = ".ConfigActivity";
- }
+ assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
+ }
- ResolveInfo serviceInfo = null;
- if (hasServiceInfo) {
- serviceInfo = new ResolveInfo();
- serviceInfo.serviceInfo = new ServiceInfo();
- serviceInfo.serviceInfo.name = ".ScoringService";
- }
+ 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");
- return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo);
+ assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
}
- private static class ResolveInfoHolder {
- final ResolveInfo scorerResolveInfo;
- final ResolveInfo configActivityResolveInfo;
- final ResolveInfo serviceResolveInfo;
+ public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted()
+ throws Exception {
+ setNetworkRecommendationPackageNames("package1");
+ mockScoreNetworksDenied("package1");
+ mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
- public ResolveInfoHolder(ResolveInfo scorerResolveInfo,
- ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) {
- this.scorerResolveInfo = scorerResolveInfo;
- this.configActivityResolveInfo = configActivityResolveInfo;
- this.serviceResolveInfo = serviceResolveInfo;
+ 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);
+ }
+
+ 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_RECOMMEND_NETWORKS
+ .equals(intent.getAction())
+ && packageName.equals(intent.getPackage());
+ }
+ }), Mockito.eq(flags))).thenReturn(serviceInfo);
}
}
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
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.IBinder;
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 com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.TransferPipe;
-
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
* 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
}
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);
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();
}
}
}
+ /**
+ * 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));
}
/** 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. */
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);
}
}
}
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 (scorerData != null && scorerData.recommendationServiceClassName != null) {
+ ComponentName componentName = new ComponentName(scorerData.packageName,
+ scorerData.recommendationServiceClassName);
// If we're connected to a different component then drop it.
if (mServiceConnection != null
&& !mServiceConnection.mComponentName.equals(componentName)) {
mServiceConnection.disconnect(mContext);
}
mServiceConnection = null;
+ clearInternal();
}
@Override
// 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
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>() {
writer.println("Scoring is disabled.");
return;
}
- writer.println("Current scorer: " + currentScorer.mPackageName);
+ writer.println("Current scorer: " + currentScorer.packageName);
sendCallback(new Consumer<INetworkScoreCache>() {
@Override
package com.android.server;
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.assertTrue;
import static junit.framework.Assert.fail;
-
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.res.Resources;
import android.net.INetworkScoreCache;
import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
import android.net.NetworkScorerAppManager;
import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.net.ScoredNetwork;
import android.os.IBinder;
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 org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
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;
}
@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));
- }
-
- @Test
public void testSystemRunning() {
when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
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 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);
when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
} 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