Improve WifiManager support in sl4a.

Add APIs to wifi manager facade.
Add ConnectivityManager support
Improve README

Change-Id: I8d888f54e5a3d5ba01ed7fb930d095db032c5dcf
diff --git a/Common/src/com/googlecode/android_scripting/facade/ConnectivityManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/ConnectivityManagerFacade.java
new file mode 100644
index 0000000..b453b7a
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/ConnectivityManagerFacade.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.googlecode.android_scripting.facade;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
+import com.googlecode.android_scripting.rpc.Rpc;
+
+/**
+ * Access ConnectivityManager functions.
+ */
+public class ConnectivityManagerFacade extends RpcReceiver {
+    private final Service mService;
+    private final ConnectivityManager mCon;
+
+    public ConnectivityManagerFacade(FacadeManager manager) {
+        super(manager);
+        mService = manager.getService();
+        mCon = (ConnectivityManager) mService.getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+    class ConnectivityReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                Log.d("Connectivity state changed.");
+            }
+        }
+    }
+
+    @Rpc(description = "Check whether the active network is connected to the Internet.")
+    public Boolean networkIsConnected() {
+        NetworkInfo current = mCon.getActiveNetworkInfo();
+        if (current == null) {
+            Log.d("No network is active at the moment.");
+            return false;
+        }
+        return current.isConnected();
+    }
+
+    @Override
+    public void shutdown() {
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java
index 7c52fb2..d1ef771 100644
--- a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java
@@ -5,10 +5,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.NetworkInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager.WifiLock;
 import android.os.Bundle;
 
@@ -21,6 +23,7 @@
 import com.googlecode.android_scripting.rpc.RpcParameter;
 
 import java.net.ConnectException;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -33,19 +36,33 @@
   private final static String mEventType = "WiFiManager";
   private final Service mService;
   private final WifiManager mWifi;
-  private final IntentFilter mIntentFilter;
-  private final WifiScanReceiver mReceiver;
+  private final EventFacade mEventFacade;
+
+  private final IntentFilter mScanFilter;
+  private final IntentFilter mStateChangeFilter;
+  private final WifiScanReceiver mScanFinishedReceiver;
   private final WifiActionListener mWifiActionListener;
+  private final WifiStateChangeReceiver mStateChangeReceiver;
+
   private WifiLock mLock;
+  private boolean mIsConnected;
 
   public WifiManagerFacade(FacadeManager manager) {
     super(manager);
     mService = manager.getService();
     mWifi = (WifiManager) mService.getSystemService(Context.WIFI_SERVICE);
     mLock = null;
-    mIntentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-    mReceiver = new WifiScanReceiver(manager.getReceiver(EventFacade.class));
-    mWifiActionListener = new WifiActionListener(manager.getReceiver(EventFacade.class));
+    mEventFacade = manager.getReceiver(EventFacade.class);
+
+    mScanFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+    mStateChangeFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+    mStateChangeFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+    mStateChangeFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+    mStateChangeFilter.setPriority(999);
+
+    mScanFinishedReceiver = new WifiScanReceiver(mEventFacade);
+    mWifiActionListener = new WifiActionListener(mEventFacade);
+    mStateChangeReceiver = new WifiStateChangeReceiver();
   }
 
   private void makeLock(int wifiMode) {
@@ -56,7 +73,7 @@
   }
 
   /**
-   * Handle Brodacst receiver for Scan Result
+   * Handle Broadcast receiver for Scan Result
    * @parm eventFacade
    *        Object of EventFacade
    */
@@ -71,11 +88,12 @@
 
     @Override
     public void onReceive(Context c, Intent intent) {
-      Log.d("WifiScanReceiver  "+ mEventType);
+      Log.d("Wifi connection scan finished, results available.");
       mResults.putLong("Timestamp", System.currentTimeMillis()/1000);
       mResults.putString("Type", "onWifiScanReceive");
-      mEventFacade.postEvent(mEventType, mResults.clone());
+      mEventFacade.postEvent("WifiScanFinished", mResults.clone());
       mResults.clear();
+      mService.unregisterReceiver(mScanFinishedReceiver);
     }
   }
 
