Initial implementation of WifiScanner

This change implements basic functionality of WifiScanner. Following
functionality is enabled

1. Scanning - specify a list of channels to scan
2. Significant change detection
3. AP hotlist

Change-Id: I4fbb2cccbb15df21aae7a81f5d9b17fde2bda8c0
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 161f0f8..ed8be96 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -17,6 +17,8 @@
 package com.android.server.wifi;
 
 import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiScanner;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pGroup;
@@ -1022,24 +1024,27 @@
 
     private long mWifiHalHandle;                        /* used by JNI to save wifi_handle */
     private long[] mWifiIfaceHandles;                   /* used by JNI to save interface handles */
+    private int mWlan0Index;
+    private int mP2p0Index;
 
-    public native boolean startHalNative();
-
-    public native void stopHalNative();
-
-    public native void waitForHalEventNative();
+    private native boolean startHalNative();
+    private native void stopHalNative();
+    private native void waitForHalEventNative();
 
     private class MonitorThread extends Thread {
         public void run() {
+            Log.i(mTAG, "Waiting for HAL events");
             waitForHalEventNative();
         }
     }
 
-    public void startHal() {
+    public boolean startHal() {
         if (startHalNative()) {
             new MonitorThread().start();
+            return true;
         } else {
             Log.i(mTAG, "Could not start hal");
+            return false;
         }
     }
 
@@ -1050,7 +1055,17 @@
     private native int getInterfacesNative();
 
     public int getInterfaces() {
-        return getInterfacesNative();
+        int num = getInterfacesNative();
+        for (int i = 0; i < num; i++) {
+            String name = getInterfaceNameNative(i);
+            Log.i(mTAG, "interface[" + i + "] = " + name);
+            if (name.equals("wlan0")) {
+                mWlan0Index = i;
+            } else if (name.equals("p2p0")) {
+                mP2p0Index = i;
+            }
+        }
+        return num;
     }
 
     private native String getInterfaceNameNative(int index);
@@ -1062,44 +1077,183 @@
         }
     }
 
