Merge remote-tracking branch 'goog/security-aosp-mnc-mr1-release' into HEAD
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index d40b875..d6e41a1 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -1330,6 +1330,109 @@
         }
     }
 
+    /**
+     * Returns whether the provided network config is enabled for autojoin or not.
+     */
+    private static boolean isNetworkEnabled(WifiConfiguration config) {
+        return (config.status == Status.ENABLED && !config.ephemeral
+                && (config.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED));
+    }
+
+    /**
+     * Returns whether the provided network config is only temporarily disabled for autojoin or not.
+     */
+    private static boolean isNetworkTempDisabled(WifiConfiguration config) {
+        return (config.status == Status.ENABLED && !config.ephemeral
+                && ((config.autoJoinStatus <= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE)
+                && (config.autoJoinStatus > WifiConfiguration.AUTO_JOIN_ENABLED)));
+    }
+
+    /**
+     * Returns an integer representing a score for each configuration. The scores are assigned based
+     * on the status of the configuration. The scores are assigned according to this order:
+     * Fully enabled network > Temporarily disabled network > Permanently disabled network.
+     */
+    private static int getPnoNetworkSortScore(WifiConfiguration config) {
+        // Do we need static constants for these scores? We're not using them anywhere else though.
+        if (isNetworkEnabled(config)) {
+            return 3;
+        } else if (isNetworkTempDisabled(config)) {
+            return 2;
+        } else {
+            return 1;
+        }
+    }
+
+    /**
+     * PnoNetwork list sorting algorithm:
+     * 1, Place the fully enabled networks first. Among the fully enabled networks,
+     * sort them in descending order of their |numAssociation| values. If networks have
+     * the same |numAssociation|, then sort them in descending order of their |priority|
+     * values.
+     * 2. Next place all the temporarily disabled networks. Among the temporarily disabled
+     * networks, sort them in the same order as the fully enabled networks.
+     * 3. Place the permanently disabled networks last. The order among permanently disabled
+     * networks doesn't matter.
+     */
+    private static final Comparator<WifiConfiguration> sPnoListSortComparator =
+            new Comparator<WifiConfiguration>() {
+                public int compare(WifiConfiguration a, WifiConfiguration b) {
+                    int configAScore = getPnoNetworkSortScore(a);
+                    int configBScore = getPnoNetworkSortScore(b);
+                    if (configAScore == configBScore) {
+                        // If 2 networks have the same saved |numAssociation| value, sort them
+                        // according to their priority.
+                        if (a.numAssociation != b.numAssociation) {
+                            return Long.compare(b.numAssociation, a.numAssociation);
+                        } else {
+                            return Integer.compare(b.priority, a.priority);
+                        }
+                    } else {
+                        return Integer.compare(configBScore, configAScore);
+                    }
+                }
+            };
+
+    /**
+     * Retrieves an updated list of priorities for all the saved networks before
+     * enabling/disabling PNO.
+     *
+     * wpa_supplicant uses the priority of networks to build the list of SSID's to monitor
+     * during PNO. If there are a lot of saved networks, this list will be truncated and we
+     * might end up not connecting to the networks we use most frequently. So, We want the networks
+     * to be re-sorted based on the relative |numAssociation| values.
+     *
+     * @param enablePno boolean indicating whether PNO is being enabled or disabled.
+     * @return list of networks with updated priorities.
+     */
+    ArrayList<WifiNative.PnoNetworkPriority> retrievePnoNetworkPriorityList(boolean enablePno) {
+        ArrayList<WifiNative.PnoNetworkPriority> pnoList =
+                new ArrayList<WifiNative.PnoNetworkPriority>();
+        ArrayList<WifiConfiguration> wifiConfigurations =
+                new ArrayList<WifiConfiguration>(mConfiguredNetworks.values());
+        if (enablePno) {
+            Collections.sort(wifiConfigurations, sPnoListSortComparator);
+            // Let's use the network list size as the highest priority and then go down from there.
+            // So, the most frequently connected network has the highest priority now.
+            int priority = wifiConfigurations.size();
+            if (DBG) {
+                Log.d(TAG, "Retrieve network priorities before PNO. Max priority: " + priority);
+            }
+            for (WifiConfiguration config : wifiConfigurations) {
+                pnoList.add(new WifiNative.PnoNetworkPriority(config.networkId, priority));
+                priority--;
+            }
+        } else {
+            // Revert the priorities back to the saved config values after PNO.
+            if (DBG) Log.d(TAG, "Retrieve network priorities after PNO.");
+            for (WifiConfiguration config : wifiConfigurations) {
+                pnoList.add(new WifiNative.PnoNetworkPriority(config.networkId, config.priority));
+            }
+        }
+        return pnoList;
+    }
+
+
     String[] getWhiteListedSsids(WifiConfiguration config) {
         int num_ssids = 0;
         String nonQuoteSSID;
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 2e25bae..620f8b5 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -656,8 +656,47 @@
             return doBooleanCommand("DRIVER COUNTRY");
     }
 