@@ -87,6 +105,7 @@
       mEventFacade = eventFacade;
       mResults = new Bundle();
     }
+
     @Override
     public void onSuccess() {
       Log.d("WifiActionListener  "+ mEventType);
@@ -102,104 +121,92 @@
       mEventFacade.postEvent(mEventType, mResults.clone());
       mResults.clear();
     }
-
   }
 
-  @Rpc(description = "Returns the list of access points found during the most recent Wifi scan.")
-  public List<ScanResult> wifiGetScanResults() {
-    mService.unregisterReceiver(mReceiver);
-    return mWifi.getScanResults();
-  }
-
-  @Rpc(description = "Acquires a full Wifi lock.")
-  public void wifiLockAcquireFull() {
-    makeLock(WifiManager.WIFI_MODE_FULL);
-  }
-
-  @Rpc(description = "Acquires a scan only Wifi lock.")
-  public void wifiLockAcquireScanOnly() {
-    makeLock(WifiManager.WIFI_MODE_SCAN_ONLY);
-  }
-
-  @Rpc(description = "Releases a previously acquired Wifi lock.")
-  public void wifiLockRelease() {
-    if (mLock != null) {
-      mLock.release();
-      mLock = null;
+  public class WifiStateChangeReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            Log.d("Wifi network state changed.");
+            if (intent.hasExtra(WifiManager.EXTRA_WIFI_INFO)) {
+              WifiInfo wInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+              Bundle msg = new Bundle();
+              String ssid = wInfo.getSSID();
+              if (ssid.charAt(0)=='"' && ssid.charAt(ssid.length()-1)=='"') {
+                  msg.putString("ssid", ssid.substring(1, ssid.length()-1));
+              } else {
+                  msg.putString("ssid", ssid);
+              }
+              msg.putString("bssid", wInfo.getBSSID());
+              mEventFacade.postEvent("WifiNetworkConnected", msg);
+            }
+            NetworkInfo nInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+            WifiInfo wInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+            Log.d("NetworkInfo " + nInfo);
+            Log.d("WifiInfo " + wInfo);
+        } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
+            Log.d("Supplicant connection state changed.");
+            mIsConnected = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
+            Bundle msg = new Bundle();
+            msg.putBoolean("Connected", mIsConnected);
+            mEventFacade.postEvent("SupplicantConnectionChanged", msg);
+        }
     }
   }
 
-  @Rpc(description = "Starts a scan for Wifi access points.", returns = "True if the scan was initiated successfully.")
-  public Boolean wifiStartScan() {
-    mService.registerReceiver(mReceiver, mIntentFilter);
-    return mWifi.startScan();
+  private WifiConfiguration wifiConfigurationFromScanResult(ScanResult result) {
+      if (result == null) return null;
+      WifiConfiguration config = new WifiConfiguration();
+      config.SSID = "\"" + result.SSID + "\"";
+      if (result.capabilities.contains("WEP")) {
+          config.allowedKeyManagement.set(KeyMgmt.NONE);
+          config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); //?
+          config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+      }
+      if (result.capabilities.contains("PSK")) {
+          config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+      }
+      if (result.capabilities.contains("EAP")) {
+          //this is probably wrong, as we don't have a way to enter the enterprise config
+          config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
+          config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
+      }
+      if (result.capabilities.length() == 5 && result.capabilities.contains("ESS")) {
+          config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+      }
+      config.BSSID = result.BSSID;
+      config.scanResultCache = new HashMap<String, ScanResult>();
+      if (config.scanResultCache == null)
+          return null;
+      config.scanResultCache.put(result.BSSID, result);
+      return config;
+  }
+
+  private boolean matchScanResult(ScanResult result, String id) {
+      if (result.BSSID.equals(id) || result.SSID.equals(id)) {
+          return true;
+      }
+      return false;
+  }
+
+  @Rpc(description = "Add a network.")
+  public Integer wifiAddNetwork(@RpcParameter(name = "wifiId") String wifiId) {
+      ScanResult target = null;
+      for (ScanResult r : mWifi.getScanResults()) {
+          if (matchScanResult(r, wifiId)) {
+              target = r;
+              break;
+          }
+      }
+      return mWifi.addNetwork(wifiConfigurationFromScanResult(target));
   }
 
   @Rpc(description = "Checks Wifi state.", returns = "True if Wifi is enabled.")
