Merge "Revert "Disable proguard check. Make sl4a to be installed as priviliged app""
diff --git a/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothLeAdvertiseFacade.java b/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothLeAdvertiseFacade.java
new file mode 100644
index 0000000..739577a
--- /dev/null
+++ b/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothLeAdvertiseFacade.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2014 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.AdvertisementData.Builder;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.MainThread;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
+import com.googlecode.android_scripting.rpc.Rpc;
+import com.googlecode.android_scripting.rpc.RpcMinSdk;
+import com.googlecode.android_scripting.rpc.RpcParameter;
+import com.googlecode.android_scripting.rpc.RpcStartEvent;
+import com.googlecode.android_scripting.rpc.RpcStopEvent;
+
+/**
+ * BluetoothLe Advertise functions.
+ */
+
+@RpcMinSdk(5)
+public class BluetoothLeAdvertiseFacade extends RpcReceiver {
+
+    private final EventFacade mEventFacade;
+    private BluetoothAdapter mBluetoothAdapter;
+    private static int BleAdvertiseCallbackCount;
+    private static int BleAdvertiseSettingsCount;
+    private final HashMap<Integer, myAdvertiseCallback> mAdvertiseCallbackList;
+    private final BluetoothLeAdvertiser mAdvertise;
+    private final Service mService;
+    private final HashMap<Integer, Builder> mAdvertiseDataList;
+    private final HashMap<Integer, android.bluetooth.le.AdvertiseSettings.Builder> mAdvertiseSettingsList;
+
+    public BluetoothLeAdvertiseFacade(FacadeManager manager) {
+        super(manager);
+        mService = manager.getService();
+        mBluetoothAdapter = MainThread.run(mService,
+                new Callable<BluetoothAdapter>() {
+                    @Override
+                    public BluetoothAdapter call() throws Exception {
+                        return BluetoothAdapter.getDefaultAdapter();
+                    }
+                });
+        mEventFacade = manager.getReceiver(EventFacade.class);
+        mAdvertiseCallbackList = new HashMap<Integer, myAdvertiseCallback>();
+        mAdvertise = mBluetoothAdapter.getBluetoothLeAdvertiser();
+        mAdvertiseDataList = new HashMap<Integer, Builder>();
+        mAdvertiseSettingsList = new HashMap<Integer, android.bluetooth.le.AdvertiseSettings.Builder>();
+    }
+
+    /**
+     * Constructs a myAdvertiseCallback obj and returns its index
+     *
+     * @return myAdvertiseCallback.index
+     */
+    @Rpc(description = "Generate a new myAdvertisement Object")
+    public Integer genBleAdvertiseCallback() {
+        BleAdvertiseCallbackCount += 1;
+        int index = BleAdvertiseCallbackCount;
+        myAdvertiseCallback mCallback = new myAdvertiseCallback(index);
+        mAdvertiseCallbackList.put(mCallback.index,
+                mCallback);
+        return mCallback.index;
+    }
+
+    /**
+     * Constructs a AdvertisementData obj and returns its index
+     *
+     * @return index
+     */
+    @Rpc(description = "Constructs a new Builder obj for AdvertiseData and returns its index")
+    public Integer genBleAdvertiseData() {
+        int index = BleAdvertiseCallbackCount;
+        Builder mData = new Builder();
+        mAdvertiseDataList.put(index,
+                mData);
+        return index;
+    }
+
+    /**
+     * Constructs a AdvertisementSettings obj and returns its index
+     *
+     * @return index
+     */
+    @Rpc(description = "Constructs a new android.bluetooth.le.AdvertiseSettings.Builder obj for AdvertiseSettings and returns its index")
+    public Integer genBleAdvertiseSettings() {
+        BleAdvertiseSettingsCount += 1;
+        int index = BleAdvertiseSettingsCount;
+        android.bluetooth.le.AdvertiseSettings.Builder mSettings = new android.bluetooth.le.AdvertiseSettings.Builder();
+        mAdvertiseSettingsList.put(index,
+                mSettings);
+        return index;
+    }
+
+    /**
+     * Stops Advertising and Removes a myAdvertiseCallback obj
+     *
+     * @throws Exception
+     */
+    @Rpc(description = "Stops Advertising and Removes a myAdvertiseCallback obj")
+    public void removeBleAdvertiseCallback(
+            @RpcParameter(name = "index")
+            Integer index
+            ) throws Exception {
+        if (mAdvertiseCallbackList.get(index) != null) {
+            mAdvertiseCallbackList.remove(index);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Removes a AdvertiseSettings obj
+     *
+     * @throws Exception
+     */
+    @Rpc(description = "Removes a AdvertiseSettings obj")
+    public void removeBleAdvertiseSetting(
+            @RpcParameter(name = "index")
+            Integer index
+            ) throws Exception {
+        if (mAdvertiseSettingsList.get(index) != null) {
+            mAdvertiseSettingsList.remove(index);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Removes a AdvertiseData obj
+     *
+     * @throws Exception
+     */
+    @Rpc(description = "Removes a AdvertiseData obj")
+    public void removeBleAdvertiseData(
+            @RpcParameter(name = "index")
+            Integer index
+            ) throws Exception {
+        if (mAdvertiseDataList.get(index) != null) {
+            mAdvertiseDataList.remove(index);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Stops a ble advertisement
+     *
+     * @param index the id of the advertisement to stop advertising on
+     * @throws Exception
+     */
+    @Rpc(description = "Stops an ongoing ble advertisement scan")
+    @RpcStopEvent("BleAdvertise")
+    public void stopBleAdvertising(
+            @RpcParameter(name = "index")
+            Integer index) throws Exception {
+        if (mAdvertiseCallbackList.get(index) != null) {
+            Log.d("bluetooth_le mAdvertise " + index);
+            mAdvertise.stopAdvertising(mAdvertiseCallbackList
+                    .get(index));
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Starts ble advertising
+     *
+     * @param index the myAdvertisement object to start advertising on
+     * @throws Exception
+     */
+    @Rpc(description = "Starts ble advertisement")
+    @RpcStartEvent("BleAdvertising")
+    public void startBleAdvertising(
+            @RpcParameter(name = "callbackIndex")
+            Integer callbackIndex,
+            @RpcParameter(name = "dataIndex")
+            Integer dataIndex,
+            @RpcParameter(name = "settingsIndex")
+            Integer settingsIndex
+            ) throws Exception {
+        AdvertisementData mData = new AdvertisementData.Builder().build();
+        AdvertiseSettings mSettings = new AdvertiseSettings.Builder().build();
+        if (mAdvertiseDataList.get(dataIndex) != null) {
+            mData = mAdvertiseDataList.get(dataIndex).build();
+        } else {
+            throw new Exception("Invalid dataIndex input:"
+                    + Integer.toString(dataIndex));
+        }
+        if (mAdvertiseSettingsList.get(settingsIndex) != null) {
+            mSettings = mAdvertiseSettingsList.get(settingsIndex).build();
+        } else {
+            throw new Exception("Invalid settingsIndex input:"
+                    + Integer.toString(settingsIndex));
+        }
+        if (mAdvertiseCallbackList.get(callbackIndex) != null) {
+            Log.d("bluetooth_le starting a background scan on callback index: "
+                    + Integer.toString(callbackIndex));
+            mAdvertise
+                    .startAdvertising(mSettings, mData, mAdvertiseCallbackList.get(callbackIndex));
+        } else {
+            throw new Exception("Invalid callbackIndex input:"
+                    + Integer.toString(callbackIndex));
+        }
+    }
+
+    /**
+     * Set ble advertisement data include tx power level
+     *
+     * @param index the advertise data object to start advertising on
+     * @param includeTxPowerLevel boolean whether to include the tx power level or not in the
+     *            advertisement
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertisement data include tx power level")
+    public void setAdvertisementDataAdvertisementDataIncludeTxPowerLevel(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "includeTxPowerLevel")
+            Boolean includeTxPowerLevel
+            ) throws Exception {
+        if (mAdvertiseDataList.get(index) != null) {
+            mAdvertiseDataList.get(index).setIncludeTxPowerLevel(includeTxPowerLevel);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Set ble advertisement data service uuids
+     *
+     * @param index the advertise data object to start advertising on
+     * @param uuidList
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertisement data service uuids")
+    public void setAdvertisementDataSetServiceUuids(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "uuidList")
+            List<String> uuidList
+            ) throws Exception {
+        if (mAdvertiseDataList.get(index) != null) {
+            ArrayList<ParcelUuid> mUuids = new ArrayList<ParcelUuid>();
+            for (String uuid : uuidList) {
+                mUuids.add(ParcelUuid.fromString(uuid));
+            }
+            mAdvertiseDataList.get(index).setServiceUuids(mUuids);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Set ble advertise data service uuids
+     *
+     * @param index the advertise data object index
+     * @param serviceDataUuid
+     * @param serviceData
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertise data service uuids")
+    public void setAdvertisementDataSetServiceData(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "serviceDataUuid")
+            String serviceDataUuid,
+            @RpcParameter(name = "serviceData")
+            byte[] serviceData
+            ) throws Exception {
+        if (mAdvertiseDataList.get(index) != null) {
+            mAdvertiseDataList.get(index).setServiceData(
+                    ParcelUuid.fromString(serviceDataUuid),
+                    serviceData);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Set ble advertise data manufacturer id
+     *
+     * @param index the advertise data object index
+     * @param manufacturerId the manufacturer id to set
+     * @param manufacturerSpecificData the manufacturer specific data to set
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertise data manufacturerId")
+    public void setAdvertisementDataManufacturerId(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "manufacturerId")
+            Integer manufacturerId,
+            @RpcParameter(name = "manufacturerSpecificData")
+            byte[] manufacturerSpecificData
+            ) throws Exception {
+        if (mAdvertiseDataList.get(index) != null) {
+            mAdvertiseDataList.get(index).setManufacturerData(manufacturerId,
+                    manufacturerSpecificData);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Set ble advertise settings advertise mode
+     *
+     * @param index the advertise settings object index
+     * @param advertiseMode
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertise settings advertise mode")
+    public void setAdvertisementSettingAdvertiseMode(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "advertiseMode")
+            Integer advertiseMode
+            ) throws Exception {
+        if (mAdvertiseSettingsList.get(index) != null) {
+            mAdvertiseSettingsList.get(index).setAdvertiseMode(advertiseMode);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Set ble advertise settings tx power level
+     *
+     * @param index the advertise settings object index
+     * @param txPowerLevel the tx power level to set
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertise settings tx power level")
+    public void setAdvertisementSettingIncludeTxPowerLevel(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "includeTxPowerLevel")
+            Integer txPowerLevel
+            ) throws Exception {
+        if (mAdvertiseSettingsList.get(index) != null) {
+            mAdvertiseSettingsList.get(index).setTxPowerLevel(
+                    txPowerLevel);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Set ble advertise settings the setting type
+     *
+     * @param index the advertise settings object index
+     * @param type the setting type
+     * @throws Exception
+     */
+    @Rpc(description = "Set ble advertise settings the setting type")
+    public void setAdvertisementSettingType(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "type")
+            Integer type
+            ) throws Exception {
+        if (mAdvertiseSettingsList.get(index) != null) {
+            mAdvertiseSettingsList.get(index).setType(type);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    private class myAdvertiseCallback extends AdvertiseCallback {
+        public Integer index;
+        private final Bundle mResults;
+        String mEventType;
+
+        public myAdvertiseCallback(int idx) {
+            index = idx;
+            mEventType = "BleAdvertise";
+            mResults = new Bundle();
+        }
+
+        @Override
+        public void onSuccess(AdvertiseSettings settingsInEffect) {
+            Log.d("bluetooth_le_advertisement onSuccess " + mEventType + " "
+                    + index);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onSuccess");
+            mResults.putParcelable("SettingsInEffect", settingsInEffect);
+            mEventFacade.postEvent(mEventType + index + "onSuccess",
+                    mResults.clone());
+            mResults.clear();
+        }
+
+        @Override
+        public void onFailure(int errorCode) {
+            Log.d("bluetooth_le_advertisement onFailure " + mEventType + " "
+                    + index);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onFailure");
+            mResults.putInt("ErrorCode", errorCode);
+            mEventFacade.postEvent(mEventType + index + "onFailure",
+                    mResults.clone());
+            mResults.clear();
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        if (mAdvertiseCallbackList.isEmpty() == false) {
+            for (myAdvertiseCallback mAdvertise : mAdvertiseCallbackList
+                    .values()) {
+                if (mAdvertise != null) {
+                    mBluetoothAdapter.getBluetoothLeAdvertiser()
+                            .stopAdvertising(mAdvertise);
+                }
+            }
+            mAdvertiseCallbackList.clear();
+            mAdvertiseSettingsList.clear();
+            mAdvertiseDataList.clear();
+        }
+    }
+
+}
diff --git a/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothLeScanFacade.java b/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothLeScanFacade.java
new file mode 100644
index 0000000..2d868a3
--- /dev/null
+++ b/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothLeScanFacade.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2014 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanFilter.Builder;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.MainThread;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
+import com.googlecode.android_scripting.rpc.Rpc;
+import com.googlecode.android_scripting.rpc.RpcMinSdk;
+import com.googlecode.android_scripting.rpc.RpcOptional;
+import com.googlecode.android_scripting.rpc.RpcParameter;
+import com.googlecode.android_scripting.rpc.RpcStartEvent;
+import com.googlecode.android_scripting.rpc.RpcStopEvent;
+
+/**
+ * BluetoothLe Scan functions.
+ */
+
+@RpcMinSdk(5)
+public class BluetoothLeScanFacade extends RpcReceiver {
+
+    private final EventFacade mEventFacade;
+
+    private BluetoothAdapter mBluetoothAdapter;
+    private static int ScanCallbackCount;
+    private static int FilterListCount;
+    private static int ScanSettingsCount;
+    private final Service mService;
+    private final BluetoothLeScanner mScanner;
+    private final HashMap<Integer, myScanCallback> mScanCallbackList;
+    private final HashMap<Integer, ArrayList<Builder>> mScanFilterList;
+    private final HashMap<Integer, android.bluetooth.le.ScanSettings.Builder> mScanSettingsList;
+
+    public BluetoothLeScanFacade(FacadeManager manager) {
+        super(manager);
+        mService = manager.getService();
+        mBluetoothAdapter = MainThread.run(mService,
+                new Callable<BluetoothAdapter>() {
+                    @Override
+                    public BluetoothAdapter call() throws Exception {
+                        return BluetoothAdapter.getDefaultAdapter();
+                    }
+                });
+        mScanner = mBluetoothAdapter.getBluetoothLeScanner();
+        mEventFacade = manager.getReceiver(EventFacade.class);
+        mScanFilterList = new HashMap<Integer, ArrayList<Builder>>();
+        mScanSettingsList = new HashMap<Integer, android.bluetooth.le.ScanSettings.Builder>();
+        mScanCallbackList = new HashMap<Integer, myScanCallback>();
+
+    }
+
+    /**
+     * Constructs a myScanCallback obj and returns its index
+     *
+     * @return Integer myScanCallback.index
+     */
+    @Rpc(description = "Generate a new myScanCallback Object")
+    public Integer createScanCallback() {
+        ScanCallbackCount += 1;
+        int index = ScanCallbackCount;
+        myScanCallback mScan = new myScanCallback(index);
+        mScanCallbackList.put(mScan.index, mScan);
+        return mScan.index;
+    }
+
+    /**
+     * Constructs a new filter list array and returns its index
+     *
+     * @return Integer index
+     */
+    @Rpc(description = "Generate a new Filter list")
+    public Integer createFilterList() {
+        FilterListCount += 1;
+        int index = FilterListCount;
+        mScanFilterList.put(index, new ArrayList<Builder>());
+        return index;
+    }
+
+    /**
+     * Constructs a new scan setting and returns its index
+     *
+     * @return Integer index
+     */
+    @Rpc(description = "Generate a new scan settings Object")
+    public Integer createScanSetting() {
+        ScanSettingsCount += 1;
+        int index = ScanSettingsCount;
+        mScanSettingsList.put(index, new android.bluetooth.le.ScanSettings.Builder());
+        return index;
+    }
+
+    /**
+     * Removes a scan setting
+     *
+     * @return Integer index
+     * @throws Exception
+     */
+    @Rpc(description = "Removes a scan setting")
+    public void deleteScanSetting(
+            @RpcParameter(name = "index")
+            Integer index
+            ) throws Exception {
+        if (mScanSettingsList.get(index) != null) {
+            mScanSettingsList.remove(index);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Removes a scan callback
+     *
+     * @return Integer index
+     * @throws Exception
+     */
+    @Rpc(description = "Removes a scan callback")
+    public void deleteScanCallback(
+            @RpcParameter(name = "index")
+            Integer index
+            ) throws Exception {
+        if (mScanCallbackList.get(index) != null) {
+            mScanner.stopScan(mScanCallbackList.get(index));
+            mScanCallbackList.remove(index);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Removes a filter list
+     *
+     * @return Integer index
+     * @throws Exception
+     */
+    @Rpc(description = "Removes a filter list")
+    public void deleteFilterList(
+            @RpcParameter(name = "index")
+            Integer index
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            mScanFilterList.remove(index);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Stops a ble scan
+     *
+     * @param index the id of the myScan whose ScanCallback to stop
+     * @throws Exception
+     */
+    @Rpc(description = "Stops an ongoing ble advertisement scan")
+    @RpcStopEvent("BleScan")
+    public void stopBleScan(
+            @RpcParameter(name = "index")
+            Integer index) throws Exception {
+        Log.d("bluetooth_le_scan mScanCallback " + index);
+        if (mScanCallbackList.get(index) != null) {
+            myScanCallback mScanCallback = mScanCallbackList.get(index);
+            mScanner.stopScan(mScanCallback);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Starts a ble scan
+     *
+     * @param index the id of the myScan whose ScanCallback to start
+     * @throws Exception
+     */
+    @Rpc(description = "Starts a ble advertisement scan")
+    @RpcStartEvent("BleScan")
+    public void startBleScan(
+            @RpcParameter(name = "filterListIndex")
+            Integer filterListIndex,
+            @RpcParameter(name = "scanSettingsIndex")
+            Integer scanSettingsIndex,
+            @RpcParameter(name = "callbackIndex")
+            Integer callbackIndex
+            ) throws Exception {
+        Log.d("bluetooth_le_scan starting a background scan");
+        ArrayList<ScanFilter> mScanFilters = new ArrayList<ScanFilter>();
+        ScanSettings mScanSettings = new ScanSettings.Builder().build();
+        if (mScanFilterList.get(filterListIndex) != null) {
+            ArrayList<Builder> mFilterList = mScanFilterList.get(filterListIndex);
+            if (mFilterList.size() == 0) { // then use the default filter
+                mScanFilters.add(new ScanFilter.Builder().build());
+            } else {
+                for (Builder unbuiltScanFilter : mFilterList) {
+                    mScanFilters.add(unbuiltScanFilter.build());
+                }
+            }
+        } else {
+            throw new Exception("Invalid filterListIndex input:"
+                    + Integer.toString(filterListIndex));
+        }
+        if (mScanSettingsList.get(scanSettingsIndex) != null) {
+            mScanSettings = mScanSettingsList.get(scanSettingsIndex).build();
+        } else if (!mScanSettingsList.isEmpty()) {
+            throw new Exception("Invalid scanSettingsIndex input:"
+                    + Integer.toString(scanSettingsIndex));
+        }
+        if (mScanCallbackList.get(callbackIndex) != null) {
+            mScanner.startScan(mScanFilters, mScanSettings, mScanCallbackList.get(callbackIndex));
+        } else {
+            throw new Exception("Invalid filterListIndex input:"
+                    + Integer.toString(filterListIndex));
+        }
+    }
+
+    /**
+     * Get a ble batch Scan results
+     *
+     * @param flush the results
+     * @throws Exception
+     */
+    @Rpc(description = "Gets the results of the ble ScanCallback")
+    public List<ScanResult> getBatchScanResults(
+            @RpcParameter(name = "callbackIndex")
+            Integer callbackIndex,
+            @RpcParameter(name = "flush")
+            Boolean flush) throws Exception {
+        if (mScanCallbackList.get(callbackIndex) != null) {
+            return mBluetoothAdapter
+                    .getBluetoothLeScanner().getBatchScanResults(
+                            mScanCallbackList.get(callbackIndex),
+                            flush);
+        } else {
+            throw new Exception("Invalid callbackIndex input:"
+                    + Integer.toString(callbackIndex));
+        }
+    }
+
+    /**
+     * Set scanSettings for ble scan. Note: You have to set all variables at once.
+     *
+     * @param index the index of the ScanSettings
+     * @param callbackType Bluetooth LE scan callback type
+     * @param reportDelayNanos Time of delay for reporting the scan result
+     * @param scanMode Bluetooth LE scan mode.
+     * @param scanResultType Bluetooth LE scan result type
+     * @throws Exception
+     */
+    @Rpc(description = "Set a new Scan Setting for ble scanning")
+    public void setScanSettings(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "callbackType")
+            Integer callbackType,
+            @RpcParameter(name = "reportDelayNanos")
+            Integer reportDelayNanos,
+            @RpcParameter(name = "scanMode")
+            Integer scanMode,
+            @RpcParameter(name = "scanResultType")
+            Integer scanResultType) throws Exception {
+        if (mScanSettingsList.get(index) != null) {
+            android.bluetooth.le.ScanSettings.Builder mBuilder = mScanSettingsList.get(index);
+            mBuilder.setCallbackType(callbackType);
+            mBuilder.setCallbackType(scanMode);
+            mBuilder.setCallbackType(scanResultType);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Add filter "macAddress" to existing ScanFilter
+     *
+     * @param index the index of the ScanFilter
+     * @param filterIndex Integer the filter to add to
+     * @param macAddress the macAddress to filter against
+     * @throws Exception
+     */
+    @Rpc(description = "Add filter \"macAddress\" to existing ScanFilter")
+    public void setScanFilterMacAddress(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex,
+            @RpcParameter(name = "macAddress")
+            String macAddress
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            mScanFilterList.get(index).get(filterIndex).setMacAddress(macAddress);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Add filter "manufacturereDataId and/or manufacturerData" to existing ScanFilter
+     *
+     * @param index the index of the myScan
+     * @param filterIndex Integer the filter to add to
+     * @param manufacturerDataId the manufacturer data id to filter against
+     * @param manufacturerDataMask the manufacturere data mask to filter against
+     * @throws Exception
+     */
+    @Rpc(description = "Add filter \"manufacturereDataId and/or manufacturerData\" to existing ScanFilter")
+    public void setScanFiltermanufacturerData(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex,
+            @RpcParameter(name = "manufacturerDataId")
+            Integer manufacturerDataId,
+            @RpcParameter(name = "manufacturerData")
+            byte[] manufacturerData,
+            @RpcParameter(name = "manufacturerDataMask")
+            @RpcOptional
+            byte[] manufacturerDataMask
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            if (manufacturerDataMask != null) {
+                mScanFilterList.get(index).get(filterIndex).setManufacturerData(manufacturerDataId,
+                        manufacturerData, manufacturerDataMask);
+            } else {
+                mScanFilterList.get(index).get(filterIndex).setManufacturerData(manufacturerDataId,
+                        manufacturerData);
+            }
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Add filter "minRssi and maxRssi" to existing ScanFilter
+     *
+     * @param index the index of the myScan
+     * @param filterIndex Integer the filter to add to
+     * @param minRssi the min rssi to filter against
+     * @param maxRssi the max rssi to filter against
+     * @throws Exception
+     */
+    @Rpc(description = "Add filter \"minRssi and maxRssi\" to existing ScanFilter")
+    public void setScanFilterRssiRange(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex,
+            @RpcParameter(name = "minRssi")
+            Integer minRssi,
+            @RpcParameter(name = "maxRssi")
+            Integer maxRssi
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            mScanFilterList.get(index).get(filterIndex).setRssiRange(minRssi, maxRssi);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Add filter "serviceData and serviceDataMask" to existing ScanFilter
+     *
+     * @param index the index of the myScan
+     * @param filterIndex Integer the filter to add to
+     * @param serviceData the service data to filter against
+     * @param serviceDataMask the servie data mask to filter against
+     * @throws Exception
+     */
+    @Rpc(description = "Add filter \"serviceData and serviceDataMask\" to existing ScanFilter ")
+    public void setScanFilterServiceData(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex,
+            @RpcParameter(name = "serviceData")
+            byte[] serviceData,
+            @RpcParameter(name = "serviceDataMask")
+            @RpcOptional
+            byte[] serviceDataMask
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            if (serviceData != null) {
+                mScanFilterList.get(index).get(filterIndex)
+                        .setServiceData(serviceData, serviceDataMask);
+            } else {
+                mScanFilterList.get(index).get(filterIndex).setServiceData(serviceData);
+            }
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Add filter "serviceUuid and/or serviceMask" to existing ScanFilter
+     *
+     * @param index the index of the myScan
+     * @param filterIndex Integer the filter to add to
+     * @param serviceUuid the service uuid to filter against
+     * @param serviceMask the service mask to filter against
+     * @throws Exception
+     */
+    @Rpc(description = "Add filter \"serviceUuid and/or serviceMask\" to existing ScanFilter")
+    public void setScanFilterServiceUuid(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex,
+            @RpcParameter(name = "serviceUuid")
+            String serviceUuid,
+            @RpcParameter(name = "serviceMask")
+            @RpcOptional
+            String serviceMask
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            mScanFilterList
+                    .get(index)
+                    .get(filterIndex)
+                    .setServiceUuid(ParcelUuid.fromString(serviceUuid),
+                            ParcelUuid.fromString(serviceMask));
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Add filter "name" to existing ScanFilter
+     *
+     * @param index the index of the myScan
+     * @param filterIndex Integer the filter to add to
+     * @param name the name to filter against
+     * @throws Exception
+     */
+    @Rpc(description = "Remove a scanFilter from the scanFilterList")
+    public void setScanFilterName(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex,
+            @RpcParameter(name = "name")
+            String name
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            mScanFilterList.get(index).get(filterIndex).setName(name);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    /**
+     * Remove a scanFilter from the scanFilterList
+     *
+     * @param index the index of the myScan
+     * @param filterIndex Integer of the filter to remove
+     * @throws Exception
+     */
+    @Rpc(description = "Remove a scanFilter from the scanFilterList")
+    public void removeScanFilterFromScanFilterList(
+            @RpcParameter(name = "index")
+            Integer index,
+            @RpcParameter(name = "filterIndex")
+            Integer filterIndex
+            ) throws Exception {
+        if (mScanFilterList.get(index) != null) {
+            mScanFilterList.get(index).remove(filterIndex);
+        } else {
+            throw new Exception("Invalid index input:"
+                    + Integer.toString(index));
+        }
+    }
+
+    private class myScanCallback extends ScanCallback {
+        public Integer index;
+        String mEventType;
+        private final Bundle mResults;
+
+        public myScanCallback(Integer idx) {
+            index = idx;
+            mEventType = "BleScan";
+            mResults = new Bundle();
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            Log.d("bluetooth_le_scan change onScanFailed " + mEventType + " " + index);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onScanFailed");
+            mResults.putInt("ErrorCode", errorCode);
+            mEventFacade.postEvent(mEventType + index + "onScanFailed",
+                    mResults.clone());
+            mResults.clear();
+        }
+
+        @Override
+        public void onAdvertisementUpdate(ScanResult result) {
+            Log.d("bluetooth_le_scan change onUpdate " + mEventType + " " + index);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onAdvertisementUpdate");
+            mResults.putParcelable("Result", result);
+            mEventFacade.postEvent(
+                    mEventType + index + "onAdvertisementUpdate",
+                    mResults.clone());
+            mResults.clear();
+        }
+
+        @Override
+        public void onAdvertisementFound(ScanResult result) {
+            Log.d("bluetooth_le_scan onAdvertisementFound " + mEventType + " "
+                    + index);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onAdvertisementFound");
+            mEventFacade.postEvent(mEventType + index + "onAdvertisementFound",
+                    mResults.clone());
+            mResults.clear();
+        }
+
+        @Override
+        public void onAdvertisementLost(ScanResult result) {
+            Log.d("bluetooth_le_scan onAdvertisementLost" + mEventType + " " + index);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onAdvertisementLost");
+            mResults.putParcelable("Result", result);
+            mEventFacade.postEvent(mEventType + index + "onAdvertisementLost",
+                    mResults.clone());
+            mResults.clear();
+        }
+
+        @Override
+        public void onBatchScanResults(List<ScanResult> results) {
+            Log.d("reportResult " + mEventType + " " + index);
+            mResults.putLong("Timestamp", System.currentTimeMillis() / 1000);
+            mResults.putInt("ID", index);
+            mResults.putString("Type", "onBatchScanResults");
+            mResults.putParcelableList("Results", results);
+            mEventFacade.postEvent(mEventType + index + "onBatchScanResult",
+                    mResults.clone());
+            mResults.clear();
+        }
+
+    }
+
+    @Override
+    public void shutdown() {
+        if (mScanCallbackList.isEmpty() == false) {
+            for (myScanCallback mScanCallback : mScanCallbackList.values()) {
+                if (mScanCallback != null) {
+                    mBluetoothAdapter.getBluetoothLeScanner().stopScan(
+                            mScanCallback);
+                }
+            }
+        }
+        mScanCallbackList.clear();
+        mScanFilterList.clear();
+        mScanSettingsList.clear();
+    }
+
+}
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
index eca99d3..bed06e6 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
@@ -16,6 +16,23 @@
 
 package com.googlecode.android_scripting.jsonrpc;
 
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.codec.binary.Base64Codec;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.le.AdvertiseSettings;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.location.Address;
@@ -29,289 +46,360 @@
 
 import com.googlecode.android_scripting.event.Event;
 
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.apache.commons.codec.binary.Base64Codec;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
 public class JsonBuilder {
 
-  private JsonBuilder() {
-    // This is a utility class.
-  }
+    private JsonBuilder() {
+        // This is a utility class.
+    }
 
-  @SuppressWarnings("unchecked")
-  public static Object build(Object data) throws JSONException {
-    if (data == null) {
-      return JSONObject.NULL;
+    @SuppressWarnings("unchecked")
+    public static Object build(Object data) throws JSONException {
+        if (data == null) {
+            return JSONObject.NULL;
+        }
+        if (data instanceof Integer) {
+            return data;
+        }
+        if (data instanceof Float) {
+            return data;
+        }
+        if (data instanceof Double) {
+            return data;
+        }
+        if (data instanceof Long) {
+            return data;
+        }
+        if (data instanceof String) {
+            return data;
+        }
+        if (data instanceof Boolean) {
+            return data;
+        }
+        if (data instanceof JSONObject) {
+            return data;
+        }
+        if (data instanceof JSONArray) {
+            return data;
+        }
+        if (data instanceof Set<?>) {
+            List<Object> items = new ArrayList<Object>((Set<?>) data);
+            return buildJsonList(items);
+        }
+        if (data instanceof List<?>) {
+            return buildJsonList((List<?>) data);
+        }
+        if (data instanceof Address) {
+            return buildJsonAddress((Address) data);
+        }
+        if (data instanceof Location) {
+            return buildJsonLocation((Location) data);
+        }
+        if (data instanceof Bundle) {
+            return buildJsonBundle((Bundle) data);
+        }
+        if (data instanceof Intent) {
+            return buildJsonIntent((Intent) data);
+        }
+        if (data instanceof Event) {
+            return buildJsonEvent((Event) data);
+        }
+        if (data instanceof Map<?, ?>) {
+            // TODO(damonkohler): I would like to make this a checked cast if possible.
+            return buildJsonMap((Map<String, ?>) data);
+        }
+        if (data instanceof ScanResult) {
+            return buildJsonScanResult((ScanResult) data);
+        }
+        if (data instanceof android.bluetooth.le.ScanResult) {
+            return buildJsonBleScanResult((android.bluetooth.le.ScanResult) data);
+        }
+        if (data instanceof AdvertiseSettings) {
+            return buildJsonBleAdvertiseSettings((AdvertiseSettings) data);
+        }
+        if (data instanceof BluetoothGattService) {
+            return buildJsonBluetoothGattService((BluetoothGattService) data);
+        }
+        if (data instanceof BluetoothGattCharacteristic) {
+            return buildJsonBluetoothGattCharacteristic((BluetoothGattCharacteristic) data);
+        }
+        if (data instanceof BluetoothGattDescriptor) {
+            return buildJsonBluetoothGattDescriptor((BluetoothGattDescriptor) data);
+        }
+        if (data instanceof BluetoothDevice) {
+            return buildJsonBluetoothDevice((BluetoothDevice) data);
+        }
+        if (data instanceof CellLocation) {
+            return buildJsonCellLocation((CellLocation) data);
+        }
+        if (data instanceof WifiInfo) {
+            return buildJsonWifiInfo((WifiInfo) data);
+        }
+        if (data instanceof NeighboringCellInfo) {
+            return buildNeighboringCellInfo((NeighboringCellInfo) data);
+        }
+        if (data instanceof InetSocketAddress) {
+            return buildInetSocketAddress((InetSocketAddress) data);
+        }
+        if (data instanceof byte[]) {
+            return Base64Codec.encodeBase64((byte[]) data);
+        }
+        if (data instanceof Object[]) {
+            return buildJSONArray((Object[]) data);
+        }
+        return data.toString();
+        // throw new JSONException("Failed to build JSON result. " + data.getClass().getName());
     }
-    if (data instanceof Integer) {
-      return data;
-    }
-    if (data instanceof Float) {
-      return data;
-    }
-    if (data instanceof Double) {
-      return data;
-    }
-    if (data instanceof Long) {
-      return data;
-    }
-    if (data instanceof String) {
-      return data;
-    }
-    if (data instanceof Boolean) {
-      return data;
-    }
-    if (data instanceof JSONObject) {
-      return data;
-    }
-    if (data instanceof JSONArray) {
-      return data;
-    }
-    if (data instanceof Set<?>) {
-      List<Object> items = new ArrayList<Object>((Set<?>) data);
-      return buildJsonList(items);
-    }
-    if (data instanceof List<?>) {
-      return buildJsonList((List<?>) data);
-    }
-    if (data instanceof Address) {
-      return buildJsonAddress((Address) data);
-    }
-    if (data instanceof Location) {
-      return buildJsonLocation((Location) data);
-    }
-    if (data instanceof Bundle) {
-      return buildJsonBundle((Bundle) data);
-    }
-    if (data instanceof Intent) {
-      return buildJsonIntent((Intent) data);
-    }
-    if (data instanceof Event) {
-      return buildJsonEvent((Event) data);
-    }
-    if (data instanceof Map<?, ?>) {
-      // TODO(damonkohler): I would like to make this a checked cast if possible.
-      return buildJsonMap((Map<String, ?>) data);
-    }
-    if (data instanceof ScanResult) {
-      return buildJsonScanResult((ScanResult) data);
-    }
-    if (data instanceof CellLocation) {
-      return buildJsonCellLocation((CellLocation) data);
-    }
-    if (data instanceof WifiInfo) {
-      return buildJsonWifiInfo((WifiInfo) data);
-    }
-    if (data instanceof NeighboringCellInfo) {
-      return buildNeighboringCellInfo((NeighboringCellInfo) data);
-    }
-    if (data instanceof InetSocketAddress) {
-      return buildInetSocketAddress((InetSocketAddress) data);
-    }
-    if (data instanceof byte[]) {
-      return Base64Codec.encodeBase64((byte[]) data);
-    }
-    if (data instanceof Object[]) {
-      return buildJSONArray((Object[]) data);
-    }
-    return data.toString();
-    // throw new JSONException("Failed to build JSON result. " + data.getClass().getName());
-  }
 
-  private static JSONArray buildJSONArray(Object[] data) throws JSONException {
-    JSONArray result = new JSONArray();
-    for (Object o : data) {
-      result.put(build(o));
+    private static Object buildJsonBluetoothDevice(BluetoothDevice data) throws JSONException {
+        JSONObject deviceInfo = new JSONObject();
+        deviceInfo.put("macAddress", data.getAddress());
+        deviceInfo.put("bondState", data.getBondState());
+        deviceInfo.put("name", data.getName());
+        deviceInfo.put("type", data.getType());
+        return deviceInfo;
     }
-    return result;
-  }
 
-  private static Object buildInetSocketAddress(InetSocketAddress data) {
-    JSONArray address = new JSONArray();
-    address.put(data.getHostName());
-    address.put(data.getPort());
-    return address;
-  }
-
-  private static <T> JSONArray buildJsonList(final List<T> list) throws JSONException {
-    JSONArray result = new JSONArray();
-    for (T item : list) {
-      result.put(build(item));
+    private static JSONArray buildJSONArray(Object[] data) throws JSONException {
+        JSONArray result = new JSONArray();
+        for (Object o : data) {
+            result.put(build(o));
+        }
+        return result;
     }
-    return result;
-  }
 
-  private static JSONObject buildJsonAddress(Address address) throws JSONException {
-    JSONObject result = new JSONObject();
-    result.put("admin_area", address.getAdminArea());
-    result.put("country_code", address.getCountryCode());
-    result.put("country_name", address.getCountryName());
-    result.put("feature_name", address.getFeatureName());
-    result.put("phone", address.getPhone());
-    result.put("locality", address.getLocality());
-    result.put("postal_code", address.getPostalCode());
-    result.put("sub_admin_area", address.getSubAdminArea());
-    result.put("thoroughfare", address.getThoroughfare());
-    result.put("url", address.getUrl());
-    return result;
-  }
-
-  private static JSONObject buildJsonLocation(Location location) throws JSONException {
-    JSONObject result = new JSONObject();
-    result.put("altitude", location.getAltitude());
-    result.put("latitude", location.getLatitude());
-    result.put("longitude", location.getLongitude());
-    result.put("time", location.getTime());
-    result.put("accuracy", location.getAccuracy());
-    result.put("speed", location.getSpeed());
-    result.put("provider", location.getProvider());
-    result.put("bearing", location.getBearing());
-    return result;
-  }
-
-  private static JSONObject buildJsonBundle(Bundle bundle) throws JSONException {
-    JSONObject result = new JSONObject();
-    for (String key : bundle.keySet()) {
-      result.put(key, build(bundle.get(key)));
+    private static Object buildInetSocketAddress(InetSocketAddress data) {
+        JSONArray address = new JSONArray();
+        address.put(data.getHostName());
+        address.put(data.getPort());
+        return address;
     }
-    return result;
-  }
 
-  private static JSONObject buildJsonIntent(Intent data) throws JSONException {
-    JSONObject result = new JSONObject();
-    result.put("data", data.getDataString());
-    result.put("type", data.getType());
-    result.put("extras", build(data.getExtras()));
-    result.put("categories", build(data.getCategories()));
-    result.put("action", data.getAction());
-    ComponentName component = data.getComponent();
-    if (component != null) {
-      result.put("packagename", component.getPackageName());
-      result.put("classname", component.getClassName());
+    private static <T> JSONArray buildJsonList(final List<T> list) throws JSONException {
+        JSONArray result = new JSONArray();
+        for (T item : list) {
+            result.put(build(item));
+        }
+        return result;
     }
-    result.put("flags", data.getFlags());
-    return result;
-  }
 
-  private static JSONObject buildJsonEvent(Event event) throws JSONException {
-    JSONObject result = new JSONObject();
-    result.put("name", event.getName());
-    result.put("data", build(event.getData()));
-    result.put("time", event.getCreationTime());
-    return result;
-  }
-
-  private static JSONObject buildJsonMap(Map<String, ?> map) throws JSONException {
-    JSONObject result = new JSONObject();
-    for (Entry<String, ?> entry : map.entrySet()) {
-      result.put(entry.getKey(), build(entry.getValue()));
+    private static JSONObject buildJsonAddress(Address address) throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("admin_area", address.getAdminArea());
+        result.put("country_code", address.getCountryCode());
+        result.put("country_name", address.getCountryName());
+        result.put("feature_name", address.getFeatureName());
+        result.put("phone", address.getPhone());
+        result.put("locality", address.getLocality());
+        result.put("postal_code", address.getPostalCode());
+        result.put("sub_admin_area", address.getSubAdminArea());
+        result.put("thoroughfare", address.getThoroughfare());
+        result.put("url", address.getUrl());
+        return result;
     }
-    return result;
-  }
 
-  private static JSONObject buildJsonScanResult(ScanResult scanResult) throws JSONException {
-    JSONObject result = new JSONObject();
-    result.put("bssid", scanResult.BSSID);
-    result.put("ssid", scanResult.SSID);
-    result.put("frequency", scanResult.frequency);
-    result.put("level", scanResult.level);
-    result.put("capabilities", scanResult.capabilities);
-    result.put("timestamp", scanResult.timestamp);
-// The following fields are hidden for now, uncomment when they're unhidden
-//    result.put("seen", scanResult.seen);
-//    result.put("distanceCm", scanResult.distanceCm);
-//    result.put("distanceSdCm", scanResult.distanceSdCm);
-//    if (scanResult.informationElements != null){
-//      JSONArray infoEles = new JSONArray();
-//      for(ScanResult.InformationElement ie : scanResult.informationElements) {
-//        JSONObject infoEle = new JSONObject();
-//        infoEle.put("id", ie.id);
-//        infoEle.put("bytes", Base64Codec.encodeBase64(ie.bytes));
-//        infoEles.put(infoEle);
-//      }
-//      result.put("InfomationElements", infoEles);
-//    } else
-//      result.put("InfomationElements", null);
-    return result;
-  }
-
-  private static JSONObject buildJsonCellLocation(CellLocation cellLocation) throws JSONException {
-    JSONObject result = new JSONObject();
-    if (cellLocation instanceof GsmCellLocation) {
-      GsmCellLocation location = (GsmCellLocation) cellLocation;
-      result.put("lac", location.getLac());
-      result.put("cid", location.getCid());
+    private static JSONObject buildJsonLocation(Location location) throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("altitude", location.getAltitude());
+        result.put("latitude", location.getLatitude());
+        result.put("longitude", location.getLongitude());
+        result.put("time", location.getTime());
+        result.put("accuracy", location.getAccuracy());
+        result.put("speed", location.getSpeed());
+        result.put("provider", location.getProvider());
+        result.put("bearing", location.getBearing());
+        return result;
     }
-    // TODO(damonkohler): Add support for CdmaCellLocation. Not supported until API level 5.
-    return result;
-  }
 
-  private static JSONObject buildJsonWifiInfo(WifiInfo data) throws JSONException {
-    JSONObject result = new JSONObject();
-    result.put("hidden_ssid", data.getHiddenSSID());
-    result.put("ip_address", data.getIpAddress());
-    result.put("link_speed", data.getLinkSpeed());
-    result.put("network_id", data.getNetworkId());
-    result.put("rssi", data.getRssi());
-    result.put("bssid", data.getBSSID());
-    result.put("mac_address", data.getMacAddress());
-    result.put("ssid", data.getSSID());
-    String supplicantState = "";
-    switch (data.getSupplicantState()) {
-    case ASSOCIATED:
-      supplicantState = "associated";
-      break;
-    case ASSOCIATING:
-      supplicantState = "associating";
-      break;
-    case COMPLETED:
-      supplicantState = "completed";
-      break;
-    case DISCONNECTED:
-      supplicantState = "disconnected";
-      break;
-    case DORMANT:
-      supplicantState = "dormant";
-      break;
-    case FOUR_WAY_HANDSHAKE:
-      supplicantState = "four_way_handshake";
-      break;
-    case GROUP_HANDSHAKE:
-      supplicantState = "group_handshake";
-      break;
-    case INACTIVE:
-      supplicantState = "inactive";
-      break;
-    case INVALID:
-      supplicantState = "invalid";
-      break;
-    case SCANNING:
-      supplicantState = "scanning";
-      break;
-    case UNINITIALIZED:
-      supplicantState = "uninitialized";
-      break;
-    default:
-      supplicantState = null;
+    private static JSONObject buildJsonBundle(Bundle bundle) throws JSONException {
+        JSONObject result = new JSONObject();
+        for (String key : bundle.keySet()) {
+            result.put(key, build(bundle.get(key)));
+        }
+        return result;
     }
-    result.put("supplicant_state", build(supplicantState));
-    return result;
-  }
 
-  private static JSONObject buildNeighboringCellInfo(NeighboringCellInfo data) throws JSONException {
-    // TODO(damonkohler): Additional information available at API level 5.
-    JSONObject result = new JSONObject();
-    result.put("cid", data.getCid());
-    result.put("rssi", data.getRssi());
-    return result;
-  }
+    private static JSONObject buildJsonIntent(Intent data) throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("data", data.getDataString());
+        result.put("type", data.getType());
+        result.put("extras", build(data.getExtras()));
+        result.put("categories", build(data.getCategories()));
+        result.put("action", data.getAction());
+        ComponentName component = data.getComponent();
+        if (component != null) {
+            result.put("packagename", component.getPackageName());
+            result.put("classname", component.getClassName());
+        }
+        result.put("flags", data.getFlags());
+        return result;
+    }
+
+    private static JSONObject buildJsonEvent(Event event) throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("name", event.getName());
+        result.put("data", build(event.getData()));
+        result.put("time", event.getCreationTime());
+        return result;
+    }
+
+    private static JSONObject buildJsonMap(Map<String, ?> map) throws JSONException {
+        JSONObject result = new JSONObject();
+        for (Entry<String, ?> entry : map.entrySet()) {
+            result.put(entry.getKey(), build(entry.getValue()));
+        }
+        return result;
+    }
+
+    private static JSONObject buildJsonScanResult(ScanResult scanResult) throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("bssid", scanResult.BSSID);
+        result.put("ssid", scanResult.SSID);
+        result.put("frequency", scanResult.frequency);
+        result.put("level", scanResult.level);
+        result.put("capabilities", scanResult.capabilities);
+        result.put("timestamp", scanResult.timestamp);
+        // The following fields are hidden for now, uncomment when they're unhidden
+        // result.put("seen", scanResult.seen);
+        // result.put("distanceCm", scanResult.distanceCm);
+        // result.put("distanceSdCm", scanResult.distanceSdCm);
+        // if (scanResult.informationElements != null){
+        // JSONArray infoEles = new JSONArray();
+        // for(ScanResult.InformationElement ie : scanResult.informationElements) {
+        // JSONObject infoEle = new JSONObject();
+        // infoEle.put("id", ie.id);
+        // infoEle.put("bytes", Base64Codec.encodeBase64(ie.bytes));
+        // infoEles.put(infoEle);
+        // }
+        // result.put("InfomationElements", infoEles);
+        // } else
+        // result.put("InfomationElements", null);
+        return result;
+    }
+
+    private static JSONObject buildJsonBleScanResult(android.bluetooth.le.ScanResult scanResult)
+            throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("rssi", scanResult.getRssi());
+        result.put("timestampNanos", scanResult.getTimestampNanos());
+        result.put("scanRecord", build(scanResult.getScanRecord()));
+        result.put("deviceInfo", build(scanResult.getDevice()));
+        return result;
+    }
+
+    private static JSONObject buildJsonBleAdvertiseSettings(AdvertiseSettings advertiseSettings)
+            throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("mode", advertiseSettings.getMode());
+        result.put("txPowerLevel", advertiseSettings.getTxPowerLevel());
+        result.put("type", advertiseSettings.getType());
+        return result;
+    }
+
+    private static Object buildJsonBluetoothGattService(BluetoothGattService data)
+            throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("instanceId", data.getInstanceId());
+        result.put("type", data.getType());
+        result.put("gattCharacteristicList", build(data.getCharacteristics()));
+        result.put("includedServices", build(data.getIncludedServices()));
+        result.put("uuid", data.getUuid().toString());
+        return result;
+    }
+
+    private static Object buildJsonBluetoothGattCharacteristic(BluetoothGattCharacteristic data)
+            throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("instanceId", data.getInstanceId());
+        result.put("permissions", data.getPermissions());
+        result.put("properties", data.getProperties());
+        result.put("writeType", data.getWriteType());
+        result.put("descriptorsList", build(data.getDescriptors()));
+        result.put("uuid", data.getUuid().toString());
+        result.put("value", build(data.getValue()));
+
+        return result;
+    }
+
+    private static Object buildJsonBluetoothGattDescriptor(BluetoothGattDescriptor data)
+            throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("instanceId", data.getInstanceId());
+        result.put("permissions", data.getPermissions());
+        result.put("characteristic", data.getCharacteristic());
+        result.put("uuid", data.getUuid().toString());
+        result.put("value", build(data.getValue()));
+        return result;
+    }
+
+    private static JSONObject buildJsonCellLocation(CellLocation cellLocation) throws JSONException {
+        JSONObject result = new JSONObject();
+        if (cellLocation instanceof GsmCellLocation) {
+            GsmCellLocation location = (GsmCellLocation) cellLocation;
+            result.put("lac", location.getLac());
+            result.put("cid", location.getCid());
+        }
+        // TODO(damonkohler): Add support for CdmaCellLocation. Not supported until API level 5.
+        return result;
+    }
+
+    private static JSONObject buildJsonWifiInfo(WifiInfo data) throws JSONException {
+        JSONObject result = new JSONObject();
+        result.put("hidden_ssid", data.getHiddenSSID());
+        result.put("ip_address", data.getIpAddress());
+        result.put("link_speed", data.getLinkSpeed());
+        result.put("network_id", data.getNetworkId());
+        result.put("rssi", data.getRssi());
+        result.put("bssid", data.getBSSID());
+        result.put("mac_address", data.getMacAddress());
+        result.put("ssid", data.getSSID());
+        String supplicantState = "";
+        switch (data.getSupplicantState()) {
+            case ASSOCIATED:
+                supplicantState = "associated";
+                break;
+            case ASSOCIATING:
+                supplicantState = "associating";
+                break;
+            case COMPLETED:
+                supplicantState = "completed";
+                break;
+            case DISCONNECTED:
+                supplicantState = "disconnected";
+                break;
+            case DORMANT:
+                supplicantState = "dormant";
+                break;
+            case FOUR_WAY_HANDSHAKE:
+                supplicantState = "four_way_handshake";
+                break;
+            case GROUP_HANDSHAKE:
+                supplicantState = "group_handshake";
+                break;
+            case INACTIVE:
+                supplicantState = "inactive";
+                break;
+            case INVALID:
+                supplicantState = "invalid";
+                break;
+            case SCANNING:
+                supplicantState = "scanning";
+                break;
+            case UNINITIALIZED:
+                supplicantState = "uninitialized";
+                break;
+            default:
+                supplicantState = null;
+        }
+        result.put("supplicant_state", build(supplicantState));
+        return result;
+    }
+
+    private static JSONObject buildNeighboringCellInfo(NeighboringCellInfo data)
+            throws JSONException {
+        // TODO(damonkohler): Additional information available at API level 5.
+        JSONObject result = new JSONObject();
+        result.put("cid", data.getCid());
+        result.put("rssi", data.getRssi());
+        return result;
+    }
 }
diff --git a/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java b/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
index 3992a7f..31ca648 100644
--- a/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
+++ b/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2014 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
@@ -38,7 +38,7 @@
 
 /**
  * Encapsulates the list of supported facades and their construction.
- * 
+ *
  * @author Damon Kohler (damonkohler@gmail.com)
  * @author Igor Karp (igor.v.karp@gmail.com)
  */
@@ -106,6 +106,8 @@
     if (sSdkLevel >= 18) {
       sFacadeClassList.add(ScanFacade.class);
       sFacadeClassList.add(WifiPasspointFacade.class);
+      sFacadeClassList.add(BluetoothLeScanFacade.class);
+      sFacadeClassList.add(BluetoothLeAdvertiseFacade.class);
     }
 
     for (Class<? extends RpcReceiver> recieverClass : sFacadeClassList) {