Add support for batched wifi scans.

bug:9301872

Change-Id: I5a7edfdbd2b78a65119d11acad491eae350c0870
diff --git a/Android.mk b/Android.mk
index 7e34c84..bf7d4ab 100644
--- a/Android.mk
+++ b/Android.mk
@@ -384,6 +384,8 @@
 	frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
 	frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \
+	frameworks/base/wifi/java/android/net/wifi/BatchedScanSettings.aidl \
+	frameworks/base/wifi/java/android/net/wifi/BatchedScanResult.aidl \
 
 gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
 $(gen): PRIVATE_SRC_FILES := $(aidl_files)
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b9840e2..8af360c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -353,6 +353,9 @@
          Default value is 2 minutes. -->
     <integer translatable="false" name="config_wifi_driver_stop_delay">120000</integer>
 
+    <!-- Wifi driver supports batched scan -->
+    <bool translatable="false" name="config_wifi_batched_scan_supported">false</bool>
+
     <!-- Flag indicating whether the we should enable the automatic brightness in Settings.
          Software implementation will be used if config_hardware_auto_brightness_available is not set -->
     <bool name="config_automatic_brightness_available">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0bfed1b..634913c 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -281,7 +281,8 @@
   <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
   <java-symbol type="bool" name="config_useFixedVolume" />
   <java-symbol type="bool" name="config_forceDefaultOrientation" />
-  
+  <java-symbol type="bool" name="config_wifi_batched_scan_supported" />
+
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index 6e0e055..db030f1 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -25,18 +25,20 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
-import android.net.wifi.IWifiManager;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateMachine;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiWatchdogStateMachine;
 import android.net.DhcpInfo;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiStateMachine;
+import android.net.wifi.WifiWatchdogStateMachine;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Messenger;
@@ -63,6 +65,7 @@
 import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -121,6 +124,8 @@
     /* Tracks the persisted states for wi-fi & airplane mode */
     final WifiSettingsStore mSettingsStore;
 
+    final boolean mBatchedScanSupported;
+
     /**
      * Asynchronous channel to WifiStateMachine
      */
@@ -246,6 +251,9 @@
         mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
         mWifiController.start();
 
+        mBatchedScanSupported = mContext.getResources().getBoolean(
+                R.bool.config_wifi_batched_scan_supported);
+
         registerForScanModeChange();
         mContext.registerReceiver(
                 new BroadcastReceiver() {
@@ -314,6 +322,142 @@
         mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
     }
 
+    private class BatchedScanRequest extends DeathRecipient {
+        BatchedScanSettings settings;
+        int uid;
+
+        BatchedScanRequest(BatchedScanSettings settings, IBinder binder, int uid) {
+            super(0, null, binder, null);
+            this.settings = settings;
+            this.uid = uid;
+        }
+        public void binderDied() {
+            stopBatchedScan(settings, mBinder);
+        }
+        public String toString() {
+            return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
+        }
+    }
+
+    private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
+
+    public boolean isBatchedScanSupported() {
+        return mBatchedScanSupported;
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#requestBatchedScan()}
+     */
+    public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) {
+        enforceChangePermission();
+        if (mBatchedScanSupported == false) return false;
+        requested = new BatchedScanSettings(requested);
+        if (requested.isInvalid()) return false;
+        BatchedScanRequest r = new BatchedScanRequest(requested, binder, Binder.getCallingUid());
+        synchronized(mBatchedScanners) {
+            mBatchedScanners.add(r);
+            resolveBatchedScannersLocked();
+        }
+        return true;
+    }
+
+    public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
+        enforceAccessPermission();
+        if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return new ArrayList<BatchedScanResult>();
+            }
+            int currentUser = ActivityManager.getCurrentUser();
+            if (userId != currentUser) {
+                return new ArrayList<BatchedScanResult>();
+            } else {
+                return mWifiStateMachine.syncGetBatchedScanResultsList();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+
+    public void stopBatchedScan(BatchedScanSettings settings, IBinder binder) {
+        enforceChangePermission();
+        if (mBatchedScanSupported == false) return;
+        synchronized(mBatchedScanners) {
+            BatchedScanRequest found = null;
+            for (BatchedScanRequest r : mBatchedScanners) {
+                if (r.mBinder.equals(binder) && r.settings.equals(settings)) {
+                    found = r;
+                    break;
+                }
+            }
+            if (found != null) {
+                mBatchedScanners.remove(found);
+                resolveBatchedScannersLocked();
+            }
+        }
+    }
+
+    private void resolveBatchedScannersLocked() {
+        BatchedScanSettings setting = new BatchedScanSettings();
+        setting.scanIntervalSec = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+        int responsibleUid = 0;
+        setting.channelSet = new ArrayList<String>();
+
+        if (mBatchedScanners.size() == 0) {
+            mWifiStateMachine.setBatchedScanSettings(null, 0);
+            return;
+        }
+
+        for (BatchedScanRequest r : mBatchedScanners) {
+            BatchedScanSettings s = r.settings;
+            if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxScansPerBatch < setting.maxScansPerBatch) {
+                setting.maxScansPerBatch = s.maxScansPerBatch;
+                responsibleUid = r.uid;
+            }
+            if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxApPerScan > setting.maxApPerScan) {
+                setting.maxApPerScan = s.maxApPerScan;
+            }
+            if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
+                    s.scanIntervalSec < setting.scanIntervalSec) {
+                setting.scanIntervalSec = s.scanIntervalSec;
+                responsibleUid = r.uid;
+            }
+            if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxApForDistance > setting.maxApForDistance) {
+                setting.maxApForDistance = s.maxApForDistance;
+            }
+            if (s.channelSet != null) {
+                for (String i : s.channelSet) {
+                    if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
+                }
+            }
+        }
+        if (setting.channelSet.size() == 0) setting.channelSet = null;
+        if (setting.scanIntervalSec < BatchedScanSettings.MIN_INTERVAL_SEC) {
+            setting.scanIntervalSec = BatchedScanSettings.MIN_INTERVAL_SEC;
+        }
+        if (setting.maxScansPerBatch == BatchedScanSettings.UNSPECIFIED) {
+            setting.maxScansPerBatch = BatchedScanSettings.DEFAULT_SCANS_PER_BATCH;
+        }
+        if (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED) {
+            setting.maxApPerScan = BatchedScanSettings.DEFAULT_AP_PER_SCAN;
+        }
+        if (setting.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+            setting.scanIntervalSec = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+        }
+        if (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED) {
+            setting.maxApForDistance = BatchedScanSettings.DEFAULT_AP_FOR_DISTANCE;
+        }
+        mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid);
+    }
+
     private void enforceAccessPermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
                                                 "WifiService");