-  public Boolean checkWifiState() {
+  public Boolean wifiCheckState() {
     return mWifi.getWifiState() == WifiManager.WIFI_STATE_ENABLED;
   }
 
-  @Rpc(description = "Toggle Wifi on and off.", returns = "True if Wifi is enabled.")
-  public Boolean toggleWifiState(@RpcParameter(name = "enabled") @RpcOptional Boolean enabled) {
-    if (enabled == null) {
-      enabled = !checkWifiState();
-    }
-    mWifi.setWifiEnabled(enabled);
-    return enabled;
-  }
-
-  @Rpc(description = "Disconnects from the currently active access point.", returns = "True if the operation succeeded.")
-  public Boolean wifiDisconnect() {
-    return mWifi.disconnect();
-  }
-
-  @Rpc(description = "Returns information about the currently active access point.")
-  public WifiInfo wifiGetConnectionInfo() {
-    return mWifi.getConnectionInfo();
-  }
-
-  @Rpc(description = "Reassociates with the currently active access point.", returns = "True if the operation succeeded.")
-  public Boolean wifiReassociate() {
-    return mWifi.reassociate();
-  }
-
-  @Rpc(description = "Reconnects to the currently active access point.", returns = "True if the operation succeeded.")
-  public Boolean wifiReconnect() {
-    return mWifi.reconnect();
-  }
-
-  /**
-   * Connects to a wifi network with priority
-   *
-   * @param wifiSSID
-   *          SSID of the wifi network
-   * @param wifiPassword
-   *          password for the wifi network
-   */
-  @Rpc(description = "Connects a wifi network as priority by pasing ssid")
-  public void wifiPriorityConnect(@RpcParameter(name = "wifiSSID") String wifiSSID,
-      @RpcParameter(name = "wifiPassword") String wifiPassword) {
-    WifiConfiguration wifiConfig = new WifiConfiguration();
-    wifiConfig.SSID = "\"" + wifiSSID + "\"";
-    if(wifiPassword == null)
-      wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-    else
-      wifiConfig.preSharedKey = "\"" + wifiPassword + "\"";
-    mWifi.connect(wifiConfig, mWifiActionListener);
-  }
-
-  /**
-   * Forget to a wifi network with priority
-   *
-   * @param networkID
-   *          Id of wifi network
-   */
-  @Rpc(description = "Forget a wifi network with priority")
-  public void wifiForgetNetwork(@RpcParameter(name = "wifiSSID") Integer newtorkId ) {
-    mWifi.forget(newtorkId, mWifiActionListener);
-  }
-
   /**
    * Connects to a WPA protected wifi network
    *
@@ -210,7 +217,8 @@
    * @return true on success
    * @throws ConnectException
    */
-  @Rpc(description = "Connects a wifi network by ssid", returns = "True if the operation succeeded.")
+  @Rpc(description = "Connects a wifi network by ssid",
+           returns = "True if the operation succeeded.")
   public Boolean wifiConnectWPA(@RpcParameter(name = "wifiSSID") String wifiSSID,
       @RpcParameter(name = "wifiPassword") String wifiPassword) throws ConnectException {
     WifiConfiguration wifiConfig = new WifiConfiguration();
@@ -244,8 +252,129 @@
     return status;
   }
 
+  @Rpc(description = "Disconnects from the currently active access point.",
+           returns = "True if the operation succeeded.")
+  public Boolean wifiDisconnect() {
+    return mWifi.disconnect();
+  }
+
+  @Rpc(description = "Enable a configured network. Initiate a connection if disableOthers is true",
+           returns = "True if the operation succeeded.")
+  public Boolean wifiEnableNetwork(@RpcParameter(name = "netId") Integer netId,
+                                   @RpcParameter(name = "disableOthers") Boolean disableOthers) {
+    return mWifi.enableNetwork(netId, disableOthers);
+  }
+  /**
+   * Forget a wifi network with priority
+   *
+   * @param networkID
+   *          Id of wifi network
+   */
+  @Rpc(description = "Forget a wifi network with priority")
+  public void wifiForgetNetwork(@RpcParameter(name = "wifiSSID") Integer newtorkId ) {
+    mWifi.forget(newtorkId, mWifiActionListener);
+  }
+
+  @Rpc(description = "Return a list of all the configured wifi networks.")
+  public List<WifiConfiguration> wifiGetConfiguredNetworks() {
+      return mWifi.getConfiguredNetworks();
+  }
+
+  @Rpc(description = "Returns information about the currently active access point.")
+  public WifiInfo wifiGetConnectionInfo() {
+    return mWifi.getConnectionInfo();
+  }
+
+  @Rpc(description = "Returns the list of access points found during the most recent Wifi scan.")
+  public List<ScanResult> wifiGetScanResults() {
+    return mWifi.getScanResults();
+  }
+
+  @Rpc(description = "Acquires a full Wifi lock.")
+  public void wifiLockAcquireFull() {
+    makeLock(WifiManager.WIFI_MODE_FULL);
+  }
+
+  @Rpc(description = "Acquires a scan only Wifi lock.")
+  public void wifiLockAcquireScanOnly() {
+    makeLock(WifiManager.WIFI_MODE_SCAN_ONLY);
+  }
+
+  @Rpc(description = "Releases a previously acquired Wifi lock.")
+  public void wifiLockRelease() {
+    if (mLock != null) {
+      mLock.release();
+      mLock = null;
+    }
+  }
+
+  /**
+   * Connects to a wifi network with priority
+   *
+   * @param wifiSSID
+   *          SSID of the wifi network
+   * @param wifiPassword
+   *          password for the wifi network
+   */
+  @Rpc(description = "Connects a wifi network as priority by pasing ssid")
+  public void wifiPriorityConnect(@RpcParameter(name = "wifiSSID") String wifiSSID,
+      @RpcParameter(name = "wifiPassword") String wifiPassword) {
+    WifiConfiguration wifiConfig = new WifiConfiguration();
+    wifiConfig.SSID = "\"" + wifiSSID + "\"";
+    if(wifiPassword == null)
+      wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+    else
+      wifiConfig.preSharedKey = "\"" + wifiPassword + "\"";
+    mWifi.connect(wifiConfig, mWifiActionListener);
+  }
+
+  @Rpc(description = "Reassociates with the currently active access point.",
+           returns = "True if the operation succeeded.")
+  public Boolean wifiReassociate() {
+    return mWifi.reassociate();
+  }
+
+  @Rpc(description = "Reconnects to the currently active access point.",
+           returns = "True if the operation succeeded.")
+  public Boolean wifiReconnect() {
+    return mWifi.reconnect();
+  }
+
+  @Rpc(description = "Remove a configured network.",
+           returns = "True if the operation succeeded.")
+   public Boolean wifiRemoveNetwork(@RpcParameter(name = "netId") Integer netId) {
+     return mWifi.removeNetwork(netId);
+   }
+
+  @Rpc(description = "Starts a scan for Wifi access points.",
+           returns = "True if the scan was initiated successfully.")
+  public Boolean wifiStartScan() {
+    mService.registerReceiver(mScanFinishedReceiver, mScanFilter);
+    return mWifi.startScan();
+  }
+
+  @Rpc(description = "Start receiving wifi state change related broadcasts.")
+  public void wifiStartTrackingStateChange() {
+      mService.registerReceiver(mStateChangeReceiver, mStateChangeFilter);
+  }
+
+  @Rpc(description = "Stop receiving wifi state change related broadcasts.")
+  public void wifiStopTrackingStateChange() {
+      mService.unregisterReceiver(mStateChangeReceiver);
+  }
+
+  @Rpc(description = "Toggle Wifi on and off.", returns = "True if Wifi is enabled.")
+  public Boolean wifiToggleState(@RpcParameter(name = "enabled") @RpcOptional Boolean enabled) {
+    if (enabled == null) {
+      enabled = !wifiCheckState();
+    }
+    mWifi.setWifiEnabled(enabled);
+    return enabled;
+  }
+
   @Override
   public void shutdown() {
     wifiLockRelease();
+    mService.unregisterReceiver(mStateChangeReceiver);
   }
 }
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
index 818c4b2..62421a0 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
@@ -41,6 +41,7 @@
 import android.location.Location;
 import android.net.wifi.RttManager.Capabilities;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.os.Bundle;
 import android.os.ParcelUuid;