-    public boolean enableBackgroundScan(boolean enable) {
+    /**
+     * Object holding the network ID and the corresponding priority to be set before enabling/
+     * disabling PNO.
+     */
+    public static class PnoNetworkPriority {
+        public int networkId;
+        public int priority;
+
+        PnoNetworkPriority(int networkId, int priority) {
+            this.networkId = networkId;
+            this.priority = priority;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sbuf = new StringBuilder();
+            sbuf.append(" Network ID=").append(this.networkId);
+            sbuf.append(" Priority=").append(this.priority);
+            return sbuf.toString();
+        }
+    }
+
+    public boolean enableBackgroundScan(
+            boolean enable,
+            List<PnoNetworkPriority> pnoNetworkList) {
         boolean ret;
+        // TODO: Couple of cases yet to be handled:
+        // 1. What if the network priority update fails, should we bail out of PNO setting?
+        // 2. If PNO setting fails below, should we go back and revert this priority change?
+        if (pnoNetworkList != null) {
+            if (DBG) Log.i(mTAG, "Update priorities for PNO. Enable: " + enable);
+            for (PnoNetworkPriority pnoNetwork : pnoNetworkList) {
+                // What if this fails? Should we bail out?
+                boolean isSuccess = setNetworkVariable(pnoNetwork.networkId,
+                        WifiConfiguration.priorityVarName,
+                        Integer.toString(pnoNetwork.priority));
+                if (!isSuccess) {
+                    Log.e(mTAG, "Update priority failed for :" + pnoNetwork.networkId);
+                }
+            }
+        }
         if (enable) {
             ret = doBooleanCommand("SET pno 1");
         } else {
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index d85da64..4e40f73 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -1919,14 +1919,13 @@
                 || workSource != null)) {
             mScanWorkSource = workSource != null ? workSource : new WorkSource(callingUid);
 
-            WorkSource batteryWorkSource = mScanWorkSource;
             if (mScanWorkSource.size() == 1 && mScanWorkSource.get(0) < 0) {
                 // WiFi uses negative UIDs to mean special things. BatteryStats don't care!
-                batteryWorkSource = new WorkSource(Process.WIFI_UID);
+                mScanWorkSource = new WorkSource(Process.WIFI_UID);
             }
 
             try {
-                mBatteryStats.noteWifiScanStartedFromSource(batteryWorkSource);
+                mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource);
             } catch (RemoteException e) {
                 log(e.toString());
             }
@@ -2358,7 +2357,9 @@
         if (enable) {
             mWifiConfigStore.enableAllNetworks();
         }
-        boolean ret = mWifiNative.enableBackgroundScan(enable);
+        List<WifiNative.PnoNetworkPriority> pnoList =
+                mWifiConfigStore.retrievePnoNetworkPriorityList(enable);
+        boolean ret = mWifiNative.enableBackgroundScan(enable, pnoList);
         if (ret) {
             mLegacyPnoEnabled = enable;
         } else {