@@ -569,11 +713,11 @@
         int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
-        if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
-                != AppOpsManager.MODE_ALLOWED) {
-            return new ArrayList<ScanResult>();
-        }
         try {
+            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return new ArrayList<ScanResult>();
+            }
             int currentUser = ActivityManager.getCurrentUser();
             if (userId != currentUser) {
                 return new ArrayList<ScanResult>();
diff --git a/wifi/java/android/net/wifi/BatchedScanResult.aidl b/wifi/java/android/net/wifi/BatchedScanResult.aidl
new file mode 100644
index 0000000..a70bc0a
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanResult.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+parcelable BatchedScanResult;
diff --git a/wifi/java/android/net/wifi/BatchedScanResult.java b/wifi/java/android/net/wifi/BatchedScanResult.java
new file mode 100644
index 0000000..eb4e0276
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanResult.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Describes the Results of a batched set of wifi scans where the firmware performs many
+ * scans and stores the timestamped results without waking the main processor each time.
+ * @hide pending review
+ */
+public class BatchedScanResult implements Parcelable {
+    private static final String TAG = "BatchedScanResult";
+
+    /** Inidcates this scan was interrupted and may only have partial results. */
+    public boolean truncated;
+
+    /** The result of this particular scan. */
+    public final List<ScanResult> scanResults = new ArrayList<ScanResult>();
+
+
+    public BatchedScanResult() {
+    }
+
+    public BatchedScanResult(BatchedScanResult source) {
+        truncated = source.truncated;
+        for (ScanResult s : source.scanResults) scanResults.add(new ScanResult(s));
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("BatchedScanResult: ").
+                append("truncated: ").append(String.valueOf(truncated)).
+                append("scanResults: [");
+        for (ScanResult s : scanResults) {
+            sb.append(" <").append(s.toString()).append("> ");
+        }
+        sb.append(" ]");
+        return sb.toString();
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(truncated ? 1 : 0);
+        dest.writeInt(scanResults.size());
+        for (ScanResult s : scanResults) {
+            s.writeToParcel(dest, flags);
+        }
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<BatchedScanResult> CREATOR =
+        new Creator<BatchedScanResult>() {
+            public BatchedScanResult createFromParcel(Parcel in) {
+                BatchedScanResult result = new BatchedScanResult();
+                result.truncated = (in.readInt() == 1);
+                int count = in.readInt();
+                while (count-- > 0) {
+                    result.scanResults.add(ScanResult.CREATOR.createFromParcel(in));
+                }
+                return result;
+            }
+
+            public BatchedScanResult[] newArray(int size) {
+                return new BatchedScanResult[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/BatchedScanSettings.aidl b/wifi/java/android/net/wifi/BatchedScanSettings.aidl
new file mode 100644
index 0000000..8cfc508
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanSettings.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+parcelable BatchedScanSettings;
diff --git a/wifi/java/android/net/wifi/BatchedScanSettings.java b/wifi/java/android/net/wifi/BatchedScanSettings.java
new file mode 100644
index 0000000..82945d6
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanSettings.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Describes the settings for batched wifi scans where the firmware performs many
+ * scans and stores the timestamped results without waking the main processor each time.
+ * This can give information over time with minimal battery impact.
+ * @hide pending review
+ */
+public class BatchedScanSettings implements Parcelable {
+    private static final String TAG = "BatchedScanSettings";
+
+    /** Used to indicate no preference for an int value */
+    public final static int UNSPECIFIED = Integer.MAX_VALUE;
+
+    // TODO - make MIN/mAX dynamic and gservices adjustable.
+    public final static int MIN_SCANS_PER_BATCH = 2;
+    public final static int MAX_SCANS_PER_BATCH = 255;
+    public final static int DEFAULT_SCANS_PER_BATCH = MAX_SCANS_PER_BATCH;
+
+    public final static int MIN_AP_PER_SCAN = 2;
+    public final static int MAX_AP_PER_SCAN = 255;
+    public final static int DEFAULT_AP_PER_SCAN = 16;
+
+    public final static int MIN_INTERVAL_SEC = 0;
+    public final static int MAX_INTERVAL_SEC = 3600;
+    public final static int DEFAULT_INTERVAL_SEC = 30;
+
+    public final static int MIN_AP_FOR_DISTANCE = 0;
+    public final static int MAX_AP_FOR_DISTANCE = MAX_AP_PER_SCAN;
+    public final static int DEFAULT_AP_FOR_DISTANCE = 0;
+
+
+    /** The expected number of scans per batch.  Note that the firmware may drop scans
+     *  leading to fewer scans during the normal batch scan duration.  This value need not
+     *  be specified (may be set to {@link UNSPECIFIED}) by the application and we will try
+     *  to scan as many times as the firmware can support.  If another app requests fewer
+     *  scans per batch we will attempt to honor that.
+     */
+    public int maxScansPerBatch;
+
+    /** The maximum desired AP listed per scan.  Fewer AP may be returned if that's all
+     *  that the driver detected.  If another application requests more AP per scan that
+     *  will take precedence.  The if more channels are detected than we request, the APs
+     *  with the lowest signal strength will be dropped.
+     */
+    public int maxApPerScan;
+
+    /** The channels used in the scan.  If all channels should be used, {@code null} may be
+     *  specified.  If another application requests more channels or all channels, that
+     *  will take precedence.
+     */
+    public Collection<String> channelSet;
+
+    /** The time between the start of two sequential scans, in seconds.  If another
+     *  application requests more frequent scans, that will take precedence.  If this
+     * value is less than the duration of a scan, the next scan should start immediately.
+     */
+    public int scanIntervalSec;
+
+    /** The number of the best (strongest signal) APs for which the firmware will
+     *  attempt to get distance information (RTT).  Not all firmware supports this
+     *  feature, so it may be ignored.  If another application requests a greater
+     *  number, that will take precedence.
+     */
+    public int maxApForDistance;
+
+    public BatchedScanSettings() {
+        clear();
+    }
+
+    public void clear() {
+        maxScansPerBatch = UNSPECIFIED;
+        maxApPerScan = UNSPECIFIED;
+        channelSet = null;
+        scanIntervalSec = UNSPECIFIED;
+        maxApForDistance = UNSPECIFIED;
+    }
+
+    public BatchedScanSettings(BatchedScanSettings source) {
+        maxScansPerBatch = source.maxScansPerBatch;
+        maxApPerScan = source.maxApPerScan;
+        if (source.channelSet != null) {
+            channelSet = new ArrayList(source.channelSet);
+        }
+        scanIntervalSec = source.scanIntervalSec;
+        maxApForDistance = source.maxApForDistance;
+    }
+
+    private boolean channelSetIsValid() {
+        if (channelSet == null || channelSet.isEmpty()) return true;
+        for (String channel : channelSet) {
+            try {
+                int i = Integer.parseInt(channel);
+                if (i > 0 && i < 197) continue;
+            } catch (NumberFormatException e) {}
+            if (channel.equals("A") || channel.equals("B")) continue;
+            return false;
+        }
+        return true;
+    }
+    /** @hide */
+    public boolean isInvalid() {
+        if (maxScansPerBatch != UNSPECIFIED && (maxScansPerBatch < MIN_SCANS_PER_BATCH ||
+                maxScansPerBatch > MAX_SCANS_PER_BATCH)) return true;
+        if (maxApPerScan != UNSPECIFIED && (maxApPerScan < MIN_AP_PER_SCAN ||
+                maxApPerScan > MAX_AP_PER_SCAN)) return true;
+        if (channelSetIsValid() == false) return true;
+        if (scanIntervalSec != UNSPECIFIED && (scanIntervalSec < MIN_INTERVAL_SEC ||
+                scanIntervalSec > MAX_INTERVAL_SEC)) return true;
+        if (maxApForDistance != UNSPECIFIED && (maxApForDistance < MIN_AP_FOR_DISTANCE ||
+                maxApForDistance > MAX_AP_FOR_DISTANCE)) return true;
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof BatchedScanSettings == false) return false;
+        BatchedScanSettings o = (BatchedScanSettings)obj;
+        if (maxScansPerBatch != o.maxScansPerBatch ||
+              maxApPerScan != o.maxApPerScan ||
+              scanIntervalSec != o.scanIntervalSec ||
+              maxApForDistance != o.maxApForDistance) return false;
+        if (channelSet == null) {
+            return (o.channelSet == null);
+        }
+        return channelSet.equals(o.channelSet);
+    }
+
+    @Override
+    public int hashCode() {
+        return maxScansPerBatch +
+                (maxApPerScan * 3) +
+                (scanIntervalSec * 5) +
+                (maxApForDistance * 7) +
+                (channelSet.hashCode() * 11);
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        String none = "<none>";
+
+        sb.append("BatchScanSettings [maxScansPerBatch: ").
+                append(maxScansPerBatch == UNSPECIFIED ? none : maxScansPerBatch).
+                append(", maxApPerScan: ").append(maxApPerScan == UNSPECIFIED? none : maxApPerScan).
+                append(", scanIntervalSec: ").
+                append(scanIntervalSec == UNSPECIFIED ? none : scanIntervalSec).
+                append(", maxApForDistance: ").
+                append(maxApForDistance == UNSPECIFIED ? none : maxApForDistance).
+                append(", channelSet: ");
+        if (channelSet == null) {
+            sb.append("ALL");
+        } else {
+            sb.append("<");
+            for (String channel : channelSet) {
+                sb.append(" " + channel);
+            }
+            sb.append(">");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(maxScansPerBatch);
+        dest.writeInt(maxApPerScan);
+        dest.writeInt(scanIntervalSec);
+        dest.writeInt(maxApForDistance);
+        dest.writeInt(channelSet == null ? 0 : channelSet.size());
+        if (channelSet != null) {
+            for (String channel : channelSet) dest.writeString(channel);
+        }
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<BatchedScanSettings> CREATOR =
+        new Creator<BatchedScanSettings>() {
+            public BatchedScanSettings createFromParcel(Parcel in) {
+                BatchedScanSettings settings = new BatchedScanSettings();
+                settings.maxScansPerBatch = in.readInt();
+                settings.maxApPerScan = in.readInt();
+                settings.scanIntervalSec = in.readInt();
+                settings.maxApForDistance = in.readInt();
+                int channelCount = in.readInt();
+                if (channelCount > 0) {
+                    settings.channelSet = new ArrayList(channelCount);
+                    while (channelCount-- > 0) {
+                        settings.channelSet.add(in.readString());
+                    }
+                }
+                return settings;
+            }
+
+            public BatchedScanSettings[] newArray(int size) {
+                return new BatchedScanSettings[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 8103e84..c8cf323 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -16,8 +16,10 @@
 
 package android.net.wifi;
 
-import android.net.wifi.WifiInfo;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.ScanResult;
 import android.net.DhcpInfo;
 
@@ -114,5 +116,13 @@
     void enableTdls(String remoteIPAddress, boolean enable);
 
     void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
+
+    boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder);
+
+    void stopBatchedScan(in BatchedScanSettings requested, IBinder binder);
+
+    List<BatchedScanResult> getBatchedScanResults(String callingPackage);
+
+    boolean isBatchedScanSupported();
 }
 
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 9977419..12729d2 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -54,7 +54,26 @@
      * Time Synchronization Function (tsf) timestamp in microseconds when
      * this result was last seen.
      */
-     public long timestamp;
+    public long timestamp;
+
+    /**
+     * The approximate distance to the AP in centimeter, if available.  Else
+     * {@link UNSPECIFIED}.
+     * {@hide}
+     */
+    public int distanceCm;
+
+    /**
+     * The standard deviation of the distance to the AP, if available.
+     * Else {@link UNSPECIFIED}.
+     * {@hide}
+     */
+    public int distanceSdCm;
+
+    /**
+     * {@hide}
+     */
+    public final static int UNSPECIFIED = -1;
 
     /** {@hide} */
     public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
@@ -66,8 +85,23 @@
         this.level = level;
         this.frequency = frequency;
         this.timestamp = tsf;
+        this.distanceCm = UNSPECIFIED;
+        this.distanceSdCm = UNSPECIFIED;
     }
 
+    /** {@hide} */
+    public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
+            long tsf, int distCm, int distSdCm) {
+        this.wifiSsid = wifiSsid;
+        this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+        this.BSSID = BSSID;
+        this.capabilities = caps;
+        this.level = level;
+        this.frequency = frequency;
+        this.timestamp = tsf;
+        this.distanceCm = distCm;
+        this.distanceSdCm = distSdCm;
+    }
 
     /** copy constructor {@hide} */
     public ScanResult(ScanResult source) {
@@ -79,6 +113,8 @@
             level = source.level;
             frequency = source.frequency;
             timestamp = source.timestamp;
+            distanceCm = source.distanceCm;
+            distanceSdCm = source.distanceSdCm;
         }
     }
 
@@ -100,6 +136,11 @@
             append(", timestamp: ").
             append(timestamp);
 
+        sb.append(", distance: ").append((distanceCm != UNSPECIFIED ? distanceCm : "?")).
+                append("(cm)");
+        sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
+                append("(cm)");
+
         return sb.toString();
     }
 
@@ -121,6 +162,8 @@
         dest.writeInt(level);
         dest.writeInt(frequency);
         dest.writeLong(timestamp);
+        dest.writeInt(distanceCm);
+        dest.writeInt(distanceSdCm);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -137,7 +180,9 @@
                     in.readString(),
                     in.readInt(),
                     in.readInt(),
-                    in.readLong()
+                    in.readLong(),
+                    in.readInt(),
+                    in.readInt()
                 );
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6793710..01ca378 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -35,6 +35,7 @@
 import java.net.InetAddress;
 import java.util.concurrent.CountDownLatch;
 
+import com.android.internal.R;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
@@ -365,6 +366,14 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
     /**
+     * A batch of access point scans has been completed and the results areavailable.
+     * Call {@link #getBatchedScanResults()} to obtain the results.
+     * @hide pending review
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String BATCHED_SCAN_RESULTS_AVAILABLE_ACTION =
+            "android.net.wifi.BATCHED_RESULTS";
+    /**
      * The RSSI (signal strength) has changed.
      * @see #EXTRA_NEW_RSSI
      */
@@ -778,6 +787,59 @@
     }
 
     /**
+     * Request a batched scan for access points.  To end your requested batched scan,
+     * call stopBatchedScan with the same Settings.
+     *
+     * If there are mulitple requests for batched scans, the more demanding settings will
+     * take precidence.
+     *
+     * @param requested {@link BatchedScanSettings} the scan settings requested.
+     * @return false on known error
+     * @hide
+     */
+    public boolean requestBatchedScan(BatchedScanSettings requested) {
+        try {
+            return mService.requestBatchedScan(requested, new Binder());
+        } catch (RemoteException e) { return false; }
+    }
+
+    /**
+     * Check if the Batched Scan feature is supported.
+     *
+     * @return false if not supported.
+     * @hide
+     */
+    public boolean isBatchedScanSupported() {
+        try {
+            return mService.isBatchedScanSupported();
+        } catch (RemoteException e) { return false; }
+    }
+
+    /**
+     * End a requested batch scan for this applicaiton.  Note that batched scan may
+     * still occur if other apps are using them.
+     * @hide
+     */
+    public void stopBatchedScan(BatchedScanSettings requested) {
+        try {
+            mService.stopBatchedScan(requested, new Binder());
+        } catch (RemoteException e) {}
+    }
+
+    /**
+     * Retrieve the latest batched scan result.  This should be called immediately after
+     * {@link BATCHED_SCAN_RESULTS_AVAILABLE_ACTION} is received.
+     * @hide
+     */
+    public List<BatchedScanResult> getBatchedScanResults() {
+        try {
+            return mService.getBatchedScanResults(mContext.getBasePackageName());
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Return dynamic information about the current Wi-Fi connection, if any is active.
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index b1dd2ce..3dae225 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -216,6 +216,40 @@
         return doStringCommand("BSS RANGE=" + sid + "- MASK=0x21987");
     }
 
+    /**
+     * Format of command
+     * DRIVER WLS_BATCHING SET SCAN_FRQ=x BESTN=y CHANNEL=<z, w, t> RTT=s
+     * where x is an ascii representation of an integer number of seconds between scans
+     *       y is an ascii representation of an integer number of the max AP to remember per scan
+     *       z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values
+     *           indicating entire ranges of channels
+     *       s is an ascii representation of an integer number of highest-strength AP
+     *           for which we'd like approximate distance reported
+     *
+     * The return value is an ascii integer representing a guess of the number of scans
+     * the firmware can remember before it runs out of buffer space or -1 on error
+     */
+    public String setBatchedScanSettings(BatchedScanSettings settings) {
+        if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP");
+        String cmd = "DRIVER WLS_BATCHING SET SCAN_FRQ=" + settings.scanIntervalSec;
+        if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
+            cmd += " BESTN " + settings.maxApPerScan;
+        }
+        if (settings.channelSet != null && !settings.channelSet.isEmpty()) {
+            cmd += " CHANNEL=<";
+            for (String channel : settings.channelSet) cmd += " " + channel;
+            cmd += ">";
+        }
+        if (settings.maxApForDistance != BatchedScanSettings.UNSPECIFIED) {
+            cmd += " RTT=" + settings.maxApForDistance;
+        }
+        return doStringCommand(cmd);
+    }
+
+    public String getBatchedScanResults() {
+        return doStringCommand("DRIVER WLS_BATCHING GET");
+    }
+
     public boolean startDriver() {
         return doBooleanCommand("DRIVER START");
     }
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 4628c91..9815fa2 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -122,6 +122,11 @@
     private static final int SCAN_RESULT_CACHE_SIZE = 80;
     private final LruCache<String, ScanResult> mScanResultCache;
 
+    /* Batch scan results */
+    private final List<BatchedScanResult> mBatchedScanResults =
+            new ArrayList<BatchedScanResult>();
+    private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE;
+
     /* Chipset supports background scan */
     private final boolean mBackgroundScanSupported;
 
@@ -210,6 +215,7 @@
     private AlarmManager mAlarmManager;
     private PendingIntent mScanIntent;
     private PendingIntent mDriverStopIntent;
+    private PendingIntent mBatchedScanIntervalIntent;
 
     /* Tracks current frequency mode */
     private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
@@ -355,6 +361,13 @@
 
     public static final int CMD_BOOT_COMPLETED            = BASE + 134;
 
+    /* change the batch scan settings.
+     * arg1 = responsible UID
+     * obj = the new settings
+     */
+    public static final int CMD_SET_BATCH_SCAN            = BASE + 135;
+    public static final int CMD_START_NEXT_BATCHED_SCAN   = BASE + 136;
+
     public static final int CONNECT_MODE                   = 1;
     public static final int SCAN_ONLY_MODE                 = 2;
     public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE   = 3;
@@ -519,6 +532,8 @@
     private static final String ACTION_DELAYED_DRIVER_STOP =
         "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
 
+    private static final String ACTION_REFRESH_BATCHED_SCAN =
+            "com.android.server.WifiManager.action.REFRESH_BATCHED_SCAN";
     /**
      * Keep track of whether WIFI is running.
      */
@@ -541,6 +556,9 @@
 
     private final IBatteryStats mBatteryStats;
 
+    private BatchedScanSettings mBatchedScanSettings = null;
+
+
     public WifiStateMachine(Context context, String wlanInterface) {
         super("WifiStateMachine");
 
@@ -577,6 +595,9 @@
         Intent scanIntent = new Intent(ACTION_START_SCAN, null);
         mScanIntent = PendingIntent.getBroadcast(mContext, SCAN_REQUEST, scanIntent, 0);
 
+        Intent batchedIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
+        mBatchedScanIntervalIntent = PendingIntent.getBroadcast(mContext, 0, batchedIntent, 0);
+
         mDefaultFrameworkScanIntervalMs = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_scan_interval);
 
@@ -614,22 +635,25 @@
                 },
                 new IntentFilter(ACTION_START_SCAN));
 
-        IntentFilter screenFilter = new IntentFilter();
-        screenFilter.addAction(Intent.ACTION_SCREEN_ON);
-        screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
-        BroadcastReceiver screenReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
 
-                if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                    handleScreenStateChanged(true);
-                } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                    handleScreenStateChanged(false);
-                }
-            }
-        };
-        mContext.registerReceiver(screenReceiver, screenFilter);
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            handleScreenStateChanged(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            handleScreenStateChanged(false);
+                        } else if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
+                            startNextBatchedScanAsync();
+                        }
+                    }
+                }, filter);
 
         mContext.registerReceiver(
                 new BroadcastReceiver() {
@@ -738,6 +762,269 @@
         sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
     }
 
+    /**
+     * start or stop batched scanning using the given settings
+     */
+    public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) {
+        sendMessage(CMD_SET_BATCH_SCAN, callingUid, 0, settings);
+    }
+
+    public List<BatchedScanResult> syncGetBatchedScanResultsList() {
+        synchronized (mBatchedScanResults) {
+            List<BatchedScanResult> batchedScanList =
+                    new ArrayList<BatchedScanResult>(mBatchedScanResults.size());
+            for(BatchedScanResult result: mBatchedScanResults) {
+                batchedScanList.add(new BatchedScanResult(result));
+            }
+            return batchedScanList;
+        }
+    }
+
+    private void startBatchedScan() {
+        // first grab any existing data
+        retrieveBatchedScanData();
+
+        mAlarmManager.cancel(mBatchedScanIntervalIntent);
+
+        String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
+
+        try {
+            int expected = Integer.parseInt(scansExpected);
+            setNextBatchedAlarm(expected);
+        } catch (NumberFormatException e) {
+            loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
+        }
+    }
+
+    // called from BroadcastListener
+    private void startNextBatchedScanAsync() {
+        sendMessage(CMD_START_NEXT_BATCHED_SCAN);
+    }
+
+    private void startNextBatchedScan() {
+        // first grab any existing data
+        int nextCount = retrieveBatchedScanData();
+
+        setNextBatchedAlarm(nextCount);
+    }
+
+    // return true if new/different
+    private boolean recordBatchedScanSettings(BatchedScanSettings settings) {
+        if (DBG) log("set batched scan to " + settings);
+        if (settings != null) {
+            // TODO - noteBatchedScanStart(message.arg1);
+            if (settings.equals(mBatchedScanSettings)) return false;
+        } else {
+            if (mBatchedScanSettings == null) return false;
+            // TODO - noteBatchedScanStop(message.arg1);
+        }
+        mBatchedScanSettings = settings;
+        return true;
+    }
+
+    private void stopBatchedScan() {
+        mAlarmManager.cancel(mBatchedScanIntervalIntent);
+        retrieveBatchedScanData();
+        mWifiNative.setBatchedScanSettings(null);
+    }
+
+    private void setNextBatchedAlarm(int scansExpected) {
+
+        if (mBatchedScanSettings == null || scansExpected < 1) return;
+
+        if (mBatchedScanSettings.maxScansPerBatch < scansExpected) {
+            scansExpected = mBatchedScanSettings.maxScansPerBatch;
+        }
+
+        int secToFull = mBatchedScanSettings.scanIntervalSec;
+        secToFull *= scansExpected;
+
+        int debugPeriod = SystemProperties.getInt("wifi.batchedScan.pollPeriod", 0);
+        if (debugPeriod > 0) secToFull = debugPeriod;
+
+        // set the alarm to do the next poll.  We set it a little short as we'd rather
+        // wake up wearly than miss a scan due to buffer overflow
+        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+                + ((secToFull - (mBatchedScanSettings.scanIntervalSec / 2)) * 1000),
+                mBatchedScanIntervalIntent);
+    }
+
+    /**
+     * Start reading new scan data
+     * Data comes in as:
+     * "scancount=5\n"
+     * "nextcount=5\n"
+     *   "apcount=3\n"
+     *   "trunc\n" (optional)
+     *     "bssid=...\n"
+     *     "ssid=...\n"
+     *     "freq=...\n" (in Mhz)
+     *     "level=...\n"
+     *     "dist=...\n" (in cm)
+     *     "distsd=...\n" (standard deviation, in cm)
+     *     "===="
+     *     "bssid=...\n"
+     *     etc
+     *     "===="
+     *     "bssid=...\n"
+     *     etc
+     *     "%%%%"
+     *   "apcount=2\n"
+     *     "bssid=...\n"
+     *     etc
+     *     "%%%%
+     *   etc
+     *   "----"
+     */
+    private int retrieveBatchedScanData() {
+        String rawData = mWifiNative.getBatchedScanResults();
+        if (rawData == null) {
+            loge("Unexpected null BatchedScanResults");
+            return 0;
+        }
+
+        int nextCount = 0;
+        int scanCount = 0;
+        final String END_OF_SCAN = "====";
+        final String END_OF_BATCH = "%%%%";
+        final String END_OF_BATCHES = "----";
+        final String SCANCOUNT = "scancount=";
+        final String NEXTCOUNT = "nextcount=";
+        final String TRUNCATED = "trunc";
+        final String APCOUNT = "apcount=";
+        final String AGE = "age=";
+        final String DIST = "dist=";
+        final String DISTSD = "distsd=";
+
+        String splitData[] = rawData.split("\n");
+        int n = 0;
+        if (splitData[n].startsWith(SCANCOUNT)) {
+            try {
+                scanCount = Integer.parseInt(splitData[n++].substring(SCANCOUNT.length()));
+            } catch (NumberFormatException e) {}
+        }
+        if (scanCount == 0) {
+            loge("scanCount not found");
+            return 0;
+        }
+        if (splitData[n].startsWith(NEXTCOUNT)) {
+            try {
+                nextCount = Integer.parseInt(splitData[n++].substring(NEXTCOUNT.length()));
+            } catch (NumberFormatException e) {}
+        }
+        if (nextCount == 0) {
+            loge("nextCount not found");
+            return 0;
+        }
+
+        final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+        synchronized (mBatchedScanResults) {
+            mBatchedScanResults.clear();
+            BatchedScanResult batchedScanResult = new BatchedScanResult();
+
+            String bssid = null;
+            WifiSsid wifiSsid = null;
+            int level = 0;
+            int freq = 0;
+            int dist, distSd;
+            long tsf = 0;
+            dist = distSd = ScanResult.UNSPECIFIED;
+            long now = System.currentTimeMillis();
+
+            while (true) {
+                while (n < splitData.length) {
+                    if (splitData[n].equals(END_OF_BATCHES)) {
+                        if (++n != splitData.length) {
+                            loge("didn't consume " + (splitData.length-n));
+                        }
+                        if (mBatchedScanResults.size() > 0) {
+                            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                        }
+                        return nextCount;
+                    }
+                    if ((splitData[n].equals(END_OF_SCAN)) || splitData[n].equals(END_OF_BATCH)) {
+                        if (bssid != null) {
+                            batchedScanResult.scanResults.add(new ScanResult(
+                                    wifiSsid, bssid, "", level, freq, tsf, dist, distSd));
+                            wifiSsid = null;
+                            bssid = null;
+                            level = 0;
+                            freq = 0;
+                            tsf = 0;
+                            dist = distSd = ScanResult.UNSPECIFIED;
+                        }
+                        if (splitData[n].equals(END_OF_BATCH)) {
+                            if (batchedScanResult.scanResults.size() != 0) {
+                                mBatchedScanResults.add(batchedScanResult);
+                                batchedScanResult = new BatchedScanResult();
+                            } else {
+                                logd("Found empty batch");
+                            }
+                        }
+                        n++;
+                    } else if (splitData[n].equals(BSSID_STR)) {
+                        bssid = splitData[n++].substring(BSSID_STR.length());
+                    } else if (splitData[n].equals(FREQ_STR)) {
+                        try {
+                            freq = Integer.parseInt(splitData[n++].substring(FREQ_STR.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid freqency: " + splitData[n-1]);
+                            freq = 0;
+                        }
+                    } else if (splitData[n].equals(AGE)) {
+                        try {
+                            tsf = now - Long.parseLong(splitData[n++].substring(AGE.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid timestamp: " + splitData[n-1]);
+                            tsf = 0;
+                        }
+                    } else if (splitData[n].equals(SSID_STR)) {
+                        wifiSsid = WifiSsid.createFromAsciiEncoded(
+                                splitData[n++].substring(SSID_STR.length()));
+                    } else if (splitData[n].equals(LEVEL_STR)) {
+                        try {
+                            level = Integer.parseInt(splitData[n++].substring(LEVEL_STR.length()));
+                            if (level > 0) level -= 256;
+                        } catch (NumberFormatException e) {
+                            loge("Invalid level: " + splitData[n-1]);
+                            level = 0;
+                        }
+                    } else if (splitData[n].equals(DIST)) {
+                        try {
+                            dist = Integer.parseInt(splitData[n++].substring(DIST.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid distance: " + splitData[n-1]);
+                            dist = ScanResult.UNSPECIFIED;
+                        }
+                    } else if (splitData[n].equals(DISTSD)) {
+                        try {
+                            distSd = Integer.parseInt(splitData[n++].substring(DISTSD.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid distanceSd: " + splitData[n-1]);
+                            distSd = ScanResult.UNSPECIFIED;
+                        }
+                    }
+                }
+                rawData = mWifiNative.getBatchedScanResults();
+                if (rawData == null) {
+                    loge("Unexpected null BatchedScanResults");
+                    return nextCount;
+                }
+                splitData = rawData.split("\n");
+                if (splitData.length == 0 || splitData[0].equals("ok")) {
+                    loge("batch scan results just ended!");
+                    if (mBatchedScanResults.size() > 0) {
+                        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                    }
+                    return nextCount;
+                }
+                n = 0;
+            }
+        }
+    }
+
     // If workSource is not null, blame is given to it, otherwise blame is given to callingUid.
     private void noteScanStart(int callingUid, WorkSource workSource) {
         if (mScanWorkSource == null && (callingUid != UNKNOWN_SCAN_SOURCE || workSource != null)) {
@@ -1979,6 +2266,12 @@
                         sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode);
                     }
                     break;
+                case CMD_SET_BATCH_SCAN:
+                    recordBatchedScanSettings((BatchedScanSettings)message.obj);
+                    break;
+                case CMD_START_NEXT_BATCHED_SCAN:
+                    startNextBatchedScan();
+                    break;
                     /* Discard */
                 case CMD_START_SCAN:
                 case CMD_START_SUPPLICANT:
@@ -2472,6 +2765,10 @@
                 mWifiNative.stopFilteringMulticastV4Packets();
             }
 
+            if (mBatchedScanSettings != null) {
+                startBatchedScan();
+            }
+
             if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
                 transitionTo(mScanModeState);
@@ -2513,6 +2810,10 @@
                     noteScanStart(message.arg1, (WorkSource) message.obj);
                     startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
                     break;
+                case CMD_SET_BATCH_SCAN:
+                    recordBatchedScanSettings((BatchedScanSettings)message.obj);
+                    startBatchedScan();
+                    break;
                 case CMD_SET_COUNTRY_CODE:
                     String country = (String) message.obj;
                     if (DBG) log("set country code " + country);
@@ -2641,6 +2942,10 @@
             updateBatteryWorkSource(null);
             mScanResults = new ArrayList<ScanResult>();
 
+            if (mBatchedScanSettings != null) {
+                stopBatchedScan();
+            }
+
             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);