@@ -171,6 +172,9 @@
         if (data instanceof Capabilities) {
             return buildRttCapabilities((Capabilities) data);
         }
+        if (data instanceof WifiConfiguration) {
+            return buildWifiConfiguration((WifiConfiguration) data);
+        }
         if (data instanceof byte[]) {
             return Base64Codec.encodeBase64((byte[]) data);
         }
@@ -432,6 +436,30 @@
         return cap;
     }
 
+    private static Object buildWifiConfiguration(WifiConfiguration data) throws JSONException {
+        JSONObject config = new JSONObject();
+        config.put("networkId", data.networkId);
+        // Trim the double quotes if exist
+        if (data.SSID.charAt(0)=='"' && data.SSID.charAt(data.SSID.length()-1)=='"') {
+            config.put("ssid", data.SSID.substring(1, data.SSID.length()-1));
+        } else {
+            config.put("ssid", data.SSID);
+        }
+        config.put("bssid", data.BSSID);
+        config.put("priority", data.priority);
+        config.put("hiddenSSID", data.hiddenSSID);
+        if (data.status==WifiConfiguration.Status.CURRENT) {
+            config.put("status", "CURRENT");
+        } else if (data.status==WifiConfiguration.Status.DISABLED) {
+            config.put("status", "DISABLED");
+        } else if (data.status==WifiConfiguration.Status.ENABLED) {
+            config.put("status", "ENABLED");
+        } else {
+            config.put("status", "UNKNOWN");
+        }
+        return config;
+    }
+
     private static JSONObject buildJsonCellLocation(CellLocation cellLocation)
         throws JSONException {
         JSONObject result = new JSONObject();
diff --git a/README b/README
index 41f9585..16f45fd 100644
--- a/README
+++ b/README
@@ -1,9 +1,10 @@
-This directory contains projects related to the Scripting Layer For Android.
-ScriptingLayerForAndroid/ contains the main SL4A project.
+This directory contains the source code and project files of Scripting Layer For Android.
+ScriptingLayerForAndroid/ is the main project (the one that builds the apk).
 
-To build, modify the BRANCH_ROOT and other paths if necessary in build_all.sh
-Plug in your device and run build_all.sh
+The code that call API components are in Common/src/com/googlecode/android_scripting/facade. Most likely you only need to add/edit files in this directory.
 
-To run, add android.py to your $PYTHONPATH
+To build, you can either build the entire android tree or use the included build_all.sh. The script will build sl4a.apk and install it on your device.
+
+To run, follow the internal sl4a setup doc.
 
 Note: the sl4a build is dependent on prebuilt libraries in your repo branch. So your local repo needs to be able to build first.
diff --git a/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java b/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
index 1c26226..236c870 100644
--- a/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
+++ b/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
@@ -110,6 +110,7 @@
       sFacadeClassList.add(BluetoothLeScanFacade.class);
       sFacadeClassList.add(BluetoothGattFacade.class);
       sFacadeClassList.add(BluetoothLeAdvertiseFacade.class);
+      sFacadeClassList.add(ConnectivityManagerFacade.class);
       sFacadeClassList.add(DisplayFacade.class);
       sFacadeClassList.add(TelecommManagerFacade.class);
       sFacadeClassList.add(WifiPasspointManagerFacade.class);