From fa4f08ecee6579b1a4e4503312956ea19dd0b80e Mon Sep 17 00:00:00 2001 From: Jeremy Joslin Date: Tue, 6 Dec 2016 07:42:38 -0800 Subject: [PATCH] Implement the discovery of a network recommendation provider. Updated the NetworkScorerAppManager to examine the list of configured network recommendation providers and to select the first valid provider. As part of this update the old logic of looking for a valid network scorer has been removed. Scorers/recommendation providers are only selected from the configured list now. The setActiveScorer() method has been deprecated as a result. The NetworkScoreService has been updated to monitor the list of potential recommendation providers and to reevaluate the binding whenever they change. It also monitors the new setting for NETWORK_RECOMMENDATIONS_ENABLED to connect or disconnect from the provider as needed. Test: runtest frameworks-services -c com.android.server.NetworkScoreServiceTest BUG: 33158362 Change-Id: I42aeb5223da794f71f7e58cb1bdf18817200cbf2 --- .../java/android/net/NetworkScoreManager.java | 2 +- .../android/net/NetworkScorerAppManager.java | 275 ++++++++--------- .../net/NetworkScorerAppManagerTest.java | 290 +++++++++--------- .../android/server/NetworkScoreService.java | 145 ++++----- .../server/NetworkScoreServiceTest.java | 141 +-------- 5 files changed, 349 insertions(+), 504 deletions(-) diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 865b8dd1fc46a..cd56287650980 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -183,7 +183,7 @@ public class NetworkScoreManager { if (app == null) { return null; } - return app.mPackageName; + return app.packageName; } /** diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java index ebb31c9056a99..4282ca75f2b60 100644 --- a/core/java/android/net/NetworkScorerAppManager.java +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -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. * - *

A network scorer is any application which: + *

A network recommendation provider is any application which: *

- * - * @return the list of scorers, or the empty list if there are no valid scorers. */ - public Collection 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 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 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 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 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 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 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 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; } } diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java index 02c25170bb743..5bfff26b08131 100644 --- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java +++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java @@ -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 scorers = new ArrayList<>(); - scorers.add(package1); - scorers.add(package2); - scorers.add(package3); - scorers.add(package4); - scorers.add(package5); - setScorers(scorers); - - Iterator 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 scorers) { - List 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() { - @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() { - @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 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 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() { @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); } } diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index a1c3564abf8c8..723d5d8369f49 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -25,32 +25,28 @@ import android.content.Intent; 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; @@ -99,10 +95,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { * manages the service connection. */ private class NetworkScorerPackageMonitor extends PackageMonitor { - final String mRegisteredPackage; + final List mPackagesToWatch; - private NetworkScorerPackageMonitor(String mRegisteredPackage) { - this.mRegisteredPackage = mRegisteredPackage; + private NetworkScorerPackageMonitor(List packagesToWatch) { + mPackagesToWatch = packagesToWatch; } @Override @@ -136,7 +132,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 +142,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 +159,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)); } @@ -181,19 +199,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** 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 +214,40 @@ public class NetworkScoreService extends INetworkScoreService.Stub { bindToScoringServiceIfNeeded(); } + private void registerRecommendationSettingObserverIfNeeded() { + final List 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 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,9 +261,9 @@ 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 (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)) { @@ -270,6 +288,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { mServiceConnection.disconnect(mContext); } mServiceConnection = null; + clearInternal(); } @Override @@ -349,7 +368,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 +379,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() { @@ -486,7 +463,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() { @Override diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 50911cb32ddf3..e59addb7b9c32 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -17,12 +17,9 @@ 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; @@ -30,7 +27,6 @@ import static org.mockito.Matchers.anyString; 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; @@ -46,7 +42,6 @@ import android.content.pm.PackageManager; 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; @@ -54,15 +49,10 @@ import android.net.WifiKey; 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; @@ -73,7 +63,6 @@ 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; /** @@ -85,12 +74,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; @@ -114,44 +99,6 @@ public class NetworkScoreServiceTest { 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)); - } - @Test public void testSystemRunning() { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); @@ -159,7 +106,8 @@ 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)); @@ -287,45 +235,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 +247,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