Add "Use open Wi-Fi automatically" setting.

- Add constant in NetworkScoreManager for the meta-data key required
for NetworkRecommendationProviders to specify which package provides
this feature.
- Add Setting to specify which package is enabled for providing this
feature.

Bug: 34773276
Test: make
Change-Id: I3f8209c21b8b219c242650f97ba407b5985a5250
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 8f3af66..edfaee4 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -96,17 +96,24 @@
     public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
 
     /**
-     * Activity action: launch a custom activity for configuring a scorer before enabling it.
-     * Scorer applications may choose to specify an activity for this action, in which case the
-     * framework will launch that activity which should return RESULT_OK if scoring was enabled.
-     *
-     * <p>If no activity is included in a scorer which implements this action, the system dialog for
-     * selecting a scorer will be shown instead.
+     * Activity action: launch an activity for configuring a provider for the feature that connects
+     * and secures open wifi networks available before enabling it. Applications that enable this
+     * feature must provide an activity for this action. The framework will launch this activity
+     * which must return RESULT_OK if the feature should be enabled.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE";
 
     /**
+     * Meta-data specified on a {@link NetworkRecommendationProvider} that specified the package
+     * name of the application that connects and secures open wifi networks automatically. The
+     * specified package must provide an Activity for {@link #ACTION_CUSTOM_ENABLE}.
+     * @hide
+     */
+    public static final String USE_OPEN_WIFI_PACKAGE_META_DATA =
+            "android.net.wifi.use_open_wifi_package";
+
+    /**
      * Broadcast action: the active scorer has been changed. Scorer apps may listen to this to
      * perform initialization once selected as the active scorer, or clean up unneeded resources
      * if another scorer has been selected. This is an explicit broadcast only sent to the
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index a166c7f..bbc1c79 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -26,6 +26,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.content.pm.ServiceInfo;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -60,21 +61,30 @@
         /** UID of the scorer app. */
         public final int packageUid;
         private final ComponentName mRecommendationService;
+        /**
+         * The {@link ComponentName} of the Activity to start before enabling the "connect to open
+         * wifi networks automatically" feature.
+         */
+        private final ComponentName mEnableUseOpenWifiActivity;
 
-        public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp) {
+        public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp,
+                ComponentName enableUseOpenWifiActivity) {
             this.packageUid = packageUid;
             this.mRecommendationService = recommendationServiceComp;
+            this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity;
         }
 
         protected NetworkScorerAppData(Parcel in) {
             packageUid = in.readInt();
             mRecommendationService = ComponentName.readFromParcel(in);
+            mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in);
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(packageUid);
             ComponentName.writeToParcel(mRecommendationService, dest);
+            ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest);
         }
 
         @Override
@@ -103,11 +113,16 @@
             return mRecommendationService;
         }
 
+        @Nullable public ComponentName getEnableUseOpenWifiActivity() {
+            return mEnableUseOpenWifiActivity;
+        }
+
         @Override
         public String toString() {
             return "NetworkScorerAppData{" +
                     "packageUid=" + packageUid +
                     ", mRecommendationService=" + mRecommendationService +
+                    ", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity +
                     '}';
         }
 
@@ -117,12 +132,13 @@
             if (o == null || getClass() != o.getClass()) return false;
             NetworkScorerAppData that = (NetworkScorerAppData) o;
             return packageUid == that.packageUid &&
-                    Objects.equals(mRecommendationService, that.mRecommendationService);
+                    Objects.equals(mRecommendationService, that.mRecommendationService) &&
+                    Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(packageUid, mRecommendationService);
+            return Objects.hash(packageUid, mRecommendationService, mEnableUseOpenWifiActivity);
         }
     }
 
@@ -165,12 +181,14 @@
             final String potentialPkg = potentialPkgs.get(i);
 
             // Look for the recommendation service class and required receiver.
