package com.mackentoch.beaconsandroid; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.RemoteException; import android.support.annotation.Nullable; import android.util.Log; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.modules.core.DeviceEventManagerModule; import org.altbeacon.beacon.Beacon; import org.altbeacon.beacon.BeaconConsumer; import org.altbeacon.beacon.BeaconManager; import org.altbeacon.beacon.BeaconParser; import org.altbeacon.beacon.BeaconTransmitter; import org.altbeacon.beacon.Identifier; import org.altbeacon.beacon.MonitorNotifier; import org.altbeacon.beacon.RangeNotifier; import org.altbeacon.beacon.Region; import java.util.Collection; import java.util.HashMap; import java.util.Map; public class BeaconsAndroidModule extends ReactContextBaseJavaModule implements BeaconConsumer { private static final String LOG_TAG = "BeaconsAndroidModule"; private ReactApplicationContext mReactContext; private Context mApplicationContext; private BeaconManager mBeaconManager; public BeaconsAndroidModule(ReactApplicationContext reactContext) { super(reactContext); Log.d(LOG_TAG, "BeaconsAndroidModule - started"); this.mReactContext = reactContext; this.mApplicationContext = reactContext.getApplicationContext(); this.mBeaconManager = BeaconManager.getInstanceForApplication(mApplicationContext); // Detect iBeacons ( http://stackoverflow.com/questions/25027983/is-this-the-correct-layout-to-detect-ibeacons-with-altbeacons-android-beacon-li ) addParser("m:0-3=4c000215,i:4-19,i:20-21,i:22-23,p:24-24"); mBeaconManager.bind(this); } @Override public String getName() { return LOG_TAG; } @Override public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put("SUPPORTED", BeaconTransmitter.SUPPORTED); constants.put("NOT_SUPPORTED_MIN_SDK", BeaconTransmitter.NOT_SUPPORTED_MIN_SDK); constants.put("NOT_SUPPORTED_BLE", BeaconTransmitter.NOT_SUPPORTED_BLE); constants.put("NOT_SUPPORTED_CANNOT_GET_ADVERTISER_MULTIPLE_ADVERTISEMENTS", BeaconTransmitter.NOT_SUPPORTED_CANNOT_GET_ADVERTISER_MULTIPLE_ADVERTISEMENTS); constants.put("NOT_SUPPORTED_CANNOT_GET_ADVERTISER", BeaconTransmitter.NOT_SUPPORTED_CANNOT_GET_ADVERTISER); return constants; } @ReactMethod public void setHardwareEqualityEnforced(Boolean e) { Beacon.setHardwareEqualityEnforced(e.booleanValue()); } @ReactMethod public void addParser(String parser) { mBeaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout(parser)); } @ReactMethod public void removeParser(String parser) { mBeaconManager.getBeaconParsers().remove(new BeaconParser().setBeaconLayout(parser)); } @ReactMethod public void setBackgroundScanPeriod(int period) { mBeaconManager.setBackgroundScanPeriod((long) period); } @ReactMethod public void setBackgroundBetweenScanPeriod(int period) { mBeaconManager.setBackgroundBetweenScanPeriod((long) period); } @ReactMethod public void setForegroundScanPeriod(int period) { mBeaconManager.setForegroundScanPeriod((long) period); } @ReactMethod public void setForegroundBetweenScanPeriod(int period) { mBeaconManager.setForegroundBetweenScanPeriod((long) period); } @ReactMethod public void checkTransmissionSupported(Callback callback) { int result = BeaconTransmitter.checkTransmissionSupported(mReactContext); callback.invoke(result); } @ReactMethod public void getMonitoredRegions(Callback callback) { WritableArray array = new WritableNativeArray(); for (Region region: mBeaconManager.getMonitoredRegions()) { WritableMap map = new WritableNativeMap(); map.putString("identifier", region.getUniqueId()); map.putString("uuid", region.getId1().toString()); map.putInt("major", region.getId2() != null ? region.getId2().toInt() : 0); map.putInt("minor", region.getId3() != null ? region.getId3().toInt() : 0); array.pushMap(map); } callback.invoke(array); } @ReactMethod public void getRangedRegions(Callback callback) { WritableArray array = new WritableNativeArray(); for (Region region: mBeaconManager.getRangedRegions()) { WritableMap map = new WritableNativeMap(); map.putString("region", region.getUniqueId()); map.putString("uuid", region.getId1().toString()); array.pushMap(map); } callback.invoke(array); } /*********************************************************************************************** * BeaconConsumer **********************************************************************************************/ @Override public void onBeaconServiceConnect() { Log.v(LOG_TAG, "onBeaconServiceConnect"); // deprecated since v2.9 (see github: https://github.com/AltBeacon/android-beacon-library/releases/tag/2.9) // mBeaconManager.setMonitorNotifier(mMonitorNotifier); // mBeaconManager.setRangeNotifier(mRangeNotifier); mBeaconManager.addMonitorNotifier(mMonitorNotifier); mBeaconManager.addRangeNotifier(mRangeNotifier); } @Override public Context getApplicationContext() { return mApplicationContext; } @Override public void unbindService(ServiceConnection serviceConnection) { mApplicationContext.unbindService(serviceConnection); } @Override public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) { return mApplicationContext.bindService(intent, serviceConnection, i); } /*********************************************************************************************** * Monitoring **********************************************************************************************/ @ReactMethod public void startMonitoring(String regionId, String beaconUuid, int minor, int major, Callback resolve, Callback reject) { Log.d(LOG_TAG, "startMonitoring, monitoringRegionId: " + regionId + ", monitoringBeaconUuid: " + beaconUuid + ", minor: " + minor + ", major: " + major); try { Region region = createRegion( regionId, beaconUuid, String.valueOf(minor).equals("-1") ? "" : String.valueOf(minor), String.valueOf(major).equals("-1") ? "" : String.valueOf(major) ); mBeaconManager.startMonitoringBeaconsInRegion(region); resolve.invoke(); } catch (Exception e) { Log.e(LOG_TAG, "startMonitoring, error: ", e); reject.invoke(e.getMessage()); } } private MonitorNotifier mMonitorNotifier = new MonitorNotifier() { @Override public void didEnterRegion(Region region) { sendEvent(mReactContext, "regionDidEnter", createMonitoringResponse(region)); } @Override public void didExitRegion(Region region) { sendEvent(mReactContext, "regionDidExit", createMonitoringResponse(region)); } @Override public void didDetermineStateForRegion(int i, Region region) { } }; private WritableMap createMonitoringResponse(Region region) { WritableMap map = new WritableNativeMap(); map.putString("identifier", region.getUniqueId()); map.putString("uuid", region.getId1().toString()); map.putInt("major", region.getId2() != null ? region.getId2().toInt() : 0); map.putInt("minor", region.getId3() != null ? region.getId3().toInt() : 0); return map; } @ReactMethod public void stopMonitoring(String regionId, String beaconUuid, int minor, int major, Callback resolve, Callback reject) { Region region = createRegion( regionId, beaconUuid, String.valueOf(minor).equals("-1") ? "" : String.valueOf(minor), String.valueOf(major).equals("-1") ? "" : String.valueOf(major) // minor, // major ); try { mBeaconManager.stopMonitoringBeaconsInRegion(region); resolve.invoke(); } catch (Exception e) { Log.e(LOG_TAG, "stopMonitoring, error: ", e); reject.invoke(e.getMessage()); } } /*********************************************************************************************** * Ranging **********************************************************************************************/ @ReactMethod public void startRanging(String regionId, String beaconUuid, Callback resolve, Callback reject) { Log.d(LOG_TAG, "startRanging, rangingRegionId: " + regionId + ", rangingBeaconUuid: " + beaconUuid); try { Region region = createRegion(regionId, beaconUuid); mBeaconManager.startRangingBeaconsInRegion(region); resolve.invoke(); } catch (Exception e) { Log.e(LOG_TAG, "startRanging, error: ", e); reject.invoke(e.getMessage()); } } private RangeNotifier mRangeNotifier = new RangeNotifier() { @Override public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) { Log.d(LOG_TAG, "rangingConsumer didRangeBeaconsInRegion, beacons: " + beacons.toString()); Log.d(LOG_TAG, "rangingConsumer didRangeBeaconsInRegion, region: " + region.toString()); sendEvent(mReactContext, "beaconsDidRange", createRangingResponse(beacons, region)); } }; private WritableMap createRangingResponse(Collection<Beacon> beacons, Region region) { WritableMap map = new WritableNativeMap(); map.putString("identifier", region.getUniqueId()); map.putString("uuid", region.getId1() != null ? region.getId1().toString() : ""); WritableArray a = new WritableNativeArray(); for (Beacon beacon : beacons) { WritableMap b = new WritableNativeMap(); b.putString("uuid", beacon.getId1().toString()); b.putInt("major", beacon.getId2().toInt()); b.putInt("minor", beacon.getId3().toInt()); b.putInt("rssi", beacon.getRssi()); b.putDouble("distance", beacon.getDistance()); b.putString("proximity", getProximity(beacon.getDistance())); a.pushMap(b); } map.putArray("beacons", a); return map; } private String getProximity(double distance) { if (distance == -1.0) { return "unknown"; } else if (distance < 1) { return "immediate"; } else if (distance < 3) { return "near"; } else { return "far"; } } @ReactMethod public void stopRanging(String regionId, String beaconUuid, Callback resolve, Callback reject) { Region region = createRegion(regionId, beaconUuid); try { mBeaconManager.stopRangingBeaconsInRegion(region); resolve.invoke(); } catch (Exception e) { Log.e(LOG_TAG, "stopRanging, error: ", e); reject.invoke(e.getMessage()); } } /*********************************************************************************************** * Utils **********************************************************************************************/ private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, params); } private Region createRegion(String regionId, String beaconUuid) { Identifier id1 = (beaconUuid == null) ? null : Identifier.parse(beaconUuid); return new Region(regionId, id1, null, null); } private Region createRegion(String regionId, String beaconUuid, String minor, String major) { Identifier id1 = (beaconUuid == null) ? null : Identifier.parse(beaconUuid); return new Region( regionId, id1, major.length() > 0 ? Identifier.parse(major) : null, minor.length() > 0 ? Identifier.parse(minor) : null ); } }