-    private native boolean startScanNative(int iface, int id);
-    private native boolean stopScanNative(int iface, int id);
-
-    public static class ScanResult {
-        public String SSID;
-        public String BSSID;
-        public String capabilities;
-        public int level;
-        public int frequency;
-        public long timestamp;
+    public static class ScanCapabilities {
+        public int  max_scan_cache_size;                 // in number of scan results??
+        public int  max_scan_buckets;
+        public int  max_ap_cache_per_scan;
+        public int  max_rssi_sample_size;
+        public int  max_scan_reporting_threshold;        // in number of scan results??
+        public int  max_hotlist_aps;
+        public int  max_significant_wifi_change_aps;
     }
 
-    void onScanResults(int id, ScanResult[] results) {
+    public boolean getScanCapabilities(ScanCapabilities capabilities) {
+        return getScanCapabilitiesNative(mWlan0Index, capabilities);
+    }
 
-        /* !! This gets called on a different thread !! */
+    private native boolean getScanCapabilitiesNative(int iface, ScanCapabilities capabilities);
 
-        for (int i = 0; i < results.length; i++) {
-            Log.i(mTAG, "results[" + i + "].ssid = " + results[i].SSID);
-        }
+    private native boolean startScanNative(int iface, int id, ScanSettings settings);
+    private native boolean stopScanNative(int iface, int id);
+    private native ScanResult[] getScanResultsNative(int iface, boolean flush);
+
+    public static class ChannelSettings {
+        int frequency;
+        int dwell_time_ms;
+        boolean passive;
+    }
+
+    public static class BucketSettings {
+        int bucket;
+        int band;
+        int period_ms;
+        int report_events;
+        int num_channels;
+        ChannelSettings channels[] = new ChannelSettings[8];
+    }
+
+    public static class ScanSettings {
+        int base_period_ms;
+        int max_ap_per_scan;
+        int report_threshold;
+        int num_buckets;
+        BucketSettings buckets[] = new BucketSettings[8];
+    }
+
+    public interface ScanEventHandler {
+        void onScanResultsAvailable();
+        void onFullScanResult(ScanResult result, WifiScanner.InformationElement elems[]);
+    }
+
+    void onScanResultsAvailable(int id) {
+        mScanEventHandler.onScanResultsAvailable();
+    }
+
+    void onFullScanResult(int id, ScanResult result, WifiScanner.InformationElement elems[]) {
+        mScanEventHandler.onFullScanResult(result, elems);
     }
 
     private int mScanCmdId = 0;
+    private ScanEventHandler mScanEventHandler;
 
-    public boolean startScan() {
+    public boolean startScan(ScanSettings settings, ScanEventHandler eventHandler) {
         synchronized (mLock) {
             if (mScanCmdId != 0) {
                 return false;
             } else {
                 mScanCmdId = getNewCmdIdLocked();
             }
-        }
 
-        return startScanNative(0, mScanCmdId);          // results are reported by onScanResults
+            mScanEventHandler = eventHandler;
+
+            if (startScanNative(mWlan0Index, mScanCmdId, settings) == false) {
+                mScanEventHandler = null;
+                return false;
+            }
+
+            return true;
+        }
     }
 
     public void stopScan() {
         synchronized (mLock) {
-            stopScanNative(0, mScanCmdId);
+            stopScanNative(mWlan0Index, mScanCmdId);
+            mScanEventHandler = null;
+            mScanCmdId = 0;
         }
     }
+
+    public ScanResult[] getScanResults() {
+        return getScanResultsNative(mWlan0Index, /* flush = */ false);
+    }
+
+    public interface HotlistEventHandler {
+        void onHotlistApFound(ScanResult[] result);
+    }
+
+    private int mHotlistCmdId = 0;
+    private HotlistEventHandler mHotlistEventHandler;
+
+    private native boolean setHotlistNative(int iface, int id,
+            WifiScanner.HotlistSettings settings);
+    private native boolean resetHotlistNative(int iface, int id);
+
+    boolean setHotlist(WifiScanner.HotlistSettings settings, HotlistEventHandler eventHandler) {
+        synchronized (mLock) {
+            if (mHotlistCmdId != 0) {
+                return false;
+            } else {
+                mHotlistCmdId = getNewCmdIdLocked();
+            }
+
+            mHotlistEventHandler = eventHandler;
+            if (setHotlistNative(mWlan0Index, mScanCmdId, settings) == false) {
+                mHotlistEventHandler = null;
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    void resetHotlist() {
+        synchronized (mLock) {
+            if (mHotlistCmdId != 0) {
+                resetHotlistNative(mWlan0Index, mHotlistCmdId);
+                mHotlistCmdId = 0;
+                mHotlistEventHandler = null;
+            }
+        }
+    }
+
+    void onHotlistApFound(int id, ScanResult[] results) {
+        mHotlistEventHandler.onHotlistApFound(results);
+    }
+
+    public interface SignificantWifiChangeEventHandler {
+        void onChangesFound(ScanResult[] result);
+    }
+
+    SignificantWifiChangeEventHandler mSignificantWifiChangeHandler;
+    int mSignificantWifiChangeCmdId;
+
+    private native boolean trackSignificantWifiChangeNative(
+            int iface, int id, WifiScanner.WifiChangeSettings settings);
+    private native boolean untrackSignificantWifiChangeNative(int iface, int id);
+
+    boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings,
+                                       SignificantWifiChangeEventHandler handler) {
+        synchronized (mLock) {
+            if (mSignificantWifiChangeCmdId != 0) {
+                return false;
+            } else {
+                mSignificantWifiChangeCmdId = getNewCmdIdLocked();
+            }
+
+            mSignificantWifiChangeHandler = handler;
+            if (trackSignificantWifiChangeNative(mWlan0Index, mScanCmdId, settings) == false) {
+                mHotlistEventHandler = null;
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    void untrackSignificantWifiChange() {
+        synchronized (mLock) {
+            if (mSignificantWifiChangeCmdId != 0) {
+                untrackSignificantWifiChangeNative(mWlan0Index, mSignificantWifiChangeCmdId);
+                mSignificantWifiChangeCmdId = 0;
+                mSignificantWifiChangeHandler = null;
+            }
+        }
+    }
+
+    void onSignificantWifiChange(int id, ScanResult[] results) {
+        mSignificantWifiChangeHandler.onChangesFound(results);
+    }
+
+
+
 }
diff --git a/service/java/com/android/server/wifi/WifiScanningService.java b/service/java/com/android/server/wifi/WifiScanningService.java
new file mode 100644
index 0000000..f11c206
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiScanningService.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.android.server.wifi;
+
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+public class WifiScanningService extends SystemService {
+
+    private static final String TAG = "WifiScanningService";
+    WifiScanningServiceImpl mImpl;
+
+    public WifiScanningService() {
+        Log.i(TAG, "Creating " + Context.WIFI_SCANNING_SERVICE);
+    }
+
+    @Override
+    public void onStart() {
+        mImpl = new WifiScanningServiceImpl(getContext());
+
+        Log.i(TAG, "Starting " + Context.WIFI_SCANNING_SERVICE);
+        publishBinderService(Context.WIFI_SCANNING_SERVICE, mImpl);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            Log.i(TAG, "Registering " + Context.WIFI_SCANNING_SERVICE);
+            if (mImpl == null) {
+                mImpl = new WifiScanningServiceImpl(getContext());
+            }
+            mImpl.startService(getContext());
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/WifiScanningServiceImpl.java
new file mode 100644
index 0000000..8c637f3
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiScanningServiceImpl.java
@@ -0,0 +1,1301 @@
+/*
+ * 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 com.android.server.wifi;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.IWifiScanner;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiScanner.ScanSettings;
+import android.net.wifi.WifiScanner.FullScanResult;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.State;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class WifiScanningServiceImpl extends IWifiScanner.Stub {
+
+    private static final String TAG = "WifiScanningService";
+    private static final boolean DBG = true;
+    private static final int INVALID_KEY = 0;                               // same as WifiScanner
+
+    @Override
+    public Messenger getMessenger() {
+        return new Messenger(mClientHandler);
+    }
+
+    private class ClientHandler extends Handler {
+
+        ClientHandler(android.os.Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+
+            if (DBG) Log.d(TAG, "ClientHandler got" + msg);
+
+            switch (msg.what) {
+
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                        AsyncChannel c = (AsyncChannel) msg.obj;
+                        if (DBG) Slog.d(TAG, "New client listening to asynchronous messages: " +
+                                msg.replyTo);
+                        ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
+                        mClients.put(msg.replyTo, cInfo);
+                    } else {
+                        Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
+                    }
+                    return;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                    if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+                        Slog.e(TAG, "Send failed, client connection lost");
+                    } else {
+                        if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
+                    }
+                    if (DBG) Slog.d(TAG, "closing client " + msg.replyTo);
+                    mClients.remove(msg.replyTo);
+                    return;
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+                    AsyncChannel ac = new AsyncChannel();
+                    ac.connect(mContext, this, msg.replyTo);
+                    return;
+            }
+
+            ClientInfo ci = mClients.get(msg.replyTo);
+            if (ci == null) {
+                Slog.e(TAG, "Could not find client info for message " + msg.replyTo);
+                replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, null);
+                return;
+            }
+
+            int validCommands[] = {
+                    WifiScanner.CMD_SCAN,
+                    WifiScanner.CMD_START_BACKGROUND_SCAN,
+                    WifiScanner.CMD_STOP_BACKGROUND_SCAN,
+                    WifiScanner.CMD_SET_HOTLIST,
+                    WifiScanner.CMD_RESET_HOTLIST,
+                    WifiScanner.CMD_CONFIGURE_WIFI_CHANGE,
+                    WifiScanner.CMD_START_TRACKING_CHANGE,
+                    WifiScanner.CMD_STOP_TRACKING_CHANGE };
+
+            for(int cmd : validCommands) {
+                if (cmd == msg.what) {
+                    mStateMachine.sendMessage(Message.obtain(msg));
+                    return;
+                }
+            }
+
+            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, null);
+        }
+    }
+
+    private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE;
+
+    private static final int CMD_SCAN_RESULTS_AVAILABLE              = BASE + 0;
+    private static final int CMD_FULL_SCAN_RESULTS                   = BASE + 1;
+    private static final int CMD_HOTLIST_AP_FOUND                    = BASE + 2;
+    private static final int CMD_HOTLIST_AP_LOST                     = BASE + 3;
+    private static final int CMD_WIFI_CHANGE_DETECTED                = BASE + 4;
+    private static final int CMD_WIFI_CHANGES_STABILIZED             = BASE + 5;
+    private static final int CMD_DRIVER_LOADED                       = BASE + 6;
+    private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
+
+    private Context mContext;
+    private WifiScanningStateMachine mStateMachine;
+    private ClientHandler mClientHandler;
+    private WifiNative mWifiNative;
+
+    WifiScanningServiceImpl() { }
+
+    WifiScanningServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    public void startService(Context context) {
+        mContext = context;
+
+        HandlerThread thread = new HandlerThread("WifiScanningService");
+        thread.start();
+
+        mClientHandler = new ClientHandler(thread.getLooper());
+        mStateMachine = new WifiScanningStateMachine(thread.getLooper());
+        mWifiChangeStateMachine = new WifiChangeStateMachine(thread.getLooper());
+
+        mWifiNative = new WifiNative("");
+
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int state = intent.getIntExtra(
+                                WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
+                        if (DBG) Log.d(TAG, "SCAN_AVAILABLE : " + state);
+                        if (state == WifiManager.WIFI_STATE_ENABLED) {
+                            mStateMachine.sendMessage(CMD_DRIVER_LOADED);
+                        } else if (state == WifiManager.WIFI_STATE_DISABLED) {
+                            mStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
+                        }
+                    }
+                }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));
+
+        mStateMachine.start();
+        mWifiChangeStateMachine.start();
+    }
+
+    class WifiScanningStateMachine extends StateMachine implements WifiNative.ScanEventHandler,
+            WifiNative.HotlistEventHandler, WifiNative.SignificantWifiChangeEventHandler {
+
+        private final DefaultState mDefaultState = new DefaultState();
+        private final StartedState mStartedState = new StartedState();
+
+        public WifiScanningStateMachine(Looper looper) {
+            super(TAG, looper);
+
+            setLogRecSize(512);
+            setLogOnlyTransitions(false);
+            // setDbg(DBG);
+
+            addState(mDefaultState);
+                addState(mStartedState, mDefaultState);
+
+            setInitialState(mDefaultState);
+        }
+
+        @Override
+        public void onScanResultsAvailable() {
+            if (DBG) Log.d(TAG, "onScanResultAvailable event received");
+            sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
+        }
+
+        @Override
+        public void onFullScanResult(ScanResult result,
+                                     WifiScanner.InformationElement informationElements[]) {
+            if (DBG) Log.d(TAG, "Full scanresult received");
+            FullScanResult fullScanResult = new FullScanResult();
+            fullScanResult.result = result;
+            fullScanResult.informationElements = informationElements;
+            sendMessage(CMD_SCAN_RESULTS_AVAILABLE, 0, 0, fullScanResult);
+        }
+
+        @Override
+        public void onHotlistApFound(ScanResult[] results) {
+            if (DBG) Log.d(TAG, "HotlistApFound event received");
+            sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results);
+        }
+
+        @Override
+        public void onChangesFound(ScanResult[] results) {
+            if (DBG) Log.d(TAG, "onWifiChangesFound event received");
+            sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results);
+        }
+
+        class DefaultState extends State {
+            @Override
+            public void enter() {
+                if (DBG) Log.d(TAG, "DefaultState");
+            }
+            @Override
+            public boolean processMessage(Message msg) {
+
+                if (DBG) Log.d(TAG, "DefaultState got" + msg);
+
+                ClientInfo ci = mClients.get(msg.replyTo);
+
+                switch (msg.what) {
+                    case CMD_DRIVER_LOADED:
+                        if (mWifiNative.startHal() && mWifiNative.getInterfaces() != 0) {
+                            WifiNative.ScanCapabilities capabilities =
+                                    new WifiNative.ScanCapabilities();
+                            if (mWifiNative.getScanCapabilities(capabilities)) {
+                                transitionTo(mStartedState);
+                            } else {
+                                loge("could not get scan capabilities");
+                            }
+                        } else {
+                            loge("could not start HAL");
+                        }
+                        break;
+                    case WifiScanner.CMD_SCAN:
+                    case WifiScanner.CMD_START_BACKGROUND_SCAN:
+                    case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
+                    case WifiScanner.CMD_SET_HOTLIST:
+                    case WifiScanner.CMD_RESET_HOTLIST:
+                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
+                    case WifiScanner.CMD_START_TRACKING_CHANGE:
+                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
+                        replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, null);
+                        break;
+
+                    case CMD_SCAN_RESULTS_AVAILABLE:
+                        if (DBG) log("ignored scan results available event");
+                        break;
+
+                    default:
+                        break;
+                }
+
+                return HANDLED;
+            }
+        }
+
+        class StartedState extends State {
+
+            @Override
+            public void enter() {
+                if (DBG) Log.d(TAG, "StartedState");
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+
+                if (DBG) Log.d(TAG, "StartedState got" + msg);
+
+                ClientInfo ci = mClients.get(msg.replyTo);
+
+                switch (msg.what) {
+                    case CMD_DRIVER_UNLOADED:
+                        transitionTo(mDefaultState);
+                        break;
+                    case WifiScanner.CMD_SCAN:
+                        replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, null);
+                        break;
+                    case WifiScanner.CMD_START_BACKGROUND_SCAN:
+                        addScanRequest(ci, msg.arg2, (ScanSettings) msg.obj);
+                        replySucceeded(msg, null);
+                        break;
+                    case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
+                        removeScanRequest(ci, msg.arg2);
+                        break;
+                    case WifiScanner.CMD_GET_SCAN_RESULTS:
+                        getScanResults(ci, msg.arg2);
+                        break;
+                    case WifiScanner.CMD_SET_HOTLIST:
+                        setHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj);
+                        replySucceeded(msg, null);
+                        break;
+                    case WifiScanner.CMD_RESET_HOTLIST:
+                        resetHotlist(ci, msg.arg2);
+                        break;
+                    case WifiScanner.CMD_START_TRACKING_CHANGE:
+                        trackWifiChanges(ci, msg.arg2);
+                        replySucceeded(msg, null);
+                        break;
+                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
+                        untrackWifiChanges(ci, msg.arg2);
+                        break;
+                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
+                        replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, null);
+                        break;
+                    case CMD_SCAN_RESULTS_AVAILABLE: {
+                            ScanResult[] results = mWifiNative.getScanResults();
+                            Collection<ClientInfo> clients = mClients.values();
+                            for (ClientInfo ci2 : clients) {
+                                ci2.reportScanResults(results);
+                            }
+                        }
+                        break;
+                    case CMD_HOTLIST_AP_FOUND: {
+                            ScanResult[] results = (ScanResult[])msg.obj;
+                            if (DBG) Log.d(TAG, "Found " + results.length + " results");
+                            Collection<ClientInfo> clients = mClients.values();
+                            for (ClientInfo ci2 : clients) {
+                                ci2.reportHotlistResults(results);
+                            }
+                        }
+                        break;
+                    case CMD_WIFI_CHANGE_DETECTED: {
+                            ScanResult[] results = (ScanResult[])msg.obj;
+                            reportWifiChanged(results);
+                        }
+                        break;
+                    case CMD_WIFI_CHANGES_STABILIZED: {
+                            ScanResult[] results = (ScanResult[])msg.obj;
+                            reportWifiStabilized(results);
+                        }
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+
+                return HANDLED;
+            }
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            super.dump(fd, pw, args);
+            pw.println("number of clients : " + mClients.size());
+            pw.println();
+        }
+
+    }
+
+    /* client management */
+    HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
+
+    private class ClientInfo {
+        private static final int MAX_LIMIT = 16;
+        private final AsyncChannel mChannel;
+        private final Messenger mMessenger;
+
+        ClientInfo(AsyncChannel c, Messenger m) {
+            mChannel = c;
+            mMessenger = m;
+            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
+        }
+
+        @Override
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append("mChannel ").append(mChannel).append("\n");
+            sb.append("mMessenger ").append(mMessenger).append("\n");
+
+            Iterator<Map.Entry<Integer, ScanSettings>> it = mScanSettings.entrySet().iterator();
+            for (; it.hasNext(); ) {
+                Map.Entry<Integer, ScanSettings> entry = it.next();
+                sb.append("[ScanId ").append(entry.getKey()).append("\n");
+                sb.append("ScanSettings ").append(entry.getValue()).append("\n");
+                sb.append("]");
+            }
+
+            return sb.toString();
+        }
+
+        HashMap<Integer, ScanSettings> mScanSettings = new HashMap<Integer, ScanSettings>(4);
+        void addScanRequest(ScanSettings settings, int id) {
+            mScanSettings.put(id, settings);
+        }
+
+        void removeScanRequest(int id) {
+            mScanSettings.remove(id);
+        }
+
+        Collection<ScanSettings> getScanSettings() {
+            return mScanSettings.values();
+        }
+
+        void reportScanResults(ScanResult[] results) {
+            Iterator<Integer> it = mScanSettings.keySet().iterator();
+            while (it.hasNext()) {
+                int handler = it.next();
+                reportScanResults(results, handler);
+            }
+        }
+
+        void reportScanResults(ScanResult[] results, int handler) {
+            ScanSettings settings = mScanSettings.get(handler);
+
+            // check the channels this client asked for ..
+            int num_results = 0;
+            for (ScanResult result : results) {
+                for (WifiScanner.ChannelSpec channelSpec : settings.channels) {
+                    if (channelSpec.frequency == result.frequency) {
+                        num_results++;
+                        break;
+                    }
+                }
+            }
+
+            if (num_results == 0) {
+                // nothing to report
+                return;
+            }
+
+            ScanResult results2[] = new ScanResult[num_results];
+            int index = 0;
+            for (ScanResult result : results) {
+                for (WifiScanner.ChannelSpec channelSpec : settings.channels) {
+                    if (channelSpec.frequency == result.frequency) {
+                        WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(result.SSID);
+                        ScanResult newResult = new ScanResult(wifiSsid, result.BSSID, "",
+                                result.level, result.frequency, result.timestamp);
+                        results2[index] = newResult;
+                        index++;
+                        break;
+                    }
+                }
+            }
+
+            deliverScanResults(handler, results2);
+        }
+
+        void deliverScanResults(int handler, ScanResult results[]) {
+            WifiScanner.ParcelableScanResults parcelableScanResults =
+                    new WifiScanner.ParcelableScanResults(results);
+            mChannel.sendMessage(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanResults);
+        }
+
+        HashMap<Integer, WifiScanner.HotlistSettings> mHotlistSettings =
+                new HashMap<Integer, WifiScanner.HotlistSettings>();
+
+        void addHostlistSettings(WifiScanner.HotlistSettings settings, int handler) {
+            mHotlistSettings.put(handler, settings);
+        }
+
+        void removeHostlistSettings(int handler) {
+            mHotlistSettings.remove(handler);
+        }
+
+        Collection<WifiScanner.HotlistSettings> getHotlistSettings() {
+            return mHotlistSettings.values();
+        }
+
+        void reportHotlistResults(ScanResult[] results) {
+            Iterator<Map.Entry<Integer, WifiScanner.HotlistSettings>> it =
+                    mHotlistSettings.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<Integer, WifiScanner.HotlistSettings> entry = it.next();
+                int handler = entry.getKey();
+                WifiScanner.HotlistSettings settings = entry.getValue();
+                int num_results = 0;
+                for (ScanResult result : results) {
+                    for (WifiScanner.HotspotInfo hotspotInfo : settings.hotspotInfos) {
+                        if (result.BSSID.equalsIgnoreCase(hotspotInfo.bssid)) {
+                            num_results++;
+                            break;
+                        }
+                    }
+                }
+
+                if (num_results == 0) {
+                    // nothing to report
+                    return;
+                }
+
+                ScanResult results2[] = new ScanResult[num_results];
+                int index = 0;
+                for (ScanResult result : results) {
+                    for (WifiScanner.HotspotInfo hotspotInfo : settings.hotspotInfos) {
+                        if (result.BSSID.equalsIgnoreCase(hotspotInfo.bssid)) {
+                            results2[index] = result;
+                            index++;
+                        }
+                    }
+
+                }
+
+                WifiScanner.ParcelableScanResults parcelableScanResults =
+                        new WifiScanner.ParcelableScanResults(results2);
+
+                mChannel.sendMessage(WifiScanner.CMD_AP_FOUND, 0, handler, parcelableScanResults);
+            }
+        }
+
+        HashSet<Integer> mSignificantWifiHandlers = new HashSet<Integer>();
+        void addSignificantWifiChange(int handler) {
+            mSignificantWifiHandlers.add(handler);
+        }
+
+        void removeSignificantWifiChange(int handler) {
+            mSignificantWifiHandlers.remove(handler);
+        }
+
+        Collection<Integer> getWifiChangeHandlers() {
+            return mSignificantWifiHandlers;
+        }
+
+        void reportWifiChanged(ScanResult[] results) {
+            WifiScanner.ParcelableScanResults parcelableScanResults =
+                    new WifiScanner.ParcelableScanResults(results);
+            Iterator<Integer> it = mSignificantWifiHandlers.iterator();
+            while (it.hasNext()) {
+                int handler = it.next();
+                mChannel.sendMessage(WifiScanner.CMD_WIFI_CHANGE_DETECTED,
+                        0, handler, parcelableScanResults);
+            }
+        }
+
+        void reportWifiStabilized(ScanResult[] results) {
+            WifiScanner.ParcelableScanResults parcelableScanResults =
+                    new WifiScanner.ParcelableScanResults(results);
+            Iterator<Integer> it = mSignificantWifiHandlers.iterator();
+            while (it.hasNext()) {
+                int handler = it.next();
+                mChannel.sendMessage(WifiScanner.CMD_WIFI_CHANGES_STABILIZED,
+                        0, handler, parcelableScanResults);
+            }
+        }
+    }
+
+    void replySucceeded(Message msg, Object obj) {
+        if (msg.replyTo != null) {
+            Message reply = Message.obtain();
+            reply.what = WifiScanner.CMD_OP_SUCCEEDED;
+            reply.arg2 = msg.arg2;
+            reply.obj = obj;
+            try {
+                msg.replyTo.send(reply);
+            } catch (RemoteException e) {
+                // There's not much we can do if reply can't be sent!
+            }
+        } else {
+            // locally generated message; doesn't need a reply!
+        }
+    }
+
+    void replyFailed(Message msg, int reason, Object obj) {
+        Message reply = Message.obtain();
+        reply.what = WifiScanner.CMD_OP_FAILED;
+        reply.arg1 = reason;
+        reply.arg2 = msg.arg2;
+        reply.obj = obj;
+        try {
+            msg.replyTo.send(reply);
+        } catch (RemoteException e) {
+            // There's not much we can do if reply can't be sent!
+        }
+    }
+
+    private static class SettingsComputer {
+
+        private static class TimeBucket {
+            int periodInSecond;
+            int periodMinInSecond;
+            int periodMaxInSecond;
+
+            TimeBucket(int p, int min, int max) {
+                periodInSecond = p;
+                periodMinInSecond = min;
+                periodMaxInSecond = max;
+            }
+        }
+
+        private static final TimeBucket[] mTimeBuckets = new TimeBucket[] {
+                new TimeBucket( 5, 0, 10 ),
+                new TimeBucket( 10, 10, 25 ),
+                new TimeBucket( 30, 25, 55 ),
+                new TimeBucket( 60, 55, 100),
+                new TimeBucket( 120, 100, 240),
+                new TimeBucket( 300, 240, 500),
+                new TimeBucket( 600, 500, 1500),
+                new TimeBucket( 1800, 1500, WifiScanner.MAX_SCAN_PERIOD_MS) };
+
+        private static final int MAX_BUCKETS = 8;
+        private static final int MAX_CHANNELS = 8;
+
+        private WifiNative.ScanSettings mSettings;
+        {
+            mSettings = new WifiNative.ScanSettings();
+            mSettings.max_ap_per_scan = 10;
+            mSettings.base_period_ms = 5000;
+            mSettings.report_threshold = 80;
+
+            mSettings.buckets = new WifiNative.BucketSettings[MAX_BUCKETS];
+            for (int i = 0; i < mSettings.buckets.length; i++) {
+                WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
+                bucketSettings.bucket = i;
+                bucketSettings.report_events = 0;
+                bucketSettings.channels = new WifiNative.ChannelSettings[MAX_CHANNELS];
+                bucketSettings.num_channels = 0;
+                for (int j = 0; j < bucketSettings.channels.length; j++) {
+                    WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
+                    bucketSettings.channels[j] = channelSettings;
+                }
+                mSettings.buckets[i] = bucketSettings;
+            }
+        }
+
+        HashMap<Integer, Integer> mChannelToBucketMap = new HashMap<Integer, Integer>();
+
+        private int getBestBucket(WifiScanner.ScanSettings settings) {
+
+            // check to see if any of the channels are being scanned already
+            // and find the smallest bucket index (it represents the quickest
+            // period of scan)
+
+            WifiScanner.ChannelSpec channels[] = settings.channels;
+            if (channels == null) {
+                // set channels based on band
+                channels = getChannelsForBand(settings.band);
+            }
+
+            if (channels == null) {
+                // still no channels; then there's nothing to scan
+                Log.e(TAG, "No channels to scan!!");
+                return -1;
+            }
+
+            int mostFrequentBucketIndex = -1;
+
+            for (WifiScanner.ChannelSpec desiredChannelSpec : channels) {
+                if (mChannelToBucketMap.containsKey(desiredChannelSpec.frequency)) {
+                    int bucket = mChannelToBucketMap.get(desiredChannelSpec.frequency);
+                    if (bucket < mostFrequentBucketIndex) {
+                        mostFrequentBucketIndex = bucket;
+                    }
+                }
+            }
+
+            int bestBucketIndex = -1;                                   // best by period
+            for (int i = 0; i < mTimeBuckets.length; i++) {
+                TimeBucket bucket = mTimeBuckets[i];
+                if (bucket.periodMinInSecond * 1000 <= settings.periodInMs
+                        && settings.periodInMs < bucket.periodMaxInSecond * 1000) {
+                    // we set the time period to this
+                    bestBucketIndex = i;
+                    break;
+                }
+            }
+
+            if (mostFrequentBucketIndex != -1) {
+                if (mostFrequentBucketIndex < bestBucketIndex) {
+                    Log.d(TAG, "returning bucket number " + mostFrequentBucketIndex);
+                    return mostFrequentBucketIndex;
+                } else {
+                    for (WifiScanner.ChannelSpec desiredChannelSpec : channels) {
+                        mChannelToBucketMap.put(desiredChannelSpec.frequency, bestBucketIndex);
+                    }
+                    Log.d(TAG, "returning bucket number " + bestBucketIndex);
+                    return bestBucketIndex;
+                }
+            } else if (bestBucketIndex != -1) {
+                return bestBucketIndex;
+            }
+
+            Log.e(TAG, "Could not find suitable bucket for period " + settings.periodInMs);
+            return -1;
+        }
+
+        void prepChannelMap(WifiScanner.ScanSettings settings) {
+            getBestBucket(settings);
+        }
+
+        void addScanRequestToBucket(WifiScanner.ScanSettings settings) {
+
+            int bucketIndex = getBestBucket(settings);
+            if (bucketIndex == -1) {
+                Log.e(TAG, "Ignoring invalid settings");
+                return;
+            }
+
+            WifiScanner.ChannelSpec desiredChannels[] = settings.channels;
+            if (desiredChannels == null) {
+                // set channels based on band
+                desiredChannels = getChannelsForBand(settings.band);
+                if (desiredChannels == null) {
+                    // still no channels; then there's nothing to scan
+                    Log.e(TAG, "No channels to scan!!");
+                    return;
+                }
+            }
+
+            // merge the channel lists for these buckets
+            Log.d(TAG, "merging " + desiredChannels.length + " channels "
+                    + " for period " + settings.periodInMs);
+
+            WifiNative.BucketSettings bucket = mSettings.buckets[bucketIndex];
+            boolean added = (bucket.num_channels == 0)
+                    && (bucket.band == WifiScanner.WIFI_BAND_UNSPECIFIED);
+            Log.d(TAG, "existing " + bucket.num_channels + " channels ");
+
+            if (settings.band != WifiScanner.WIFI_BAND_UNSPECIFIED
+                    || (bucket.num_channels + desiredChannels.length) > bucket.channels.length) {
+                // can't accommodate all channels; switch to specifying band
+                bucket.num_channels = 0;
+                bucket.band = getBandFromChannels(bucket.channels)
+                        | getBandFromChannels(desiredChannels);
+            } else {
+                for (WifiScanner.ChannelSpec desiredChannelSpec : desiredChannels) {
+
+                    Log.d(TAG, "desired channel " + desiredChannelSpec.frequency);
+
+                    boolean found = false;
+                    for (WifiNative.ChannelSettings existingChannelSpec : bucket.channels) {
+                        if (desiredChannelSpec.frequency == existingChannelSpec.frequency) {
+                            found = true;
+                            break;
+                        }
+                    }
+
+                    if (!found) {
+                        WifiNative.ChannelSettings channelSettings = bucket.channels[bucket.num_channels];
+                        channelSettings.frequency = desiredChannelSpec.frequency;
+                        bucket.num_channels++;
+                        mChannelToBucketMap.put(bucketIndex, channelSettings.frequency);
+                    } else {
+                        if (DBG) Log.d(TAG, "Already scanning channel " + desiredChannelSpec.frequency);
+                    }
+                }
+            }
+
+            if (bucket.report_events < settings.reportEvents) {
+                bucket.report_events = settings.reportEvents;
+            }
+
+            if (added) {
+                bucket.period_ms = mTimeBuckets[bucketIndex].periodInSecond * 1000;
+                mSettings.num_buckets++;
+            }
+        }
+
+        public WifiNative.ScanSettings getComputedSettings() {
+            return mSettings;
+        }
+
+        public void compressBuckets() {
+            int num_buckets = 0;
+            for (int i = 0; i < mSettings.buckets.length; i++) {
+                if (mSettings.buckets[i].num_channels != 0
+                        || mSettings.buckets[i].band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
+                    mSettings.buckets[num_buckets] = mSettings.buckets[i];
+                    num_buckets++;
+                }
+            }
+            // remove unused buckets
+            for (int i = num_buckets; i < mSettings.buckets.length; i++) {
+                mSettings.buckets[i] = null;
+            }
+
+            mSettings.num_buckets = num_buckets;
+            if (num_buckets != 0) {
+                mSettings.base_period_ms = mSettings.buckets[0].period_ms;
+            }
+        }
+    }
+
+    void resetBuckets() {
+        SettingsComputer c = new SettingsComputer();
+        Collection<ClientInfo> clients = mClients.values();
+        for (ClientInfo ci : clients) {
+            Collection<ScanSettings> settings = ci.getScanSettings();
+            for (ScanSettings s : settings) {
+                c.prepChannelMap(s);
+            }
+        }
+
+        for (ClientInfo ci : clients) {
+            Collection<ScanSettings> settings = ci.getScanSettings();
+            for (ScanSettings s : settings) {
+                c.addScanRequestToBucket(s);
+            }
+        }
+
+        c.compressBuckets();
+
+        WifiNative.ScanSettings s = c.getComputedSettings();
+        if (s.num_buckets == 0) {
+            if (DBG) Log.d(TAG, "Stopping scan because there are no buckets");
+            mWifiNative.stopScan();
+        } else {
+            if (mWifiNative.startScan(s, mStateMachine)) {
+                if (DBG) Log.d(TAG, "Successfully started scan of " + s.num_buckets + " buckets");
+            } else {
+                if (DBG) Log.d(TAG, "Failed to start scan of " + s.num_buckets + " buckets");
+            }
+        }
+    }
+
+    void addScanRequest(ClientInfo ci, int handler, ScanSettings settings) {
+        ci.addScanRequest(settings, handler);
+        resetBuckets();
+    }
+
+    void removeScanRequest(ClientInfo ci, int handler) {
+        ci.removeScanRequest(handler);
+        resetBuckets();
+    }
+
+    void getScanResults(ClientInfo ci, int handler) {
+        ScanResult results[] = mWifiNative.getScanResults();
+        ci.reportScanResults(results, handler);
+    }
+
+    void resetHotlist() {
+        Collection<ClientInfo> clients = mClients.values();
+        int num_hotlist_ap = 0;
+
+        for (ClientInfo ci : clients) {
+            Collection<WifiScanner.HotlistSettings> c = ci.getHotlistSettings();
+            for (WifiScanner.HotlistSettings s : c) {
+                num_hotlist_ap +=  s.hotspotInfos.length;
+            }
+        }
+
+        if (num_hotlist_ap == 0) {
+            mWifiNative.resetHotlist();
+        } else {
+            WifiScanner.HotspotInfo hotspotInfos[] = new WifiScanner.HotspotInfo[num_hotlist_ap];
+            int index = 0;
+            for (ClientInfo ci : clients) {
+                Collection<WifiScanner.HotlistSettings> settings = ci.getHotlistSettings();
+                for (WifiScanner.HotlistSettings s : settings) {
+                    for (int i = 0; i < s.hotspotInfos.length; i++, index++) {
+                        hotspotInfos[index] = s.hotspotInfos[i];
+                    }
+                }
+            }
+
+            WifiScanner.HotlistSettings settings = new WifiScanner.HotlistSettings();
+            settings.hotspotInfos = hotspotInfos;
+            settings.apLostThreshold = 3;
+            mWifiNative.setHotlist(settings, mStateMachine);
+        }
+    }
+
+    void setHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) {
+        ci.addHostlistSettings(settings, handler);
+        resetHotlist();
+    }
+
+    void resetHotlist(ClientInfo ci, int handler) {
+        ci.removeHostlistSettings(handler);
+        resetHotlist();
+    }
+
+    WifiChangeStateMachine mWifiChangeStateMachine;
+
+    void trackWifiChanges(ClientInfo ci, int handler) {
+        mWifiChangeStateMachine.enable();
+        ci.addSignificantWifiChange(handler);
+    }
+
+    void untrackWifiChanges(ClientInfo ci, int handler) {
+        ci.removeSignificantWifiChange(handler);
+        Collection<ClientInfo> clients = mClients.values();
+        for (ClientInfo ci2 : clients) {
+            if (ci2.getWifiChangeHandlers().size() != 0) {
+                // there is at least one client watching for
+                // significant changes; so nothing more to do
+                return;
+            }
+        }
+
+        // no more clients looking for significant wifi changes
+        // no need to keep the state machine running; disable it
+        mWifiChangeStateMachine.disable();
+    }
+
+    void reportWifiChanged(ScanResult results[]) {
+        Collection<ClientInfo> clients = mClients.values();
+        for (ClientInfo ci : clients) {
+            ci.reportWifiChanged(results);
+        }
+    }
+
+    void reportWifiStabilized(ScanResult results[]) {
+        Collection<ClientInfo> clients = mClients.values();
+        for (ClientInfo ci : clients) {
+            ci.reportWifiStabilized(results);
+        }
+    }
+
+    class WifiChangeStateMachine extends StateMachine
+            implements WifiNative.SignificantWifiChangeEventHandler {
+
+        private static final String TAG = "WifiChangeStateMachine";
+
+        private static final int WIFI_CHANGE_CMD_NEW_SCAN_RESULTS           = 0;
+        private static final int WIFI_CHANGE_CMD_CHANGE_DETECTED            = 1;
+        private static final int WIFI_CHANGE_CMD_CHANGE_TIMEOUT             = 2;
+        private static final int WIFI_CHANGE_CMD_ENABLE                     = 3;
+        private static final int WIFI_CHANGE_CMD_DISABLE                    = 4;
+
+        private static final int MAX_APS_TO_TRACK = 3;
+        private static final int MOVING_SCAN_PERIOD_MS      = 10000;
+        private static final int STATIONARY_SCAN_PERIOD_MS  =  5000;
+        private static final int MOVING_STATE_TIMEOUT_MS    = 30000;
+
+        State mDefaultState = new DefaultState();
+        State mStationaryState = new StationaryState();
+        State mMovingState = new MovingState();
+
+        private static final String ACTION_TIMEOUT =
+                "com.android.server.WifiScanningServiceImpl.action.TIMEOUT";
+        AlarmManager  mAlarmManager;
+        PendingIntent mTimeoutIntent;
+        ScanResult    mCurrentHotspots[];
+
+        WifiChangeStateMachine(Looper looper) {
+            super("SignificantChangeStateMachine", looper);
+
+            mClients.put(null, mClientInfo);
+
+            addState(mDefaultState);
+            addState(mStationaryState, mDefaultState);
+            addState(mMovingState, mDefaultState);
+
+            setInitialState(mDefaultState);
+        }
+
+        public void enable() {
+            if (mAlarmManager == null) {
+                mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+            }
+
+            if (mTimeoutIntent == null) {
+                Intent intent = new Intent(ACTION_TIMEOUT, null);
+                mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+
+                mContext.registerReceiver(
+                        new BroadcastReceiver() {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                sendMessage(WIFI_CHANGE_CMD_CHANGE_TIMEOUT);
+                            }
+                        }, new IntentFilter(ACTION_TIMEOUT));
+            }
+
+            sendMessage(WIFI_CHANGE_CMD_ENABLE);
+        }
+
+        public void disable() {
+            sendMessage(WIFI_CHANGE_CMD_DISABLE);
+        }
+
+        class DefaultState extends State {
+            @Override
+            public void enter() {
+                if (DBG) Log.d(TAG, "Entering IdleState");
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                if (DBG) Log.d(TAG, "DefaultState state got " + msg);
+                switch (msg.what) {
+                    case WIFI_CHANGE_CMD_ENABLE :
+                        transitionTo(mMovingState);
+                        break;
+                    case WIFI_CHANGE_CMD_DISABLE:
+                        // nothing to do
+                        break;
+                    case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS:
+                        // nothing to do
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+        }
+
+        class StationaryState extends State {
+            @Override
+            public void enter() {
+                if (DBG) Log.d(TAG, "Entering StationaryState");
+                reportWifiStabilized(mCurrentHotspots);
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                if (DBG) Log.d(TAG, "Stationary state got " + msg);
+                switch (msg.what) {
+                    case WIFI_CHANGE_CMD_ENABLE :
+                        // do nothing
+                        break;
+                    case WIFI_CHANGE_CMD_CHANGE_DETECTED:
+                        if (DBG) Log.d(TAG, "Got wifi change detected");
+                        reportWifiChanged((ScanResult[])msg.obj);
+                        transitionTo(mMovingState);
+                        break;
+                    case WIFI_CHANGE_CMD_DISABLE:
+                        mCurrentHotspots = null;
+                        removeScanRequest();
+                        untrackSignificantWifiChange();
+                        transitionTo(mDefaultState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class MovingState extends State {
+            boolean wifiChangeDetected = false;
+
+            @Override
+            public void enter() {
+                if (DBG) Log.d(TAG, "Entering MovingState");
+                WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
+                settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+                /* TODO: Currently no driver allows scanning with band; hence this workaround */
+                settings.channels = getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+                settings.periodInMs = MOVING_SCAN_PERIOD_MS;
+                settings.reportEvents = 1;
+                addScanRequest(settings);
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                if (DBG) Log.d(TAG, "MovingState state got " + msg);
+                switch (msg.what) {
+                    case WIFI_CHANGE_CMD_ENABLE :
+                        // do nothing
+                        break;
+                    case WIFI_CHANGE_CMD_DISABLE:
+                        mCurrentHotspots = null;
+                        removeScanRequest();
+                        untrackSignificantWifiChange();
+                        transitionTo(mDefaultState);
+                        break;
+                    case WIFI_CHANGE_CMD_NEW_SCAN_RESULTS:
+                        if (DBG) Log.d(TAG, "Got scan results");
+                        removeScanRequest();
+                        reconfigureScan((ScanResult[])msg.obj, STATIONARY_SCAN_PERIOD_MS);
+                        wifiChangeDetected = false;
+                        mAlarmManager.setExact(AlarmManager.RTC_WAKEUP,
+                                System.currentTimeMillis() + MOVING_STATE_TIMEOUT_MS,
+                                mTimeoutIntent);
+                        break;
+                    case WIFI_CHANGE_CMD_CHANGE_DETECTED:
+                        mAlarmManager.cancel(mTimeoutIntent);
+                        reportWifiChanged((ScanResult[])msg.obj);
+                        wifiChangeDetected = true;
+                        break;
+                    case WIFI_CHANGE_CMD_CHANGE_TIMEOUT:
+                        if (DBG) Log.d(TAG, "Got timeout event");
+                        if (wifiChangeDetected == false) {
+                            transitionTo(mStationaryState);
+                        }
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                mAlarmManager.cancel(mTimeoutIntent);
+            }
+        }
+
+        void reconfigureScan(ScanResult[] results, int period) {
+            // find brightest APs and set them as sentinels
+
+            // remove duplicate BSSIDs
+            HashMap<String, ScanResult> bssidToScanResult = new HashMap<String, ScanResult>();
+            for (ScanResult result : results) {
+                ScanResult saved = bssidToScanResult.get(result.BSSID);
+                if (saved == null) {
+                    bssidToScanResult.put(result.BSSID, result);
+                } else if (saved.level > result.level) {
+                    bssidToScanResult.put(result.BSSID, result);
+                }
+            }
+
+            // find brightest BSSIDs
+            ScanResult brightest[] = new ScanResult[MAX_APS_TO_TRACK];
+            Collection<ScanResult> results2 = bssidToScanResult.values();
+            for (ScanResult result : results2) {
+                for (int j = 0; j < brightest.length; j++) {
+                    if (brightest[j] == null
+                            || (brightest[j].level < result.level)) {
+                        for (int k = brightest.length; k > (j + 1); k--) {
+                            brightest[k - 1] = brightest[k - 2];
+                        }
+                        brightest[j] = result;
+                        break;
+                    }
+                }
+            }
+
+            // Get channels to scan for
+            ArrayList<Integer> channels = new ArrayList<Integer>();
+            for (int i = 0; i < brightest.length; i++) {
+                boolean found = false;
+                for (int j = i + 1; j < brightest.length; j++) {
+                    if (brightest[j].frequency == brightest[i].frequency) {
+                        found = true;
+                    }
+                }
+                if (!found) {
+                    channels.add(brightest[i].frequency);
+                }
+            }
+
+            if (DBG) Log.d(TAG, "Found " + channels.size() + " channels");
+
+            // set scanning schedule
+            WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
+            settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+            settings.channels = new WifiScanner.ChannelSpec[channels.size()];
+            for (int i = 0; i < channels.size(); i++) {
+                settings.channels[i] = new WifiScanner.ChannelSpec(channels.get(i));
+            }
+
+            settings.periodInMs = period;
+            addScanRequest(settings);
+
+            WifiScanner.WifiChangeSettings settings2 = new WifiScanner.WifiChangeSettings();
+            settings2.rssiSampleSize = 3;
+            settings2.lostApSampleSize = 3;
+            settings2.unchangedSampleSize = 3;
+            settings2.minApsBreachingThreshold = 3;
+            settings2.hotspotInfos = new WifiScanner.HotspotInfo[brightest.length];
+
+            for (int i = 0; i < brightest.length; i++) {
+                WifiScanner.HotspotInfo hotspotInfo = new WifiScanner.HotspotInfo();
+                hotspotInfo.bssid = brightest[i].BSSID;
+                int threshold = (100 + brightest[i].level) / 32 + 2;
+                hotspotInfo.low = brightest[i].level - threshold;
+                hotspotInfo.high = brightest[i].level + threshold;
+                settings2.hotspotInfos[i] = hotspotInfo;
+
+                if (DBG) Log.d(TAG, "Setting bssid=" + hotspotInfo.bssid + ", " +
+                        "low=" + hotspotInfo.low + ", high=" + hotspotInfo.high);
+            }
+
+            trackSignificantWifiChange(settings2);
+            mCurrentHotspots = brightest;
+        }
+
+        class ClientInfoLocal extends ClientInfo {
+            ClientInfoLocal() {
+                super(null, null);
+            }
+            @Override
+            void deliverScanResults(int handler, ScanResult results[]) {
+                if (DBG) Log.d(TAG, "Delivering messages directly");
+                sendMessage(WIFI_CHANGE_CMD_NEW_SCAN_RESULTS, 0, 0, results);
+            }
+        }
+
+        @Override
+        public void onChangesFound(ScanResult results[]) {
+            sendMessage(WIFI_CHANGE_CMD_CHANGE_DETECTED, 0, 0, results);
+        }
+
+        ClientInfo mClientInfo = new ClientInfoLocal();
+        private static final int SCAN_COMMAND_ID = 1;
+
+        void addScanRequest(WifiScanner.ScanSettings settings) {
+            if (DBG) Log.d(TAG, "Starting scans");
+            Message msg = Message.obtain();
+            msg.what = WifiScanner.CMD_START_BACKGROUND_SCAN;
+            msg.arg2 = SCAN_COMMAND_ID;
+            msg.obj = settings;
+            mClientHandler.sendMessage(msg);
+        }
+
+        void removeScanRequest() {
+            if (DBG) Log.d(TAG, "Stopping scans");
+            Message msg = Message.obtain();
+            msg.what = WifiScanner.CMD_STOP_BACKGROUND_SCAN;
+            msg.arg2 = SCAN_COMMAND_ID;
+            mClientHandler.sendMessage(msg);
+        }
+
+        void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) {
+            mWifiNative.trackSignificantWifiChange(settings, this);
+        }
+
+        void untrackSignificantWifiChange() {
+            mWifiNative.untrackSignificantWifiChange();
+        }
+
+    }
+
+    private static WifiScanner.ChannelSpec[] getChannelsForBand(int band) {
+
+        /* TODO: We need to query this from the chip */
+
+        if (band == WifiScanner.WIFI_BAND_24_GHZ) {
+            return new WifiScanner.ChannelSpec[] {
+                    new WifiScanner.ChannelSpec(2412),
+                    new WifiScanner.ChannelSpec(2437),
+                    new WifiScanner.ChannelSpec(2462)
+                    };
+        } else if (band == WifiScanner.WIFI_BAND_5_GHZ
+                || band == WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS) {
+            return new WifiScanner.ChannelSpec[] {
+                    new WifiScanner.ChannelSpec(5180),
+                    new WifiScanner.ChannelSpec(5200),
+                    new WifiScanner.ChannelSpec(5220),
+                    new WifiScanner.ChannelSpec(5745),
+                    new WifiScanner.ChannelSpec(5765),
+                    new WifiScanner.ChannelSpec(5785),
+                    new WifiScanner.ChannelSpec(5805),
+                    new WifiScanner.ChannelSpec(5825)
+                    };
+        } else if (band == WifiScanner.WIFI_BAND_BOTH
+                || band == WifiScanner.WIFI_BAND_BOTH_WITH_DFS) {
+            return new WifiScanner.ChannelSpec[] {
+                    new WifiScanner.ChannelSpec(2412),
+                    new WifiScanner.ChannelSpec(2437),
+                    new WifiScanner.ChannelSpec(2462),
+                    new WifiScanner.ChannelSpec(5180),
+                    new WifiScanner.ChannelSpec(5200),
+                    new WifiScanner.ChannelSpec(5220),
+                    new WifiScanner.ChannelSpec(5745),
+                    new WifiScanner.ChannelSpec(5765),
+                    new WifiScanner.ChannelSpec(5785),
+                    new WifiScanner.ChannelSpec(5805),
+                    new WifiScanner.ChannelSpec(5825)
+                    };
+        } else {
+            return null;
+        }
+    }
+
+    private static int getBandFromChannels(WifiScanner.ChannelSpec[] channels) {
+        int band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+        for (WifiScanner.ChannelSpec channel : channels) {
+            if (2400 <= channel.frequency && channel.frequency < 2500) {
+                band |= WifiScanner.WIFI_BAND_24_GHZ;
+            } else if (5100 <= channel.frequency && channel.frequency < 6000) {
+                band |= WifiScanner.WIFI_BAND_5_GHZ;
+            } else {
+                /* TODO: Add DFS Range */
+            }
+        }
+        return band;
+    }
+    private static int getBandFromChannels(WifiNative.ChannelSettings[] channels) {
+        int band = WifiScanner.WIFI_BAND_UNSPECIFIED;
+        for (WifiNative.ChannelSettings channel : channels) {
+            if (2400 <= channel.frequency && channel.frequency < 2500) {
+                band |= WifiScanner.WIFI_BAND_24_GHZ;
+            } else if (5100 <= channel.frequency && channel.frequency < 6000) {
+                band |= WifiScanner.WIFI_BAND_5_GHZ;
+            } else {
+                /* TODO: Add DFS Range */
+            }
+        }
+        return band;
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiService.java b/service/java/com/android/server/wifi/WifiService.java
index 8caa3eb..70e2c1d 100644
--- a/service/java/com/android/server/wifi/WifiService.java
+++ b/service/java/com/android/server/wifi/WifiService.java
@@ -17,9 +17,13 @@
 package com.android.server.wifi;
 
 import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiScanner;
 import android.util.Log;
 import com.android.server.SystemService;
 
+import java.util.List;
+
 public final class WifiService extends SystemService {
 
     private static final String TAG = "WifiService";
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
index e635d62..caae355 100644
--- a/service/jni/com_android_server_wifi_WifiNative.cpp
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -22,6 +22,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
 #include <utils/String16.h>
+#include <ctype.h>
 
 #include "wifi.h"
 #include "wifi_hal.h"
@@ -165,27 +166,6 @@
     return (wifi_interface_handle) getLongArrayField(env, obj, WifiIfaceHandleVarName, index);
 }
 
-static void saveEventMethodIds(JNIEnv *env, jobject obj) {
-
-    jclass cls = (env)->GetObjectClass(obj);
-    if (cls == NULL) {
-        ALOGE("Error in accessing class");
-        return;
-    } else {
-        ALOGD("cls = %p", cls);
-    }
-
-    jmethodID method = env->GetMethodID(cls, "onScanResults",
-            "(I[Lcom/android/server/wifi/WifiNative$ScanResult;)V");
-
-    if (method == NULL) {
-        ALOGE("Error in getting method ID");
-        return;
-    } else {
-        ALOGD("method = %p", method);
-    }
-}
-
 static jboolean android_net_wifi_startHal(JNIEnv* env, jobject obj) {
     ALOGD("In wifi start Hal");
     wifi_handle halHandle = getWifiHandle(env, obj);
@@ -279,9 +259,209 @@
 
     ALOGD("onScanResultsAvailable called, vm = %p, obj = %p, env = %p", mVM, mObj, env);
 
-    /*
+    reportEvent(env, mObj, "onScanResultsAvailable", "(I)V", id);
+}
 
-    jclass clsScanResult = (env)->FindClass("com/android/server/wifi/WifiNative$ScanResult");
+static jboolean android_net_wifi_startScan(
+        JNIEnv *env, jobject obj, jint iface, jint id, jobject settings) {
+
+    wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
+    ALOGD("starting scan on interface[%d] = %p", iface, handle);
+
+    wifi_scan_cmd_params params;
+    memset(&params, 0, sizeof(params));
+    
+    params.base_period = getIntField(env, settings, "base_period_ms");
+    params.max_ap_per_scan = getIntField(env, settings, "max_ap_per_scan");
+    params.report_threshold = getIntField(env, settings, "report_threshold");
+    
+    ALOGD("Initialized common fields %d, %d, %d", params.base_period,
+            params.max_ap_per_scan, params.report_threshold);
+
+    const char *bucket_array_type = "[Lcom/android/server/wifi/WifiNative$BucketSettings;";
+    const char *channel_array_type = "[Lcom/android/server/wifi/WifiNative$ChannelSettings;";
+    
+    jobjectArray buckets = (jobjectArray)getObjectField(env, settings, "buckets", bucket_array_type);
+    params.num_buckets = getIntField(env, settings, "num_buckets");
+    
+    ALOGD("Initialized num_buckets to %d", params.num_buckets);
+
+    for (int i = 0; i < params.num_buckets; i++) {
+        jobject bucket = getObjectArrayField(env, settings, "buckets", bucket_array_type, i);
+        
+        params.buckets[i].bucket = getIntField(env, bucket, "bucket");
+        params.buckets[i].band = (wifi_band) getIntField(env, bucket, "band");
+        params.buckets[i].period = getIntField(env, bucket, "period_ms");
+        
+        ALOGD("Initialized common bucket fields %d:%d:%d", params.buckets[i].bucket,
+                params.buckets[i].band, params.buckets[i].period);
+
+        int report_events = getIntField(env, bucket, "report_events");
+        params.buckets[i].report_events = report_events;
+        
+        ALOGD("Initialized report events to %d", params.buckets[i].report_events);
+
+        jobjectArray channels = (jobjectArray)getObjectField(
+                env, bucket, "channels", channel_array_type);
+        
+        params.buckets[i].num_channels = getIntField(env, bucket, "num_channels");
+        ALOGD("Initialized num_channels to %d", params.buckets[i].num_channels);
+
+        for (int j = 0; j < params.buckets[i].num_channels; j++) {
+            jobject channel = getObjectArrayField(env, bucket, "channels", channel_array_type, j);
+            
+            params.buckets[i].channels[j].channel = getIntField(env, channel, "frequency");
+            params.buckets[i].channels[j].dwellTimeMs = getIntField(env, channel, "dwell_time_ms");
+            
+            bool passive = getBoolField(env, channel, "passive");
+            params.buckets[i].channels[j].passive = (passive ? 1 : 0);
+
+            ALOGD("Initialized channel %d", params.buckets[i].channels[j].channel);
+        }
+    }
+
+    ALOGD("Initialized all fields");
+
+    wifi_scan_result_handler handler;
+    memset(&handler, 0, sizeof(handler));
+    handler.on_scan_results_available = &onScanResultsAvailable;
+
+    return wifi_start_gscan(id, handle, params, handler) == WIFI_SUCCESS;
+}
+
+static jboolean android_net_wifi_stopScan(JNIEnv *env, jobject obj, jint iface, jint id) {
+    wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
+    ALOGD("stopping scan on interface[%d] = %p", iface, handle);
+
+    return wifi_stop_gscan(id, handle)  == WIFI_SUCCESS;
+}
+
+static jobject android_net_wifi_getScanResults(
+        JNIEnv *env, jobject obj, jint iface, jboolean flush)  {
+    
+    wifi_scan_result results[256];
+    int num_results = 256;
+    
+    wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
+    ALOGD("getting scan results on interface[%d] = %p", iface, handle);
+    
+    int result = wifi_get_cached_gscan_results(handle, 1, results, &num_results);
+    if (result == WIFI_SUCCESS) {
+        jclass clsScanResult = (env)->FindClass("android/net/wifi/ScanResult");
+        if (clsScanResult == NULL) {
+            ALOGE("Error in accessing class");
+            return NULL;
+        }
+
+        jobjectArray scanResults = env->NewObjectArray(num_results, clsScanResult, NULL);
+        if (scanResults == NULL) {
+            ALOGE("Error in allocating array");
+            return NULL;
+        }
+
+        for (int i = 0; i < num_results; i++) {
+
+            jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
+            if (scanResult == NULL) {
+                ALOGE("Error in creating scan result");
+                return NULL;
+            }
+
+            setStringField(env, scanResult, "SSID", results[i].ssid);
+
+            char bssid[32];
+            sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", results[i].bssid[0],
+                    results[i].bssid[1], results[i].bssid[2], results[i].bssid[3],
+                    results[i].bssid[4], results[i].bssid[5]);
+
+            setStringField(env, scanResult, "BSSID", bssid);
+
+            setIntField(env, scanResult, "level", results[i].rssi);
+            setIntField(env, scanResult, "frequency", results[i].channel);
+            setLongField(env, scanResult, "timestamp", results[i].ts);
+
+            env->SetObjectArrayElement(scanResults, i, scanResult);
+            env->DeleteLocalRef(scanResult);
+        }
+
+        return scanResults;
+    } else {
+        return NULL;
+    }
+}
+
+
+static jboolean android_net_wifi_getScanCapabilities(
+        JNIEnv *env, jobject obj, jint iface, jobject capabilities) {
+
+    wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
+    ALOGD("getting scan capabilities on interface[%d] = %p", iface, handle);
+
+    wifi_gscan_capabilities c;
+    memset(&c, 0, sizeof(c));
+    int result = wifi_get_gscan_capabilities(handle, &c);
+    if (result != WIFI_SUCCESS) {
+        ALOGD("failed to get capabilities : %d", result);
+        return JNI_FALSE;
+    }
+
+    setIntField(env, capabilities, "max_scan_cache_size", c.max_scan_cache_size);
+    setIntField(env, capabilities, "max_scan_buckets", c.max_scan_buckets);
+    setIntField(env, capabilities, "max_ap_cache_per_scan", c.max_ap_cache_per_scan);
+    setIntField(env, capabilities, "max_rssi_sample_size", c.max_rssi_sample_size);
+    setIntField(env, capabilities, "max_scan_reporting_threshold", c.max_scan_reporting_threshold);
+    setIntField(env, capabilities, "max_hotlist_aps", c.max_hotlist_aps);
+    setIntField(env, capabilities, "max_significant_wifi_change_aps",
+                c.max_significant_wifi_change_aps);
+
+    return JNI_TRUE;
+}
+
+
+static byte parseHexChar(char ch) {
+    if (isdigit(ch))
+        return ch - '0';
+    else if ('A' <= ch && ch <= 'F')
+        return ch - 'A' + 10;
+    else if ('a' <= ch && ch <= 'f')
+        return ch - 'a' + 10;
+    else {
+        ALOGE("invalid character in bssid %c", ch);
+        return 0;
+    }
+}
+
+static byte parseHexByte(const char * &str) {
+    byte b = parseHexChar(str[0]);
+    if (str[1] == ':' || str[1] == '\0') {
+        str += 2;
+        return b;
+    } else {
+        b = b << 4 | parseHexChar(str[1]);
+        str += 3;
+        return b;
+    }
+}
+
+static void parseMacAddress(const char *str, mac_addr addr) {
+    addr[0] = parseHexByte(str);
+    addr[1] = parseHexByte(str);
+    addr[2] = parseHexByte(str);
+    addr[3] = parseHexByte(str);
+    addr[4] = parseHexByte(str);
+    addr[5] = parseHexByte(str);
+}
+
+static void onHotlistApFound(wifi_request_id id,
+        unsigned num_results, wifi_scan_result *results) {
+
+    JNIEnv *env = NULL;
+    mVM->AttachCurrentThread(&env, NULL);
+
+    ALOGD("onHotlistApFound called, vm = %p, obj = %p, env = %p, num_results = %d",
+            mVM, mObj, env, num_results);
+
+    jclass clsScanResult = (env)->FindClass("android/net/wifi/ScanResult");
     if (clsScanResult == NULL) {
         ALOGE("Error in accessing class");
         return;
@@ -295,7 +475,7 @@
 
     for (unsigned i = 0; i < num_results; i++) {
 
-        jobject scanResult = createObject(env, "com/android/server/wifi/WifiNative$ScanResult");
+        jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
         if (scanResult == NULL) {
             ALOGE("Error in creating scan result");
             return;
@@ -304,45 +484,209 @@
         setStringField(env, scanResult, "SSID", results[i].ssid);
 
         char bssid[32];
-        sprintf(bssid, "%0x:%0x:%0x:%0x:%0x:%0x", results[i].bssid[0], results[i].bssid[1],
+        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", results[i].bssid[0], results[i].bssid[1],
             results[i].bssid[2], results[i].bssid[3], results[i].bssid[4], results[i].bssid[5]);
 
         setStringField(env, scanResult, "BSSID", bssid);
 
         setIntField(env, scanResult, "level", results[i].rssi);
-        setLongField(env, scanResult, "timestamp", results[i].ts);
         setIntField(env, scanResult, "frequency", results[i].channel);
+        setLongField(env, scanResult, "timestamp", results[i].ts);
+
+        env->SetObjectArrayElement(scanResults, i, scanResult);
+
+        ALOGD("Found AP %32s %s", results[i].ssid, bssid);
+    }
+
+    reportEvent(env, mObj, "onHotlistApFound", "(I[Landroid/net/wifi/ScanResult;)V",
+        id, scanResults);
+}
+
+static jboolean android_net_wifi_setHotlist(
+        JNIEnv *env, jobject obj, jint iface, jint id, jobject ap)  {
+
+    wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
+    ALOGD("setting hotlist on interface[%d] = %p", iface, handle);
+
+    wifi_bssid_hotlist_params params;
+    memset(&params, 0, sizeof(params));
+
+    jobjectArray array = (jobjectArray) getObjectField(env, ap,
+            "hotspotInfos", "[Landroid/net/wifi/WifiScanner$HotspotInfo;");
+    params.num = env->GetArrayLength(array);
+
+    if (params.num == 0) {
+        ALOGE("Error in accesing array");
+        return false;
+    }
+
+    for (int i = 0; i < params.num; i++) {
+        jobject objAp = env->GetObjectArrayElement(array, i);
+
+        jstring macAddrString = (jstring) getObjectField(
+                env, objAp, "bssid", "Ljava/lang/String;");
+        if (macAddrString == NULL) {
+            ALOGE("Error getting bssid field");
+            return false;
+        }
+
+        const char *bssid = env->GetStringUTFChars(macAddrString, NULL);
+        if (bssid == NULL) {
+            ALOGE("Error getting bssid");
+            return false;
+        }
+        parseMacAddress(bssid, params.bssids[i].bssid);
+
+        mac_addr addr;
+        memcpy(addr, params.bssids[i].bssid, sizeof(mac_addr));
+
+        char bssidOut[32];
+        sprintf(bssidOut, "%0x:%0x:%0x:%0x:%0x:%0x", addr[0], addr[1],
+            addr[2], addr[3], addr[4], addr[5]);
+
+        ALOGD("Added bssid %s", bssidOut);
+
+        params.bssids[i].low = getIntField(env, objAp, "low");
+        params.bssids[i].high = getIntField(env, objAp, "high");
+    }
+
+    wifi_hotlist_ap_found_handler handler;
+    memset(&handler, 0, sizeof(handler));
+
+    handler.on_hotlist_ap_found = &onHotlistApFound;
+    return wifi_set_bssid_hotlist(id, handle, params, handler) == WIFI_SUCCESS;
+}
+
+static jboolean android_net_wifi_resetHotlist(
+        JNIEnv *env, jobject obj, jint iface, jint id)  {
+
+    wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
+    ALOGD("resetting hotlist on interface[%d] = %p", iface, handle);
+
+    return wifi_reset_bssid_hotlist(id, handle) == WIFI_SUCCESS;
+}
+
+void onSignificantWifiChange(wifi_request_id id, unsigned num_results, wifi_scan_result *results) {
+    JNIEnv *env = NULL;
+    mVM->AttachCurrentThread(&env, NULL);
+
+    ALOGD("onSignificantWifiChange called, vm = %p, obj = %p, env = %p", mVM, mObj, env);
+
+    jclass clsScanResult = (env)->FindClass("android/net/wifi/ScanResult");
+    if (clsScanResult == NULL) {
+        ALOGE("Error in accessing class");
+        return;
+    }
+
+    jobjectArray scanResults = env->NewObjectArray(num_results, clsScanResult, NULL);
+    if (scanResults == NULL) {
+        ALOGE("Error in allocating array");
+        return;
+    }
+
+    for (unsigned i = 0; i < num_results; i++) {
+
+        jobject scanResult = createObject(env, "android/net/wifi/ScanResult");
+        if (scanResult == NULL) {
+            ALOGE("Error in creating scan result");
+            return;
+        }
+
+        setStringField(env, scanResult, "SSID", results[i].ssid);
+
+        char bssid[32];
+        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", results[i].bssid[0], results[i].bssid[1],
+            results[i].bssid[2], results[i].bssid[3], results[i].bssid[4], results[i].bssid[5]);
+
+        setStringField(env, scanResult, "BSSID", bssid);
+
+        setIntField(env, scanResult, "level", results[i].rssi);
+        setIntField(env, scanResult, "frequency", results[i].channel);
+        setLongField(env, scanResult, "timestamp", results[i].ts);
 
         env->SetObjectArrayElement(scanResults, i, scanResult);
     }
 
-    reportEvent(env, mObj, "onScanResults",
-            "(I[Lcom/android/server/wifi/WifiNative$ScanResult;)V", id, scanResults);
-     */
+    reportEvent(env, mObj, "onSignificantWifiChange", "(I[Landroid/net/wifi/ScanResult;)V",
+        id, scanResults);
+
 }
 
-static jboolean android_net_wifi_startScan(JNIEnv *env, jobject obj, jint iface, jint id) {
+static jboolean android_net_wifi_trackSignificantWifiChange(
+        JNIEnv *env, jobject obj, jint iface, jint id, jobject settings)  {
 
     wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
-    ALOGD("starting scan on interface[%d] = %p", iface, handle);
+    ALOGD("tracking significant wifi change on interface[%d] = %p", iface, handle);
 
-    wifi_scan_cmd_params params;
+    wifi_significant_change_params params;
     memset(&params, 0, sizeof(params));
 
-    wifi_scan_result_handler handler;
+    params.rssi_sample_size = getIntField(env, settings, "rssiSampleSize");
+    params.lost_ap_sample_size = getIntField(env, settings, "lostApSampleSize");
+    params.min_breaching = getIntField(env, settings, "minApsBreachingThreshold");
+
+    const char *hotspot_info_array_type = "[Landroid/net/wifi/WifiScanner$HotspotInfo;";
+    jobjectArray hotspots = (jobjectArray)getObjectField(
+                env, settings, "hotspotInfos", hotspot_info_array_type);
+    params.num = env->GetArrayLength(hotspots);
+
+    if (params.num == 0) {
+        ALOGE("Error in accesing array");
+        return false;
+    }
+
+    ALOGD("Initialized common fields %d, %d, %d, %d", params.rssi_sample_size,
+            params.lost_ap_sample_size, params.min_breaching, params.num);
+
+    for (int i = 0; i < params.num; i++) {
+        jobject objAp = env->GetObjectArrayElement(hotspots, i);
+
+        jstring macAddrString = (jstring) getObjectField(
+                env, objAp, "bssid", "Ljava/lang/String;");
+        if (macAddrString == NULL) {
+            ALOGE("Error getting bssid field");
+            return false;
+        }
+
+        const char *bssid = env->GetStringUTFChars(macAddrString, NULL);
+        if (bssid == NULL) {
+            ALOGE("Error getting bssid");
+            return false;
+        }
+
+        mac_addr addr;
+        parseMacAddress(bssid, addr);
+        memcpy(params.bssids[i].bssid, addr, sizeof(mac_addr));
+
+        char bssidOut[32];
+        sprintf(bssidOut, "%0x:%0x:%0x:%0x:%0x:%0x", addr[0], addr[1],
+            addr[2], addr[3], addr[4], addr[5]);
+
+        params.bssids[i].low = getIntField(env, objAp, "low");
+        params.bssids[i].high = getIntField(env, objAp, "high");
+
+        ALOGD("Added bssid %s, [%04d, %04d]", bssidOut, params.bssids[i].low, params.bssids[i].high);
+    }
+
+    ALOGD("Added %d bssids", params.num);
+
+    wifi_significant_change_handler handler;
     memset(&handler, 0, sizeof(handler));
-    handler.on_scan_results_available = &onScanResultsAvailable;
 
-    return wifi_start_gscan(id, handle, params, handler) == WIFI_SUCCESS;
+    handler.on_significant_change = &onSignificantWifiChange;
+    return wifi_set_significant_change_handler(id, handle, params, handler) == WIFI_SUCCESS;
 }
 
-static jboolean android_net_wifi_stopScan(JNIEnv *env, jobject obj, jint iface, jint id) {
+static jboolean android_net_wifi_untrackSignificantWifiChange(
+        JNIEnv *env, jobject obj, jint iface, jint id)  {
+
     wifi_interface_handle handle = getIfaceHandle(env, obj, iface);
-    ALOGD("stopping scan on interface[%d] = %p", iface, handle);
+    ALOGD("resetting significant wifi change on interface[%d] = %p", iface, handle);
 
-    return wifi_stop_gscan(id, handle);
+    return wifi_reset_significant_change_handler(id, handle) == WIFI_SUCCESS;
 }
 
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -369,8 +713,23 @@
     { "waitForHalEventNative", "()V", (void*) android_net_wifi_waitForHalEvents },
     { "getInterfacesNative", "()I", (void*) android_net_wifi_getInterfaces},
     { "getInterfaceNameNative", "(I)Ljava/lang/String;", (void*) android_net_wifi_getInterfaceName},
-    { "startScanNative", "(II)Z", (void*) android_net_wifi_startScan},
-    { "stopScanNative", "(II)Z", (void*) android_net_wifi_stopScan}
+    { "getScanCapabilitiesNative", "(ILcom/android/server/wifi/WifiNative$ScanCapabilities;)Z",
+            (void *) android_net_wifi_getScanCapabilities},
+    { "startScanNative", "(IILcom/android/server/wifi/WifiNative$ScanSettings;)Z",
+            (void*) android_net_wifi_startScan},
+    { "stopScanNative", "(II)Z", (void*) android_net_wifi_stopScan},
+    { "getScanResultsNative", "(IZ)[Landroid/net/wifi/ScanResult;",
+            (void *) android_net_wifi_getScanResults},
+
+    { "setHotlistNative", "(IILandroid/net/wifi/WifiScanner$HotlistSettings;)Z",
+            (void*) android_net_wifi_setHotlist},
+    { "resetHotlistNative", "(II)Z", (void*) android_net_wifi_resetHotlist},
+
+
+    { "trackSignificantWifiChangeNative", "(IILandroid/net/wifi/WifiScanner$WifiChangeSettings;)Z",
+            (void*) android_net_wifi_trackSignificantWifiChange},
+    { "untrackSignificantWifiChangeNative", "(II)Z",
+            (void*) android_net_wifi_untrackSignificantWifiChange}
 };
 
 int register_android_net_wifi_WifiNative(JNIEnv* env) {
diff --git a/service/jni/jni_helper.cpp b/service/jni/jni_helper.cpp
index 54e9023..81bf0e6 100644
--- a/service/jni/jni_helper.cpp
+++ b/service/jni/jni_helper.cpp
@@ -47,6 +47,34 @@
     (env)->ThrowNew(exClass, message);
 }
 
+jboolean getBoolField(JNIEnv *env, jobject obj, const char *name)
+{
+    jclass cls = (env)->GetObjectClass(obj);
+    jfieldID field = (env)->GetFieldID(cls, name, "Z");
+    if (field == 0) {
+        THROW(env, "Error in accessing field");
+        return 0;
+    }
+
+    jboolean value = (env)->GetBooleanField(obj, field);
+    env->DeleteLocalRef(cls);
+    return value;
+}
+
+jint getIntField(JNIEnv *env, jobject obj, const char *name)
+{
+    jclass cls = (env)->GetObjectClass(obj);
+    jfieldID field = (env)->GetFieldID(cls, name, "I");
+    if (field == 0) {
+        THROW(env, "Error in accessing field");
+        return 0;
+    }
+
+    jint value = (env)->GetIntField(obj, field);
+    env->DeleteLocalRef(cls);
+    return value;
+}
+
 jlong getLongField(JNIEnv *env, jobject obj, const char *name)
 {
     jclass cls = (env)->GetObjectClass(obj);
@@ -57,6 +85,21 @@
     }
 
     jlong value = (env)->GetLongField(obj, field);
+    env->DeleteLocalRef(cls);
+    return value;
+}
+
+jobject getObjectField(JNIEnv *env, jobject obj, const char *name, const char *type)
+{
+    jclass cls = (env)->GetObjectClass(obj);
+    jfieldID field = (env)->GetFieldID(cls, name, type);
+    if (field == 0) {
+        THROW(env, "Error in accessing field");
+        return 0;
+    }
+
+    jobject value = (env)->GetObjectField(obj, field);
+    env->DeleteLocalRef(cls);
     return value;
 }
 
@@ -78,9 +121,34 @@
 
     jlong value = elem[index];
     (env)->ReleaseLongArrayElements(array, elem, 0);
+
+    env->DeleteLocalRef(array);
+    env->DeleteLocalRef(cls);
+
     return value;
 }
 
+jobject getObjectArrayField(JNIEnv *env, jobject obj, const char *name, const char *type, int index)
+{
+    jclass cls = (env)->GetObjectClass(obj);
+    jfieldID field = (env)->GetFieldID(cls, name, type);
+    if (field == 0) {
+        THROW(env, "Error in accessing field definition");
+        return 0;
+    }
+
+    jobjectArray array = (jobjectArray)(env)->GetObjectField(obj, field);
+    jobject elem = (env)->GetObjectArrayElement(array, index);
+    if (elem == NULL) {
+        THROW(env, "Error in accessing index element");
+        return 0;
+    }
+
+    env->DeleteLocalRef(array);
+    env->DeleteLocalRef(cls);
+    return elem;
+}
+
 void setIntField(JNIEnv *env, jobject obj, const char *name, jint value)
 {
     jclass cls = (env)->GetObjectClass(obj);
@@ -96,6 +164,7 @@
     }
 
     (env)->SetIntField(obj, field, value);
+    env->DeleteLocalRef(cls);
 }
 
 void setLongField(JNIEnv *env, jobject obj, const char *name, jlong value)
@@ -113,6 +182,7 @@
     }
 
     (env)->SetLongField(obj, field, value);
+    env->DeleteLocalRef(cls);
 }
 
 void setLongArrayField(JNIEnv *env, jobject obj, const char *name, jlongArray value)
@@ -135,6 +205,8 @@
 
     (env)->SetObjectField(obj, field, value);
     ALOGD("array field set");
+
+    env->DeleteLocalRef(cls);
 }
 
 void setLongArrayElement(JNIEnv *env, jobject obj, const char *name, int index, jlong value)
@@ -170,7 +242,9 @@
     }
 
     elem[index] = value;
-    (env)->ReleaseLongArrayElements(array, elem, 0);
+    env->ReleaseLongArrayElements(array, elem, 0);
+    env->DeleteLocalRef(array);
+    env->DeleteLocalRef(cls);
 }
 
 void setObjectField(JNIEnv *env, jobject obj, const char *name, const char *type, jobject value)
@@ -188,11 +262,11 @@
     }
 
     (env)->SetObjectField(obj, field, value);
+    env->DeleteLocalRef(cls);
 }
 
 void setStringField(JNIEnv *env, jobject obj, const char *name, const char *value)
 {
-
     jstring str = env->NewStringUTF(value);
 
     if (str == NULL) {
@@ -201,6 +275,7 @@
     }
 
     setObjectField(env, obj, name, "Ljava/lang/String;", str);
+    env->DeleteLocalRef(str);
 }
 
 void reportEvent(JNIEnv *env, jobject obj, const char *method, const char *signature, ...)
@@ -222,6 +297,8 @@
 
     env->CallVoidMethodV(obj, methodID, params);
     va_end(params);
+
+    env->DeleteLocalRef(cls);
 }
 
 jobject createObject(JNIEnv *env, const char *className)
@@ -243,6 +320,7 @@
         return NULL;
     }
 
+    env->DeleteLocalRef(cls);
     return obj;
 }
 
diff --git a/service/jni/jni_helper.h b/service/jni/jni_helper.h
index e61ee0a..c2678f6 100644
--- a/service/jni/jni_helper.h
+++ b/service/jni/jni_helper.h
@@ -4,8 +4,12 @@
 /* JNI Helpers for wifi_hal to WifiNative bridge implementation */
 
 void throwException( JNIEnv *env, const char *message, int line );
+jboolean  getBoolField(JNIEnv *env, jobject obj, const char *name);
+jint  getIntField(JNIEnv *env, jobject obj, const char *name);
 jlong getLongField(JNIEnv *env, jobject obj, const char *name);
+jobject getObjectField(JNIEnv *env, jobject obj, const char *name, const char *type);
 jlong getLongArrayField(JNIEnv *env, jobject obj, const char *name, int index);
+jobject getObjectArrayField(JNIEnv *env, jobject obj, const char *name, const char *type, int index);
 void setIntField(JNIEnv *env, jobject obj, const char *name, jint value);
 void setLongField(JNIEnv *env, jobject obj, const char *name, jlong value);
 void setLongArrayField(JNIEnv *env, jobject obj, const char *name, jlongArray value);