diff --git a/WearScript/build.gradle b/WearScript/build.gradle index 02598b77..9477c21a 100644 --- a/WearScript/build.gradle +++ b/WearScript/build.gradle @@ -20,7 +20,7 @@ android { buildToolsVersion "19.1.0" defaultConfig { - minSdkVersion 15 + minSdkVersion 19 targetSdkVersion 19 } } diff --git a/WearScript/src/main/AndroidManifest.xml b/WearScript/src/main/AndroidManifest.xml index 1358cecd..45bdfa41 100644 --- a/WearScript/src/main/AndroidManifest.xml +++ b/WearScript/src/main/AndroidManifest.xml @@ -17,6 +17,9 @@ + diff --git a/WearScript/src/main/assets/init.js b/WearScript/src/main/assets/init.js index abd08c86..1cc90ca1 100644 --- a/WearScript/src/main/assets/init.js +++ b/WearScript/src/main/assets/init.js @@ -1016,9 +1016,19 @@ function WearScript() { this.group = function () { return WSRAW.group(); } - this.bluetoothList = function (callback) { + this.beacon = function (rangeCb, enterCb, exitCb) { + rangeCb = this._funcfix(rangeCb); + enterCb = this._funcfix(enterCb); + exitCb = this._funcfix(exitCb); + WSRAW.beacon(this._funcwrap(rangeCb), this._funcwrap(enterCb), this._funcwrap(exitCb)); + } + this.bluetoothList = function (callback, btle) { callback = this._funcfix(callback); - WSRAW.bluetoothList(this._funcwrap(function (x) {callback(JSON.parse(x))})); + if(btle) { + WSRAW.bluetoothList(this._funcwrap(callback), true); + } else { + WSRAW.bluetoothList(this._funcwrap(function (x) {callback(JSON.parse(x))}), false); + } } this.bluetoothRead = function (address, callback) { callback = this._funcfix(callback); diff --git a/WearScript/src/main/assets/init.js.min b/WearScript/src/main/assets/init.js.min index 17a3d22a..1fc83db5 100644 --- a/WearScript/src/main/assets/init.js.min +++ b/WearScript/src/main/assets/init.js.min @@ -25,6 +25,7 @@ WSRAW.warpGlassToPreviewH(this._funcwrap(function(c){a(JSON.parse(c))}))};this.w h?(h=this._funcfix(h),WSRAW.cameraOn(a,c,b,!1,this._funcwrap(h))):WSRAW.cameraOn(a,c,b,!1)};this.cameraOnBackgroundUnsafe=function(a,c,b,h){c||(c=0);b||(maxwidth=0);h?(h=this._funcfix(h),WSRAW.cameraOn(a,c,b,!0,this._funcwrap(h))):WSRAW.cameraOn(a,c,b,!0)};this.cameraPhoto=function(a){a?(a=this._funcfix(a),WSRAW.cameraPhoto(this._funcwrap(a))):WSRAW.cameraPhoto()};this.cameraPhotoData=function(a){a?(a=this._funcfix(a),WSRAW.cameraPhotoData(this._funcwrap(a))):WSRAW.cameraPhoto()};this.cameraVideo= function(a){a?(a=this._funcfix(a),WSRAW.cameraVideo(this._funcwrap(a))):WSRAW.cameraVideo()};this.cameraOff=function(){WSRAW.cameraOff()};this.activityCreate=function(){WSRAW.activityCreate()};this.activityDestroy=function(){WSRAW.activityDestroy()};this.wifiOn=function(a){a=this._funcfix(a);WSRAW.wifiOn(this._funcwrap(a))};this.wifiOff=function(){WSRAW.wifiOff()};this.wifiScan=function(){WSRAW.wifiScan()};this.serverConnect=function(a,c){c=this._funcfix(c);WSRAW.serverConnect(a,this._funcwrap(c))}; this.wake=function(){WSRAW.wake()};this.sound=function(a){WSRAW.sound(a)};this.gestureCallback=function(a,c){c=this._funcfix(c);WSRAW.gestureCallback(a,this._funcwrap(c))};this.speechRecognize=function(a,c){c=this._funcfix(c);WSRAW.speechRecognize(a,this._funcwrap(function(a){c(atob(a))}))};this.liveCardCreate=function(a,c){var b=[],h=Array.prototype.slice.call(arguments).slice(2);0==h.length&&(h.push("Open"),h.push(function(){WS.activityCreate()}),h.push("Shutdown"),h.push(function(){WS.shutdown()})); -for(var k=0;k gatts; + + public BluetoothLEManager(BackgroundService bs) { + super(bs); + gatts = new ConcurrentHashMap(); + BluetoothManager bluetoothManager = (BluetoothManager) service.getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = bluetoothManager.getAdapter(); + } + + @Override + public void reset() { + super.reset(); + scanOff(); + } + + @Override + public void shutdown() { + super.shutdown(); + scanOff(); + for(GattConnection connection : gatts.values()){ + connection.close(); + } + gatts.clear(); + } + + + @Override + protected void registerCallback(String type, String jsFunction) { + super.registerCallback(type, jsFunction); + if (type.startsWith(READ)) { + scanOff(); + String address = type.substring(READ.length()); + BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); + if(!gatts.containsKey(address)){ + gatts.put(address, new GattConnection(jsFunction)); + } + device.connectGatt(service, true, gatts.get(address)); + }else if(type.equals(LIST)){ + scanOn(); + } + + } + + private void scanOff() { + if(mBluetoothAdapter != null) + mBluetoothAdapter.stopLeScan(mLeScanCallback); + } + + public void scanOn() { + mBluetoothAdapter.startLeScan(mLeScanCallback); + } + + private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { + @Override + public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) { + makeCall(LIST, bluetoothDevice.getAddress(), bluetoothDevice.getName()); + } + }; + + private class GattConnection extends BluetoothGattCallback { + private static final int STATE_DISCONNECTED = 0; + private static final int STATE_CONNECTING = 1; + private static final int STATE_CONNECTED = 2; + + private final String mCallback; + private int mConnectionState = STATE_DISCONNECTED; + private BluetoothGatt mGatt; + + GattConnection(String callback) { + super(); + mCallback = callback; + } + + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + super.onConnectionStateChange(gatt, status, newState); + mGatt = gatt; + if (newState == BluetoothProfile.STATE_CONNECTED) { + mConnectionState = STATE_CONNECTED; + gatt.discoverServices(); + + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + mConnectionState = STATE_DISCONNECTED; + } + } + + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicRead(gatt, characteristic, status); + mGatt = gatt; + final byte[] data = characteristic.getValue(); + if (data != null && data.length > 0) { + final StringBuilder stringBuilder = new StringBuilder(data.length); + for(byte byteChar : data) + stringBuilder.append(String.format("%02X ", byteChar)); + makeCall(mCallback, stringBuilder.toString()); + } + } + + public void close() { + if(mGatt != null) + mGatt.close(); + } + } + +} \ No newline at end of file diff --git a/WearScript/src/main/java/com/dappervision/wearscript/managers/DataManager.java b/WearScript/src/main/java/com/dappervision/wearscript/managers/DataManager.java index da0ce0eb..b98fff3f 100644 --- a/WearScript/src/main/java/com/dappervision/wearscript/managers/DataManager.java +++ b/WearScript/src/main/java/com/dappervision/wearscript/managers/DataManager.java @@ -9,7 +9,6 @@ import com.dappervision.wearscript.dataproviders.DataPoint; import com.dappervision.wearscript.dataproviders.DataProvider; import com.dappervision.wearscript.dataproviders.GPSDataProvider; -import com.dappervision.wearscript.dataproviders.IBeaconDataProvider; import com.dappervision.wearscript.dataproviders.NativeDataProvider; import com.dappervision.wearscript.dataproviders.PebbleDataProvider; import com.dappervision.wearscript.dataproviders.RemoteDataProvider; @@ -54,8 +53,6 @@ else if (type == WearScript.SENSOR.BATTERY.id()) dp = new BatteryDataProvider(this, samplePeriod); else if (type == WearScript.SENSOR.PEBBLE_ACCELEROMETER.id()) dp = new PebbleDataProvider(this, samplePeriod, type); - else if (type == WearScript.SENSOR.IBEACON.id()) - dp = new IBeaconDataProvider(this, samplePeriod); else throw new RuntimeException("Invalid type: " + type); registerProvider(type, dp); diff --git a/WearScript/src/main/java/com/dappervision/wearscript/managers/IBeaconManager.java b/WearScript/src/main/java/com/dappervision/wearscript/managers/IBeaconManager.java index 3346075a..d6ae880c 100644 --- a/WearScript/src/main/java/com/dappervision/wearscript/managers/IBeaconManager.java +++ b/WearScript/src/main/java/com/dappervision/wearscript/managers/IBeaconManager.java @@ -7,10 +7,6 @@ import com.dappervision.wearscript.BackgroundService; import com.dappervision.wearscript.Log; -import com.dappervision.wearscript.Utils; -import com.dappervision.wearscript.WearScript; -import com.dappervision.wearscript.events.SendEvent; -import com.dappervision.wearscript.events.SensorJSEvent; import com.radiusnetworks.ibeacon.IBeacon; import com.radiusnetworks.ibeacon.IBeaconConsumer; import com.radiusnetworks.ibeacon.RangeNotifier; @@ -18,16 +14,17 @@ import java.util.Collection; -import com.radiusnetworks.ibeacon.IBeaconConsumer; import com.radiusnetworks.ibeacon.MonitorNotifier; -import com.radiusnetworks.ibeacon.Region; public class IBeaconManager extends Manager implements IBeaconConsumer{ - com.radiusnetworks.ibeacon.IBeaconManager iBeaconManager; + public static final String RANGE_NOTIFICATION = "RANGE_NOTIFICATION"; + public static final String ENTER_REGION = "ENTER_REGION"; + public static final String EXIT_REGION = "EXIT_REGION"; + private com.radiusnetworks.ibeacon.IBeaconManager iBeaconManager; - Region MONITOR_REGION = new Region("myMonitoringUniqueId", null, null, null); - Region RANGING_REGION = new Region("myRangingUniqueId", null, null, null); + Region MONITOR_REGION = new Region("everythingRegion", null, null, null); + Region RANGING_REGION = new Region("everythingRegion", null, null, null); public IBeaconManager(BackgroundService bs) { super(bs); @@ -36,22 +33,23 @@ public IBeaconManager(BackgroundService bs) { @Override public void reset() { super.reset(); - ibeaconSensorOff(); - ibeaconSensorOn(); + ibeaconOff(); } @Override public void shutdown() { super.shutdown(); - ibeaconSensorOff(); + ibeaconOff(); } - public void ibeaconSensorOn() { - iBeaconManager = com.radiusnetworks.ibeacon.IBeaconManager.getInstanceForApplication(service); - iBeaconManager.bind(this); + public void ibeaconOn() { + if(iBeaconManager == null) + iBeaconManager = com.radiusnetworks.ibeacon.IBeaconManager.getInstanceForApplication(service); + if(!iBeaconManager.isBound(this)) + iBeaconManager.bind(this); } - public void ibeaconSensorOff() { + public void ibeaconOff() { if(iBeaconManager == null) return; try { @@ -64,15 +62,17 @@ public void ibeaconSensorOff() { iBeaconManager = null; } - public void onEvent(SensorJSEvent event) { - int type = event.getType(); - if(event.getStatus()) { - if (type == WearScript.SENSOR.IBEACON.id()) - ibeaconSensorOn(); - } - else { - ibeaconSensorOff(); - } + @Override + protected void registerCallback(String type, String jsFunction) { + super.registerCallback(type, jsFunction); + ibeaconOn(); + } + + @Override + protected void unregisterCallback(String type) { + super.unregisterCallback(type); + if(jsCallbacks.isEmpty()) + ibeaconOff(); } @Override @@ -82,7 +82,7 @@ public void onIBeaconServiceConnect() { public void didRangeBeaconsInRegion(Collection iBeacons, Region region) { for(IBeacon myBeacon : iBeacons){ if(myBeacon.getAccuracy() >= 0) - Utils.eventBusPost(new SendEvent("ibeacon:"+myBeacon.getProximityUuid(), myBeacon.getProximityUuid(), myBeacon.getRssi(), myBeacon.getMajor(), myBeacon.getMinor())); + makeCall(RANGE_NOTIFICATION, myBeacon.getProximityUuid(), String.valueOf(myBeacon.getRssi()), String.valueOf(myBeacon.getMajor()), String.valueOf(myBeacon.getMinor()), String.valueOf(myBeacon.getProximity())); } } }); @@ -94,14 +94,12 @@ public void didRangeBeaconsInRegion(Collection iBeacons, Region region) iBeaconManager.setMonitorNotifier(new MonitorNotifier() { @Override public void didEnterRegion(Region region) { - Log.i(TAG, "I just saw an iBeacon for the firt time!"); - Utils.eventBusPost(new SendEvent("ibeacon:enter", region.getProximityUuid(), region.getMajor(), region.getMinor())); + makeCall(ENTER_REGION, region.getProximityUuid(), String.valueOf(region.getMajor()), String.valueOf(region.getMinor())); } @Override public void didExitRegion(Region region) { - Log.i(TAG, "I no longer see an iBeacon"); - Utils.eventBusPost(new SendEvent("ibeacon:exit", region.getProximityUuid(), region.getMajor(), region.getMinor())); + makeCall(EXIT_REGION, region.getProximityUuid(), String.valueOf(region.getMajor()), String.valueOf(region.getMinor())); } @Override diff --git a/WearScript/src/main/java/com/dappervision/wearscript/managers/Manager.java b/WearScript/src/main/java/com/dappervision/wearscript/managers/Manager.java index c95d7447..1190873a 100644 --- a/WearScript/src/main/java/com/dappervision/wearscript/managers/Manager.java +++ b/WearScript/src/main/java/com/dappervision/wearscript/managers/Manager.java @@ -1,11 +1,17 @@ package com.dappervision.wearscript.managers; +import android.text.TextUtils; + import com.dappervision.wearscript.BackgroundService; import com.dappervision.wearscript.Log; import com.dappervision.wearscript.Utils; import com.dappervision.wearscript.events.CallbackRegistration; import com.dappervision.wearscript.events.JsCall; +import org.apache.commons.codec.binary.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; public abstract class Manager { @@ -50,7 +56,7 @@ public void shutdown() { Utils.getEventBus().unregister(this); } - protected void makeCall(String key, String data) { + protected void makeCall(String key, String ... data) { Log.d(TAG, jsCallbacks.toString()); if (!jsCallbacks.containsKey(key)) { Log.d(TAG, "Callback not found: " + key); @@ -60,16 +66,22 @@ protected void makeCall(String key, String data) { Utils.eventBusPost(new JsCall(url)); } - protected void makeCallDirect(String callback, String data) { + protected void makeCallDirect(String callback, String ... data) { if (callback == null || data == null) return; - String url = String.format("javascript:%s(%s);", callback, data); + String url = String.format("javascript:%s(%s);", callback, formatParams(data)); Utils.eventBusPost(new JsCall(url)); } - protected String buildCallbackString(String key, String data) { + protected String buildCallbackString(String key, String... data) { if (!jsCallbacks.containsKey(key)) throw new RuntimeException("No such callback registered"); - return String.format("javascript:%s(%s);", jsCallbacks.get(key), data); + return String.format("javascript:%s(%s);", jsCallbacks.get(key), formatParams(data)); + } + + private String formatParams(String[] data){ + if(data.length == 1) + return data[0]; + return "\"" + TextUtils.join("\",\"", data) + "\""; } } diff --git a/WearScript/src/main/java/com/dappervision/wearscript/managers/ManagerManager.java b/WearScript/src/main/java/com/dappervision/wearscript/managers/ManagerManager.java index 2c17f05f..a36c3ba0 100644 --- a/WearScript/src/main/java/com/dappervision/wearscript/managers/ManagerManager.java +++ b/WearScript/src/main/java/com/dappervision/wearscript/managers/ManagerManager.java @@ -1,5 +1,7 @@ package com.dappervision.wearscript.managers; +import android.content.pm.PackageManager; + import com.dappervision.wearscript.BackgroundService; import com.dappervision.wearscript.HardwareDetector; @@ -43,6 +45,9 @@ public void newManagers(BackgroundService bs) { add(new CardTreeManager(bs)); add(new EyeManager(bs)); } + if (bs.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){ + add(new BluetoothLEManager(bs)); + } } public void add(Manager manager) {