OSDN Git Service

Bind to the recommendation service specified in the Setting.
authorJeremy Joslin <jjoslin@google.com>
Mon, 13 Feb 2017 21:44:11 +0000 (13:44 -0800)
committerJeremy Joslin <jjoslin@google.com>
Fri, 24 Feb 2017 18:50:12 +0000 (10:50 -0800)
High level changes to NetworkScorerAppManager:
  * Implemented getAllValidScorers() and removed the old
    config-based discovery code.
  * Implemented setActiveScorer() to persist its given package
    name to Settings if it represents a valid network
    recommendation app.
  * Added a new method that reverts the setting back to the
    configured default if the current setting represents an
    invalid app.

High level changes to NetworkScoreService:
  * Updated the PackageMonitor to only watch a single package.
  * Moved most of the startup logic to onUserUnlocked() so we
    don't have to worry about whether or not the device is encrypted
    when querying the PackageManager.
  * The PackageMonitor is only registered/unregistered when the
    package it's watching changes.

Test: runtest frameworks-services -c com.android.server.NetworkScorerAppManagerTest
Test: runtest frameworks-services -c com.android.server.NetworkScoreServiceTest
Bug: 35095406
Change-Id: Ib32aca72dac4b831a64ceb3cd5c31e8fa2f61396

services/core/java/com/android/server/NetworkScoreService.java
services/core/java/com/android/server/NetworkScorerAppManager.java
services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java

index d5999a9..d54ebaa 100644 (file)
@@ -131,10 +131,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
      * manages the service connection.
      */
     private class NetworkScorerPackageMonitor extends PackageMonitor {
-        final List<String> mPackagesToWatch;
+        final String mPackageToWatch;
 
-        private NetworkScorerPackageMonitor(List<String> 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<String> 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<String> 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 <code>null</code> if there is no active scorer.
      */
@@ -726,7 +731,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub {
      */
     @Override
     public List<NetworkScorerAppData> 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:
index dea2f55..2f4485a 100644 (file)
@@ -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<NetworkScorerAppData> 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.
-     *
-     * <p>A network recommendation provider is any application which:
-     * <ul>
-     * <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config.
-     * <li>Declares the {@link permission#SCORE_NETWORKS} permission.
-     * <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}.
-     * </ul>
-     */
-    @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;
-        }
-
-        final List<String> potentialPkgs = getPotentialRecommendationProviderPackages();
-        if (potentialPkgs.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "No Network Recommendation Providers specified.");
-            }
-            return null;
+        if (VERBOSE) Log.v(TAG, "getAllValidScorers()");
+        final PackageManager pm = mContext.getPackageManager();
+        final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
+        final List<ResolveInfo> 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();
         }
 
-        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<NetworkScorerAppData> 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) {
@@ -142,102 +132,114 @@ public class NetworkScorerAppManager {
     }
 
     /**
-     * @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.
+     * Get the application to use for scoring networks.
+     *
+     * @return the scorer app info or null if scoring is disabled (including if no scorer was ever
+     *     selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
+     *     it was disabled or uninstalled).
      */
-    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();
-        }
+    @Nullable
+    @VisibleForTesting
+    public NetworkScorerAppData getActiveScorer() {
+        return getScorer(getNetworkRecommendationsPackage());
+    }
 
-        if (VERBOSE) {
-            Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray));
+    private NetworkScorerAppData getScorer(String packageName) {
+        if (TextUtils.isEmpty(packageName)) {
+            return null;
         }
 
-        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.");
-                }
+        // Otherwise return the recommendation provider (which may be null).
+        List<NetworkScorerAppData> apps = getAllValidScorers();
+        for (int i = 0; i < apps.size(); i++) {
+            NetworkScorerAppData app = apps.get(i);
+            if (app.getRecommendationServicePackageName().equals(packageName)) {
+                return app;
             }
         }
 
-        return packages;
+        return null;
     }
 
-    @Nullable private ServiceInfo findRecommendationService(String packageName) {
+    private boolean hasPermissions(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);
-        }
+        return pm.checkPermission(permission.SCORE_NETWORKS, packageName)
+                == PackageManager.PERMISSION_GRANTED;
+    }
 
-        if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
-            return resolveServiceInfo.serviceInfo;
+    /**
+     * Set the specified package as the default scorer application.
+     *
+     * <p>The caller must have permission to write to {@link Settings.Global}.
+     *
+     * @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.
+     */
+    @VisibleForTesting
+    public boolean setActiveScorer(String packageName) {
+        String oldPackageName = getNetworkRecommendationsPackage();
+        if (TextUtils.equals(oldPackageName, packageName)) {
+            // No change.
+            return true;
         }
 
-        if (VERBOSE) {
-            Log.v(TAG, packageName + " does not have a service for " + serviceIntent);
+        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;
+            }
         }
-        return null;
     }
 
     /**
-     * Get the application to use for scoring networks.
-     *
-     * @return the scorer app info or null if scoring is disabled (including if no scorer was ever
-     *     selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
-     *     it was disabled or uninstalled).
+     * If the active scorer is null then revert to the default scorer.
      */
-    @Nullable
-    public NetworkScorerAppData getActiveScorer() {
-        if (isNetworkRecommendationsDisabled()) {
-            // If recommendations are disabled then there can't be an active scorer.
-            return null;
+    @VisibleForTesting
+    public void revertToDefaultIfNoActive() {
+        if (getActiveScorer() == null) {
+            final String defaultPackage = getDefaultPackageSetting();
+            setNetworkRecommendationsPackage(defaultPackage);
+            Log.i(TAG, "Defaulted the network recommendations app to: " + defaultPackage);
         }
+    }
 
-        // Otherwise return the recommendation provider (which may be null).
-        return getNetworkRecommendationProviderData();
+    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);
     }
 
     /**
-     * Set the specified package as the default scorer application.
-     *
-     * <p>The caller must have permission to write to {@link android.provider.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.
-     * @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.
+     * Wrapper around Settings to make testing easier.
      */
-    @Deprecated
-    public boolean setActiveScorer(String packageName) {
-        return false;
-    }
+    public static class SettingsFacade {
+        public boolean putString(Context context, String name, String value) {
+            return Settings.Global.putString(context.getContentResolver(), name, value);
+        }
 
-    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;
+        public String getString(Context context, String name) {
+            return Settings.Global.getString(context.getContentResolver(), name);
+        }
     }
 }
index 24ccabf..4141f2f 100644 (file)
@@ -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 {
index f71421e..e9a2d34 100644 (file)
 
 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<ResolveInfo> 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<Intent>() {
+            @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<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());
+        mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext, mSettingsFacade);
     }
 
-    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 {
+    @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 */);
 
-        NetworkScorerAppData appData =
-                mNetworkScorerAppManager.getNetworkRecommendationProviderData();
-        assertNotNull(appData);
-        assertEquals(recoComponent, appData.getRecommendationServiceComponent());
-        assertEquals(924, appData.packageUid);
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNotNull(activeScorer);
+        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
+        assertEquals(924, activeScorer.packageUid);
     }
 
-    public void testGetActiveScorer_providerAvailable() throws Exception {
+    @Test
+    public void testGetActiveScorer_permissionMissing() throws Exception {
         final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecommendationPackageNames(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
+        setNetworkRecoPackageSetting(recoComponent.getPackageName());
+        mockScoreNetworksDenied(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);
+        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) {