-            final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg);
-            if (resolveServiceInfo != null) {
-                final ComponentName componentName =
-                        new ComponentName(potentialPkg, resolveServiceInfo.serviceInfo.name);
-                return new NetworkScorerAppData(resolveServiceInfo.serviceInfo.applicationInfo.uid,
-                        componentName);
+            final ServiceInfo serviceInfo = findRecommendationService(potentialPkg);
+            if (serviceInfo != null) {
+                final ComponentName serviceComponentName =
+                    new ComponentName(potentialPkg, serviceInfo.name);
+                final ComponentName useOpenWifiNetworksActivity =
+                        findUseOpenWifiNetworksActivity(serviceInfo);
+                return new NetworkScorerAppData(serviceInfo.applicationInfo.uid,
+                    serviceComponentName, useOpenWifiNetworksActivity);
             } else {
                 if (DEBUG) {
                     Log.d(TAG, potentialPkg + " does not have the required components, skipping.");
@@ -182,6 +200,36 @@
         return null;
     }
 
+    @Nullable private ComponentName findUseOpenWifiNetworksActivity(ServiceInfo serviceInfo) {
+        if (serviceInfo.metaData == null) {
+            if (DEBUG) {
+                Log.d(TAG, "No metadata found on recommendation service.");
+            }
+            return null;
+        }
+        final String useOpenWifiPackage = serviceInfo.metaData
+                .getString(NetworkScoreManager.USE_OPEN_WIFI_PACKAGE_META_DATA);
+        if (TextUtils.isEmpty(useOpenWifiPackage)) {
+            if (DEBUG) {
+                Log.d(TAG, "No use_open_wifi_package metadata found.");
+            }
+            return null;
+        }
+        final Intent enableUseOpenWifiIntent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE)
+                .setPackage(useOpenWifiPackage);
+        final ResolveInfo resolveActivityInfo = mContext.getPackageManager()
+                .resolveActivity(enableUseOpenWifiIntent, 0 /* flags */);
+        if (VERBOSE) {
+            Log.d(TAG, "Resolved " + enableUseOpenWifiIntent + " to " + serviceInfo);
+        }
+
+        if (resolveActivityInfo != null && resolveActivityInfo.activityInfo != null) {
+            return resolveActivityInfo.activityInfo.getComponentName();
+        }
+
+        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.
@@ -220,10 +268,9 @@
         return packages;
     }
 
-    private ResolveInfo findRecommendationService(String packageName) {
+    @Nullable private ServiceInfo findRecommendationService(String packageName) {
         final PackageManager pm = mContext.getPackageManager();
-        final int resolveFlags = 0;
-
+        final int resolveFlags = PackageManager.GET_META_DATA;
         final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
         serviceIntent.setPackage(packageName);
         final ResolveInfo resolveServiceInfo =
@@ -234,7 +281,7 @@
         }
 
         if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
-            return resolveServiceInfo;
+            return resolveServiceInfo.serviceInfo;
         }
 
         if (VERBOSE) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6c319ab..0499476 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8258,6 +8258,15 @@
         public static final String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
 
         /**
+         * The package name of the application that connect and secures high quality open wifi
+         * networks automatically.
+         *
+         * Type: string package name or null if the feature is either not provided or disabled.
+         * @hide
+         */
+        public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
+
+        /**
          * The number of milliseconds the {@link com.android.server.NetworkScoreService}
          * will give a recommendation request to complete before returning a default response.
          *
@@ -9689,6 +9698,7 @@
             CURATE_SAVED_OPEN_NETWORKS,
             WIFI_WAKEUP_ENABLED,
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+            USE_OPEN_WIFI_PACKAGE,
             WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
             EMERGENCY_TONE,
             CALL_AUTO_RETRY,
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index ce5d3ef..347024d 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -23,12 +23,14 @@
 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.Bundle;
 import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 
@@ -154,6 +156,62 @@
         assertEquals(924, activeScorer.packageUid);
     }
 
+    public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotSet()
+            throws Exception {
+        final ComponentName recoComponent = new ComponentName("package1", "class1");
+        setNetworkRecommendationPackageNames(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());
+        assertEquals(924, activeScorer.packageUid);
+        assertNull(activeScorer.getEnableUseOpenWifiActivity());
+    }
+
+    public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotResolved()
+            throws Exception {
+        final ComponentName recoComponent = new ComponentName("package1", "class1");
+        setNetworkRecommendationPackageNames(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());
+        assertEquals(924, activeScorer.packageUid);
+        assertNull(activeScorer.getEnableUseOpenWifiActivity());
+    }
+
+    public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityResolved()
+            throws Exception {
+        final ComponentName recoComponent = new ComponentName("package1", "class1");
+        final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
+        setNetworkRecommendationPackageNames(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());
+        assertEquals(924, activeScorer.packageUid);
+        assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity());
+    }
+
     public void testGetActiveScorer_providerNotAvailable()
             throws Exception {
         ContentResolver cr = mTargetContext.getContentResolver();
@@ -194,14 +252,25 @@
     }
 
     private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid) {
+        mockRecommendationServiceAvailable(compName, packageUid, null);
+    }
+
+    private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid,
+            String enableUseOpenWifiActivityPackage) {
         final ResolveInfo serviceInfo = new ResolveInfo();
         serviceInfo.serviceInfo = new ServiceInfo();
         serviceInfo.serviceInfo.name = compName.getClassName();
         serviceInfo.serviceInfo.packageName = compName.getPackageName();
         serviceInfo.serviceInfo.applicationInfo = new ApplicationInfo();
         serviceInfo.serviceInfo.applicationInfo.uid = packageUid;
+        if (enableUseOpenWifiActivityPackage != null) {
+            serviceInfo.serviceInfo.metaData = new Bundle();
+            serviceInfo.serviceInfo.metaData.putString(
+                    NetworkScoreManager.USE_OPEN_WIFI_PACKAGE_META_DATA,
+                    enableUseOpenWifiActivityPackage);
+        }
 
-        final int flags = 0;
+        final int flags = PackageManager.GET_META_DATA;
         when(mMockPm.resolveService(
                 Mockito.argThat(new ArgumentMatcher<Intent>() {
                     @Override
@@ -213,4 +282,22 @@
                     }
                 }), Mockito.eq(flags))).thenReturn(serviceInfo);
     }
+
+    private void mockEnableUseOpenWifiActivity(final ComponentName useOpenWifiComp) {
+        final ResolveInfo resolveActivityInfo = new ResolveInfo();
+        resolveActivityInfo.activityInfo = new ActivityInfo();
+        resolveActivityInfo.activityInfo.name = useOpenWifiComp.getClassName();
+        resolveActivityInfo.activityInfo.packageName = useOpenWifiComp.getPackageName();
+
+        final int flags = 0;
+        when(mMockPm.resolveActivity(
+                Mockito.argThat(new ArgumentMatcher<Intent>() {
+                    @Override
+                    public boolean matches(Object object) {
+                        Intent intent = (Intent) object;
+                        return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals(intent.getAction())
+                                && useOpenWifiComp.getPackageName().equals(intent.getPackage());
+                    }
+                }), Mockito.eq(flags))).thenReturn(resolveActivityInfo);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index c0b79be..665f01f 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -120,14 +120,16 @@
     private static final String INVALID_BSSID = "invalid_bssid";
     private static final ComponentName RECOMMENDATION_SERVICE_COMP =
             new ComponentName("newPackageName", "newScoringServiceClass");
+    private static final ComponentName USE_WIFI_ENABLE_ACTIVITY_COMP =
+            new ComponentName("useWifiPackageName", "enableUseWifiActivityClass");
     private static final ScoredNetwork SCORED_NETWORK =
             new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID), "00:00:00:00:00:00")),
                     null /* rssiCurve*/);
     private static final ScoredNetwork SCORED_NETWORK_2 =
             new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")),
                     null /* rssiCurve*/);
