diff options
5 files changed, 344 insertions, 499 deletions
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index e08767cf28fe..9c6c76930954 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 ebb31c9056a9..4282ca75f2b6 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. * - * <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; } /** @@ -182,10 +198,15 @@ public class NetworkScorerAppManager { * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because * it was disabled or uninstalled). */ + @Nullable public NetworkScorerAppData getActiveScorer() { - String scorerPackage = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP); - return getScorer(scorerPackage); + if (isNetworkRecommendationsDisabled()) { + // If recommendations are disabled then there can't be an active scorer. + return null; + } + + // Otherwise return the recommendation provider (which may be null). + return getNetworkRecommendationProviderData(); } /** @@ -195,33 +216,13 @@ public class NetworkScorerAppManager { * * @param packageName the packageName of the new scorer to use. If null, scoring will be * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. - * @return true if the scorer was changed, or false if the package is not a valid scorer. + * @return true if the scorer was changed, or false if the package is not a valid scorer or + * a valid network recommendation provider exists. + * @deprecated Scorers are now selected from a configured list. */ + @Deprecated public boolean setActiveScorer(String packageName) { - String oldPackageName = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP); - if (TextUtils.equals(oldPackageName, packageName)) { - // No change. - return true; - } - - Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); - - if (packageName == null) { - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, null); - return true; - } else { - // We only make the change if the new package is valid. - if (getScorer(packageName) != null) { - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, packageName); - return true; - } else { - Log.w(TAG, "Requested network scorer is not valid: " + packageName); - return false; - } - } + return false; } /** Determine whether the application with the given UID is the enabled scorer. */ @@ -230,7 +231,7 @@ public class NetworkScorerAppManager { if (defaultApp == null) { return false; } - if (callingUid != defaultApp.mPackageUid) { + if (callingUid != defaultApp.packageUid) { return false; } // To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always @@ -239,17 +240,9 @@ public class NetworkScorerAppManager { PackageManager.PERMISSION_GRANTED; } - /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */ - public NetworkScorerAppData getScorer(String packageName) { - if (TextUtils.isEmpty(packageName)) { - return null; - } - Collection<NetworkScorerAppData> applications = getAllValidScorers(); - for (NetworkScorerAppData app : applications) { - if (packageName.equals(app.mPackageName)) { - return app; - } - } - return null; + private boolean isNetworkRecommendationsDisabled() { + final ContentResolver cr = mContext.getContentResolver(); + // A value of 1 indicates enabled. + return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1; } } diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java index 02c25170bb74..5bfff26b0813 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<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); } } diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index a1c3564abf8c..723d5d8369f4 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<String> mPackagesToWatch; - private NetworkScorerPackageMonitor(String mRegisteredPackage) { - this.mRegisteredPackage = mRegisteredPackage; + private NetworkScorerPackageMonitor(List<String> 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<String> providerPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + if (!providerPackages.isEmpty()) { + final ContentResolver resolver = mContext.getContentResolver(); + final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED); + resolver.registerContentObserver(uri, false, new SettingsObserver()); + } + } + private void registerPackageMonitorIfNeeded() { if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded"); - NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer(); + final List<String> providerPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); synchronized (mPackageMonitorLock) { // Unregister the current monitor if needed. if (mPackageMonitor != null) { if (DBG) { Log.d(TAG, "Unregistering package monitor for " - + mPackageMonitor.mRegisteredPackage); + + mPackageMonitor.mPackagesToWatch); } mPackageMonitor.unregister(); mPackageMonitor = null; } - // Create and register the monitor if a scorer is active. - if (scorer != null) { - mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName); + // Create and register the monitor if there are packages that could be providers. + if (!providerPackages.isEmpty()) { + mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages); // TODO: Need to update when we support per-user scorers. http://b/23422763 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM, false /* externalStorage */); if (DBG) { Log.d(TAG, "Registered package monitor for " - + mPackageMonitor.mRegisteredPackage); + + mPackageMonitor.mPackagesToWatch); } } } @@ -243,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<INetworkScoreCache>() { @@ -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<INetworkScoreCache>() { @Override diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 50911cb32ddf..e59addb7b9c3 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; @@ -115,51 +100,14 @@ public class NetworkScoreServiceTest { } @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)); @@ -288,45 +236,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); when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) @@ -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 |