diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index d5999a9d24537..d54ebaafb5bb6 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -131,10 +131,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { * manages the service connection. */ private class NetworkScorerPackageMonitor extends PackageMonitor { - final List mPackagesToWatch; + final String mPackageToWatch; - private NetworkScorerPackageMonitor(List packagesToWatch) { - mPackagesToWatch = packagesToWatch; + private NetworkScorerPackageMonitor(String packageToWatch) { + mPackageToWatch = packageToWatch; } @Override @@ -167,37 +167,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub { evaluateBinding(packageName, true /* forceUnbind */); } - private void evaluateBinding(String scorerPackageName, boolean forceUnbind) { - if (!mPackagesToWatch.contains(scorerPackageName)) { + private void evaluateBinding(String changedPackageName, boolean forceUnbind) { + if (!mPackageToWatch.equals(changedPackageName)) { // Early exit when we don't care about the package that has changed. return; } if (DBG) { - Log.d(TAG, "Evaluating binding for: " + scorerPackageName + Log.d(TAG, "Evaluating binding for: " + changedPackageName + ", forceUnbind=" + forceUnbind); } + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); if (activeScorer == null) { // Package change has invalidated a scorer, this will also unbind any service // connection. if (DBG) Log.d(TAG, "No active scorers available."); - unbindFromScoringServiceIfNeeded(); - } else if (activeScorer.getRecommendationServicePackageName().equals(scorerPackageName)) - { - // The active scoring service changed in some way. - if (DBG) { - Log.d(TAG, "Possible change to the active scorer: " - + activeScorer.getRecommendationServicePackageName()); - } + refreshBinding(); + } else { // The scoring service changed in some way. if (forceUnbind) { unbindFromScoringServiceIfNeeded(); } - bindToScoringServiceIfNeeded(activeScorer); - } else { - // One of the scoring apps on the device has changed and we may no longer be - // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded() - // will sort that out to leave us bound to the most recent active scorer. if (DBG) { Log.d(TAG, "Binding to " + activeScorer.getRecommendationServiceComponent() + " if needed."); @@ -271,60 +261,71 @@ 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"); - registerPackageMonitorIfNeeded(); registerRecommendationSettingsObserver(); - refreshRecommendationRequestTimeoutMs(); } /** Called when the system is ready for us to start third-party code. */ void systemRunning() { if (DBG) Log.d(TAG, "systemRunning"); - bindToScoringServiceIfNeeded(); } - private void onUserUnlocked(int userId) { + @VisibleForTesting + void onUserUnlocked(int userId) { + if (DBG) Log.d(TAG, "onUserUnlocked(" + userId + ")"); + refreshBinding(); + } + + private void refreshBinding() { + if (DBG) Log.d(TAG, "refreshBinding()"); + // Apply the default package name if the Setting isn't set. + mNetworkScorerAppManager.revertToDefaultIfNoActive(); registerPackageMonitorIfNeeded(); bindToScoringServiceIfNeeded(); } private void registerRecommendationSettingsObserver() { - final List providerPackages = - mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); - if (!providerPackages.isEmpty()) { - final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED); - mContentObserver.observe(enabledUri, - ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED); - } + final Uri packageNameUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_PACKAGE); + mContentObserver.observe(packageNameUri, + ServiceHandler.MSG_RECOMMENDATIONS_PACKAGE_CHANGED); final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS); mContentObserver.observe(timeoutUri, ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED); } + /** + * Ensures the package manager is registered to monitor the current active scorer. + * If a discrepancy is found any previous monitor will be cleaned up + * and a new monitor will be created. + * + * This method is idempotent. + */ private void registerPackageMonitorIfNeeded() { - if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded"); - final List providerPackages = - mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded()"); + final NetworkScorerAppData appData = mNetworkScorerAppManager.getActiveScorer(); synchronized (mPackageMonitorLock) { // Unregister the current monitor if needed. - if (mPackageMonitor != null) { + if (mPackageMonitor != null && (appData == null + || !appData.getRecommendationServicePackageName().equals( + mPackageMonitor.mPackageToWatch))) { if (DBG) { Log.d(TAG, "Unregistering package monitor for " - + mPackageMonitor.mPackagesToWatch); + + mPackageMonitor.mPackageToWatch); } mPackageMonitor.unregister(); mPackageMonitor = null; } - // Create and register the monitor if there are packages that could be providers. - if (!providerPackages.isEmpty()) { - mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages); + // Create and register the monitor if a scorer is active. + if (appData != null && mPackageMonitor == null) { + mPackageMonitor = new NetworkScorerPackageMonitor( + appData.getRecommendationServicePackageName()); // 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.mPackagesToWatch); + + mPackageMonitor.mPackageToWatch); } } } @@ -336,6 +337,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { bindToScoringServiceIfNeeded(scorerData); } + /** + * Ensures the service connection is bound to the current active scorer. + * If a discrepancy is found any previous connection will be cleaned up + * and a new connection will be created. + * + * This method is idempotent. + */ private void bindToScoringServiceIfNeeded(NetworkScorerAppData appData) { if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + appData + ")"); if (appData != null) { @@ -364,6 +372,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { synchronized (mServiceConnectionLock) { if (mServiceConnection != null) { mServiceConnection.disconnect(mContext); + if (DBG) Log.d(TAG, "Disconnected from: " + + mServiceConnection.mAppData.getRecommendationServiceComponent()); } mServiceConnection = null; } @@ -652,17 +662,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean setActiveScorer(String packageName) { - // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps - // to directly set the scorer app rather than having to use the consent dialog. The - // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to - // do the right thing and not enable this feature without explaining it to the user. - // In the future, should this API be opened to 3p apps, we will need to lock this down and - // figure out another way to streamline the UX. - - mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG); - - // Scorers (recommendation providers) are selected and no longer set. - return false; + // Only the system can set the active scorer + if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) { + return mNetworkScorerAppManager.setActiveScorer(packageName); + } else { + throw new SecurityException( + "Caller is neither the system process nor a score requester."); + } } /** @@ -699,7 +705,6 @@ public class NetworkScoreService extends INetworkScoreService.Stub { return null; } - /** * Returns metadata about the active scorer or null if there is no active scorer. */ @@ -726,7 +731,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { */ @Override public List getAllValidScorers() { - return mNetworkScorerAppManager.getAllValidScorers(); + // Only the system can access this data. + if (isCallerSystemProcess(getCallingUid()) || callerCanRequestScores()) { + return mNetworkScorerAppManager.getAllValidScorers(); + } else { + throw new SecurityException( + "Caller is neither the system process nor a score requester."); + } } @Override @@ -1158,7 +1169,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @VisibleForTesting public final class ServiceHandler extends Handler { public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1; - public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2; + public static final int MSG_RECOMMENDATIONS_PACKAGE_CHANGED = 2; public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3; public ServiceHandler(Looper looper) { @@ -1180,8 +1191,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { sendDefaultRecommendationResponse(request, remoteCallback); break; - case MSG_RECOMMENDATIONS_ENABLED_CHANGED: - bindToScoringServiceIfNeeded(); + case MSG_RECOMMENDATIONS_PACKAGE_CHANGED: + refreshBinding(); break; case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED: diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java index dea2f55743e0b..2f4485a4ea4cb 100644 --- a/services/core/java/com/android/server/NetworkScorerAppManager.java +++ b/services/core/java/com/android/server/NetworkScorerAppManager.java @@ -19,7 +19,6 @@ package com.android.server; import android.Manifest.permission; import android.annotation.Nullable; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -27,12 +26,12 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.NetworkScoreManager; import android.net.NetworkScorerAppData; -import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; @@ -43,78 +42,68 @@ import java.util.List; * * @hide */ +@VisibleForTesting public class NetworkScorerAppManager { private static final String TAG = "NetworkScorerAppManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private final Context mContext; + private final SettingsFacade mSettingsFacade; public NetworkScorerAppManager(Context context) { - mContext = context; + this(context, new SettingsFacade()); + } + + @VisibleForTesting + public NetworkScorerAppManager(Context context, SettingsFacade settingsFacade) { + mContext = context; + mSettingsFacade = settingsFacade; } /** * Returns the list of available scorer apps. The list will be empty if there are * no valid scorers. */ + @VisibleForTesting public List getAllValidScorers() { - return Collections.emptyList(); - } - - /** - * @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 recommendation provider is any application which: - *

    - *
  • Is listed in the config_networkRecommendationPackageNames config. - *
  • Declares the {@link permission#SCORE_NETWORKS} permission. - *
  • Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}. - *
- */ - @Nullable 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; + if (VERBOSE) Log.v(TAG, "getAllValidScorers()"); + final PackageManager pm = mContext.getPackageManager(); + final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); + final List resolveInfos = + pm.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA); + if (resolveInfos == null || resolveInfos.isEmpty()) { + if (DEBUG) Log.d(TAG, "Found 0 Services able to handle " + serviceIntent); + return Collections.emptyList(); } - final List potentialPkgs = getPotentialRecommendationProviderPackages(); - if (potentialPkgs.isEmpty()) { - if (DEBUG) { - Log.d(TAG, "No Network Recommendation Providers specified."); - } - return null; - } - - for (int i = 0; i < potentialPkgs.size(); i++) { - final String potentialPkg = potentialPkgs.get(i); - - // Look for the recommendation service class and required receiver. - final ServiceInfo serviceInfo = findRecommendationService(potentialPkg); - if (serviceInfo != null) { + List appDataList = new ArrayList<>(); + for (int i = 0; i < resolveInfos.size(); i++) { + final ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo; + if (hasPermissions(serviceInfo.packageName)) { + if (VERBOSE) { + Log.v(TAG, serviceInfo.packageName + " is a valid scorer/recommender."); + } final ComponentName serviceComponentName = - new ComponentName(potentialPkg, serviceInfo.name); + new ComponentName(serviceInfo.packageName, serviceInfo.name); final ComponentName useOpenWifiNetworksActivity = findUseOpenWifiNetworksActivity(serviceInfo); - return new NetworkScorerAppData(serviceInfo.applicationInfo.uid, - serviceComponentName, useOpenWifiNetworksActivity); + appDataList.add( + new NetworkScorerAppData(serviceInfo.applicationInfo.uid, + serviceComponentName, useOpenWifiNetworksActivity)); } else { - if (DEBUG) { - Log.d(TAG, potentialPkg + " does not have the required components, skipping."); - } + if (VERBOSE) Log.v(TAG, serviceInfo.packageName + + " is NOT a valid scorer/recommender."); } } - // None of the configured packages are valid. - return null; + return appDataList; } - @Nullable private ComponentName findUseOpenWifiNetworksActivity(ServiceInfo serviceInfo) { + @Nullable + private ComponentName findUseOpenWifiNetworksActivity(ServiceInfo serviceInfo) { if (serviceInfo.metaData == null) { if (DEBUG) { - Log.d(TAG, "No metadata found on recommendation service."); + Log.d(TAG, "No metadata found on " + serviceInfo.getComponentName()); } return null; } @@ -122,7 +111,8 @@ public class NetworkScorerAppManager { .getString(NetworkScoreManager.USE_OPEN_WIFI_PACKAGE_META_DATA); if (TextUtils.isEmpty(useOpenWifiPackage)) { if (DEBUG) { - Log.d(TAG, "No use_open_wifi_package metadata found."); + Log.d(TAG, "No use_open_wifi_package metadata found on " + + serviceInfo.getComponentName()); } return null; } @@ -131,7 +121,7 @@ public class NetworkScorerAppManager { final ResolveInfo resolveActivityInfo = mContext.getPackageManager() .resolveActivity(enableUseOpenWifiIntent, 0 /* flags */); if (VERBOSE) { - Log.d(TAG, "Resolved " + enableUseOpenWifiIntent + " to " + serviceInfo); + Log.d(TAG, "Resolved " + enableUseOpenWifiIntent + " to " + resolveActivityInfo); } if (resolveActivityInfo != null && resolveActivityInfo.activityInfo != null) { @@ -141,66 +131,6 @@ public class NetworkScorerAppManager { 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(); - } - - if (VERBOSE) { - Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray)); - } - - 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; - } - - @Nullable private ServiceInfo findRecommendationService(String packageName) { - final PackageManager pm = mContext.getPackageManager(); - final int resolveFlags = PackageManager.GET_META_DATA; - 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.serviceInfo; - } - - if (VERBOSE) { - Log.v(TAG, packageName + " does not have a service for " + serviceIntent); - } - return null; - } - /** * Get the application to use for scoring networks. * @@ -209,35 +139,107 @@ public class NetworkScorerAppManager { * it was disabled or uninstalled). */ @Nullable + @VisibleForTesting public NetworkScorerAppData getActiveScorer() { - if (isNetworkRecommendationsDisabled()) { - // If recommendations are disabled then there can't be an active scorer. + return getScorer(getNetworkRecommendationsPackage()); + } + + private NetworkScorerAppData getScorer(String packageName) { + if (TextUtils.isEmpty(packageName)) { return null; } // Otherwise return the recommendation provider (which may be null). - return getNetworkRecommendationProviderData(); + List apps = getAllValidScorers(); + for (int i = 0; i < apps.size(); i++) { + NetworkScorerAppData app = apps.get(i); + if (app.getRecommendationServicePackageName().equals(packageName)) { + return app; + } + } + + return null; + } + + private boolean hasPermissions(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + return pm.checkPermission(permission.SCORE_NETWORKS, packageName) + == PackageManager.PERMISSION_GRANTED; } /** * Set the specified package as the default scorer application. * - *

The caller must have permission to write to {@link android.provider.Settings.Global}. + *

The caller must have permission to write to {@link Settings.Global}. * - * @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. + * @param packageName the packageName of the new scorer to use. If null, the scoring app will + * revert back to the configured default. 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 or * a valid network recommendation provider exists. - * @deprecated Scorers are now selected from a configured list. */ - @Deprecated + @VisibleForTesting public boolean setActiveScorer(String packageName) { - return false; + String oldPackageName = getNetworkRecommendationsPackage(); + if (TextUtils.equals(oldPackageName, packageName)) { + // No change. + return true; + } + + Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); + + if (packageName == null) { + // revert to the default setting. + setNetworkRecommendationsPackage(getDefaultPackageSetting()); + return true; + } else { + // We only make the change if the new package is valid. + if (getScorer(packageName) != null) { + setNetworkRecommendationsPackage(packageName); + return true; + } else { + Log.w(TAG, "Requested network scorer is not valid: " + packageName); + return false; + } + } } - 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; + /** + * If the active scorer is null then revert to the default scorer. + */ + @VisibleForTesting + public void revertToDefaultIfNoActive() { + if (getActiveScorer() == null) { + final String defaultPackage = getDefaultPackageSetting(); + setNetworkRecommendationsPackage(defaultPackage); + Log.i(TAG, "Defaulted the network recommendations app to: " + defaultPackage); + } + } + + private String getDefaultPackageSetting() { + return mContext.getResources().getString( + R.string.config_defaultNetworkRecommendationProviderPackage); + } + + private String getNetworkRecommendationsPackage() { + return mSettingsFacade.getString(mContext, Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE); + } + + private void setNetworkRecommendationsPackage(String packageName) { + mSettingsFacade.putString(mContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, packageName); + } + + /** + * Wrapper around Settings to make testing easier. + */ + public static class SettingsFacade { + public boolean putString(Context context, String name, String value) { + return Settings.Global.putString(context.getContentResolver(), name, value); + } + + public String getString(Context context, String name) { + return Settings.Global.getString(context.getContentResolver(), name); + } } } diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 24ccabfabba5d..4141f2f722d6f 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -200,10 +200,10 @@ public class NetworkScoreServiceTest { } @Test - public void testSystemRunning() { + public void testOnUserUnlocked() { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); verify(mContext).bindServiceAsUser(MockUtils.checkIntent( new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS) @@ -548,9 +548,9 @@ public class NetworkScoreServiceTest { } @Test - public void testSetActiveScorer_noScoreNetworksPermission() { - doThrow(new SecurityException()).when(mContext) - .enforceCallingOrSelfPermission(eq(permission.SCORE_NETWORKS), anyString()); + public void testSetActiveScorer_noRequestNetworkScoresPermission() { + when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)) + .thenReturn(PackageManager.PERMISSION_DENIED); try { mNetworkScoreService.setActiveScorer(null); @@ -629,7 +629,7 @@ public class NetworkScoreServiceTest { @Test public void testIsCallerActiveScorer_noBoundService() throws Exception { - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); assertFalse(mNetworkScoreService.isCallerActiveScorer(Binder.getCallingUid())); } @@ -650,7 +650,7 @@ public class NetworkScoreServiceTest { @Test public void testGetActiveScorerPackage_notActive() throws Exception { - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); assertNull(mNetworkScoreService.getActiveScorerPackage()); } @@ -658,7 +658,7 @@ public class NetworkScoreServiceTest { @Test public void testGetActiveScorerPackage_active() throws Exception { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); assertEquals(NEW_SCORER.getRecommendationServicePackageName(), mNetworkScoreService.getActiveScorerPackage()); @@ -959,7 +959,7 @@ public class NetworkScoreServiceTest { return true; } }); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); } private void bindToScorer(boolean callerIsScorer) { @@ -973,7 +973,7 @@ public class NetworkScoreServiceTest { when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(appData); when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(), isA(UserHandle.class))).thenReturn(true); - mNetworkScoreService.systemRunning(); + mNetworkScoreService.onUserUnlocked(0); } private static class OnResultListener implements RemoteCallback.OnResultListener { diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java index f71421e9035b0..e9a2d34b63aec 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java @@ -16,11 +16,20 @@ package com.android.server; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest.permission; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -33,141 +42,80 @@ import android.net.NetworkScoreManager; import android.net.NetworkScorerAppData; import android.os.Bundle; import android.provider.Settings; -import android.test.InstrumentationTestCase; +import android.support.test.runner.AndroidJUnit4; import com.android.internal.R; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; -public class NetworkScorerAppManagerTest extends InstrumentationTestCase { +@RunWith(AndroidJUnit4.class) +public class NetworkScorerAppManagerTest { @Mock private Context mMockContext; @Mock private PackageManager mMockPm; @Mock private Resources mResources; - @Mock private ContentResolver mContentResolver; - private Context mTargetContext; + @Mock private NetworkScorerAppManager.SettingsFacade mSettingsFacade; private NetworkScorerAppManager mNetworkScorerAppManager; + private List mAvailableServices; - @Override + @Before public void setUp() throws Exception { - super.setUp(); - - // Configuration needed to make mockito/dexcache work. - mTargetContext = getInstrumentation().getTargetContext(); - System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath()); - ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); - Thread.currentThread().setContextClassLoader(newClassLoader); - MockitoAnnotations.initMocks(this); + mAvailableServices = new ArrayList<>(); when(mMockContext.getPackageManager()).thenReturn(mMockPm); + when(mMockPm.queryIntentServices(Mockito.argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS.equals(intent.getAction()); + } + }), eq(PackageManager.GET_META_DATA))).thenReturn(mAvailableServices); when(mMockContext.getResources()).thenReturn(mResources); - when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver()); - mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext); - } - - public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception { - setNetworkRecommendationPackageNames(/*no configured packages*/); - assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); - } - - public void testGetPotentialRecommendationProviderPackages_permissionNotGranted() - throws Exception { - setNetworkRecommendationPackageNames("package1"); - mockScoreNetworksDenied("package1"); - - 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 { - final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); - mockScoreNetworksDenied(recoComponent.getPackageName()); - mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - - assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); - } - - public void testGetNetworkRecommendationProviderData_available() throws Exception { - final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); - mockScoreNetworksGranted(recoComponent.getPackageName()); - mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - - NetworkScorerAppData appData = - mNetworkScorerAppManager.getNetworkRecommendationProviderData(); - assertNotNull(appData); - assertEquals(recoComponent, appData.getRecommendationServiceComponent()); - assertEquals(924, appData.packageUid); + + mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext, mSettingsFacade); } + @Test public void testGetActiveScorer_providerAvailable() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); assertEquals(924, activeScorer.packageUid); } + @Test + public void testGetActiveScorer_permissionMissing() throws Exception { + final ComponentName recoComponent = new ComponentName("package1", "class1"); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); + mockScoreNetworksDenied(recoComponent.getPackageName()); + mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNull(activeScorer); + } + + @Test public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotSet() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null /* enableUseOpenWifiPackageActivityPackage*/); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); @@ -175,17 +123,15 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertNull(activeScorer.getEnableUseOpenWifiActivity()); } + @Test public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotResolved() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, "package2" /* enableUseOpenWifiPackageActivityPackage*/); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); @@ -193,19 +139,17 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertNull(activeScorer.getEnableUseOpenWifiActivity()); } + @Test public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityResolved() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setNetworkRecoPackageSetting(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, enableUseOpenWifiComponent.getPackageName()); mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); - final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNotNull(activeScorer); assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent()); @@ -213,33 +157,105 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity()); } - public void testGetActiveScorer_providerNotAvailable() + @Test + public void testGetActiveScorer_packageSettingIsNull() throws Exception { - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); + // NETWORK_RECOMMENDATIONS_PACKAGE is null final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); assertNull(activeScorer); } - public void testGetActiveScorer_recommendationsDisabled() throws Exception { + @Test + public void testGetActiveScorer_packageSettingIsInvalid() throws Exception { final ComponentName recoComponent = new ComponentName("package1", "class1"); - setNetworkRecommendationPackageNames(recoComponent.getPackageName()); + setDefaultNetworkRecommendationPackage(recoComponent.getPackageName()); mockScoreNetworksGranted(recoComponent.getPackageName()); - mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */); - ContentResolver cr = mTargetContext.getContentResolver(); - Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0); + // NETWORK_RECOMMENDATIONS_PACKAGE is set to a package that isn't a recommender. 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); + @Test + public void testSetActiveScorer_noChange() throws Exception { + String packageName = "package"; + setNetworkRecoPackageSetting(packageName); + + assertTrue(mNetworkScorerAppManager.setActiveScorer(packageName)); + verify(mSettingsFacade, never()).putString(any(), any(), any()); + } + + @Test + public void testSetActiveScorer_nullPackage() throws Exception { + String packageName = "package"; + String defaultPackage = "defaultPackage"; + setNetworkRecoPackageSetting(packageName); + setDefaultNetworkRecommendationPackage(defaultPackage); + + assertTrue(mNetworkScorerAppManager.setActiveScorer(null)); + verify(mSettingsFacade).putString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, defaultPackage); + } + + @Test + public void testSetActiveScorer_validPackage() throws Exception { + String packageName = "package"; + String newPackage = "newPackage"; + setNetworkRecoPackageSetting(packageName); + final ComponentName recoComponent = new ComponentName(newPackage, "class1"); + mockScoreNetworksGranted(recoComponent.getPackageName()); + mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null); + + assertTrue(mNetworkScorerAppManager.setActiveScorer(newPackage)); + verify(mSettingsFacade).putString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, newPackage); + } + + @Test + public void testSetActiveScorer_invalidPackage() throws Exception { + String packageName = "package"; + String newPackage = "newPackage"; + setNetworkRecoPackageSetting(packageName); + // newPackage doesn't resolve to a valid recommender + + assertFalse(mNetworkScorerAppManager.setActiveScorer(newPackage)); + verify(mSettingsFacade, never()).putString(any(), any(), any()); + } + + + @Test + public void testRevertToDefaultIfNoActive_notActive() throws Exception { + String defaultPackage = "defaultPackage"; + setDefaultNetworkRecommendationPackage(defaultPackage); + + mNetworkScorerAppManager.revertToDefaultIfNoActive(); + + verify(mSettingsFacade).putString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, defaultPackage); + } + + @Test + public void testRevertToDefaultIfNoActive_active() throws Exception { + String packageName = "package"; + setNetworkRecoPackageSetting(packageName); + final ComponentName recoComponent = new ComponentName(packageName, "class1"); + mockScoreNetworksGranted(recoComponent.getPackageName()); + mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null); + + mNetworkScorerAppManager.revertToDefaultIfNoActive(); + + verify(mSettingsFacade, never()).putString(any(), any(), any()); + } + + private void setNetworkRecoPackageSetting(String packageName) { + when(mSettingsFacade.getString(mMockContext, + Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE)).thenReturn(packageName); + } + + private void setDefaultNetworkRecommendationPackage(String name) { + when(mResources.getString(R.string.config_defaultNetworkRecommendationProviderPackage)) + .thenReturn(name); } private void mockScoreNetworksGranted(String packageName) { @@ -282,6 +298,8 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { && compName.getPackageName().equals(intent.getPackage()); } }), Mockito.eq(flags))).thenReturn(serviceInfo); + + mAvailableServices.add(serviceInfo); } private void mockEnableUseOpenWifiActivity(final ComponentName useOpenWifiComp) {