-    private static final NetworkScorerAppData NEW_SCORER =
-        new NetworkScorerAppData(1, RECOMMENDATION_SERVICE_COMP);
+    private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
+            1, RECOMMENDATION_SERVICE_COMP, USE_WIFI_ENABLE_ACTIVITY_COMP);
 
     @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
     @Mock private Context mContext;
@@ -661,6 +663,8 @@
 
         assertEquals(NEW_SCORER.getRecommendationServicePackageName(),
                 mNetworkScoreService.getActiveScorerPackage());
+        assertEquals(NEW_SCORER.getEnableUseOpenWifiActivity(),
+                mNetworkScoreService.getActiveScorer().getEnableUseOpenWifiActivity());
     }
 
     @Test
@@ -920,8 +924,8 @@
             throws Exception {
         when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
-        NetworkScorerAppData expectedAppData =
-                new NetworkScorerAppData(Binder.getCallingUid(), RECOMMENDATION_SERVICE_COMP);
+        NetworkScorerAppData expectedAppData = new NetworkScorerAppData(Binder.getCallingUid(),
+                RECOMMENDATION_SERVICE_COMP, USE_WIFI_ENABLE_ACTIVITY_COMP);
         bindToScorer(expectedAppData);
         assertEquals(expectedAppData, mNetworkScoreService.getActiveScorer());
     }
@@ -961,8 +965,8 @@
 
     private void bindToScorer(boolean callerIsScorer) {
         final int callingUid = callerIsScorer ? Binder.getCallingUid() : Binder.getCallingUid() + 1;
-        NetworkScorerAppData appData =
-                new NetworkScorerAppData(callingUid, RECOMMENDATION_SERVICE_COMP);
+        NetworkScorerAppData appData = new NetworkScorerAppData(callingUid,
+                RECOMMENDATION_SERVICE_COMP, USE_WIFI_ENABLE_ACTIVITY_COMP);
         bindToScorer(appData);
     }