fixes for android sense client

revert-dabc3590
Chatura Dilan 9 years ago
parent e05906fed5
commit 971185decf

@ -11,6 +11,12 @@ android {
versionName "1.0" versionName "1.0"
} }
repositories {
maven {
url "https://dl.bintray.com/alt236/maven"
}
}
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
@ -53,5 +59,7 @@ dependencies {
compile 'com.netflix.feign:feign-jaxrs:8.16.0' compile 'com.netflix.feign:feign-jaxrs:8.16.0'
compile 'com.netflix.feign:feign-jackson:8.16.0' compile 'com.netflix.feign:feign-jackson:8.16.0'
compile 'org.altbeacon:android-beacon-library:2.8.1' compile 'org.altbeacon:android-beacon-library:2.8.1'
compile 'uk.co.alt236:easycursor-android:1.0.0'
compile 'uk.co.alt236:bluetooth-le-library-android:1.0.0'
} }

@ -78,6 +78,12 @@
android:label="Beacon Monitor" android:label="Beacon Monitor"
android:theme="@style/AppTheme.NoActionBar" > android:theme="@style/AppTheme.NoActionBar" >
</activity> </activity>
<activity
android:name="org.wso2.carbon.iot.android.sense.bmonitor.BeaconMonitoringActivity"
android:label="Beacon Monitor"
android:theme="@style/AppTheme.NoActionBar" >
</activity>
</application> </application>
</manifest> </manifest>

@ -45,20 +45,29 @@ public class BeaconDetactorService extends Service implements BeaconConsumer {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(){
@Override
public void run() {
iBeaconManager.bind(BeaconDetactorService.this);
}
}.start();
return START_STICKY; return START_STICKY;
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
iBeaconManager.bind(this);
final Handler handler = new Handler(); final Handler handler = new Handler();
final Runnable runnable = new Runnable() { final Runnable runnable = new Runnable() {
@Override @Override
public void run() { public void run() {
stopSelf(); //stopSelf();
} }
}; };
handler.postDelayed(runnable, 10000); handler.postDelayed(runnable, 10000);

@ -0,0 +1,121 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;
import java.util.HashMap;
import java.util.Map;
import agent.sense.android.iot.carbon.wso2.org.wso2_senseagent.R;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
public class BeaconMonitoringActivity extends AppCompatActivity {
private BluetoothLeScanner mScanner;
private BluetoothUtils mBluetoothUtils;
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothLeDeviceStore mDeviceStore;
protected ListView mList;
public static final int MINIMUM_DISTANCE = -70;
public static Map itemMap;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_beacon_monitoring);
itemMap = new HashMap<String, String>();
itemMap.put("DC:5F:BD:68:88:D5", "Noodles");
itemMap.put("EF:0F:50:D5:BA:A1", "Vegetables");
itemMap.put("FA:F2:CF:84:C2:F7", "Oil");
mList = (ListView) this.findViewById(android.R.id.list);
mDeviceStore = new BluetoothLeDeviceStore();
mBluetoothUtils = new BluetoothUtils(this);
mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
startScan();
}
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
if(deviceLe!= null && deviceLe.getName()!= null && !deviceLe.getName().equals("Unknown Device")){
mDeviceStore.addDevice(deviceLe);
if(deviceLe.getRssi() > MINIMUM_DISTANCE){
Object[] objects = new Object[4];
objects[0] = deviceLe.getName();
objects[1] = deviceLe.getAddress();
objects[2] = deviceLe.getRssi();
objects[3] = itemMap.get(device.getAddress());
new SendToSiddi().execute(objects);
}
final EasyObjectCursor<BluetoothLeDevice> c = mDeviceStore.getDeviceCursor();
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.swapCursor(c);
}
});
}
}
};
private void startScan() {
mLeDeviceListAdapter = new LeDeviceListAdapter(this, mDeviceStore.getDeviceCursor());
mList.setAdapter(mLeDeviceListAdapter);
final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
if (mIsBluetoothOn && mIsBluetoothLePresent) {
mScanner.scanLeDevice(-1, true);
invalidateOptionsMenu();
}
}
public class SendToSiddi extends AsyncTask<Object, Object, Void>{
@Override
protected Void doInBackground(Object... objects) {
return null;
}
}
}

@ -0,0 +1,193 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.bluetoothlelib.util.ByteUtils;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
public class BluetoothLeDeviceStore {
private final Map<String, BluetoothLeDevice> mDeviceMap;
public BluetoothLeDeviceStore() {
mDeviceMap = new HashMap<>();
}
public void addDevice(final BluetoothLeDevice device) {
if (mDeviceMap.containsKey(device.getAddress())) {
mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi());
} else {
mDeviceMap.put(device.getAddress(), device);
}
}
public void clear() {
mDeviceMap.clear();
}
public EasyObjectCursor<BluetoothLeDevice> getDeviceCursor() {
return new EasyObjectCursor<>(
BluetoothLeDevice.class,
getDeviceList(),
"address");
}
public List<BluetoothLeDevice> getDeviceList() {
final List<BluetoothLeDevice> methodResult = new ArrayList<>(mDeviceMap.values());
Collections.sort(methodResult, new Comparator<BluetoothLeDevice>() {
@Override
public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) {
return arg0.getAddress().compareToIgnoreCase(arg1.getAddress());
}
});
return methodResult;
}
private String getListAsCsv() {
final List<BluetoothLeDevice> list = getDeviceList();
final StringBuilder sb = new StringBuilder();
sb.append(CsvWriterHelper.addStuff("mac"));
sb.append(CsvWriterHelper.addStuff("name"));
sb.append(CsvWriterHelper.addStuff("firstTimestamp"));
sb.append(CsvWriterHelper.addStuff("firstRssi"));
sb.append(CsvWriterHelper.addStuff("currentTimestamp"));
sb.append(CsvWriterHelper.addStuff("currentRssi"));
sb.append(CsvWriterHelper.addStuff("adRecord"));
sb.append(CsvWriterHelper.addStuff("iBeacon"));
sb.append(CsvWriterHelper.addStuff("uuid"));
sb.append(CsvWriterHelper.addStuff("major"));
sb.append(CsvWriterHelper.addStuff("minor"));
sb.append(CsvWriterHelper.addStuff("txPower"));
sb.append(CsvWriterHelper.addStuff("distance"));
sb.append(CsvWriterHelper.addStuff("accuracy"));
sb.append('\n');
for (final BluetoothLeDevice device : list) {
sb.append(CsvWriterHelper.addStuff(device.getAddress()));
sb.append(CsvWriterHelper.addStuff(device.getName()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getFirstTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getFirstRssi()));
sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getTimestamp())));
sb.append(CsvWriterHelper.addStuff(device.getRssi()));
sb.append(CsvWriterHelper.addStuff(ByteUtils.byteArrayToHexString(device.getScanRecord())));
final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON;
final String uuid;
final String minor;
final String major;
final String txPower;
final String distance;
final String accuracy;
if (isIBeacon) {
final IBeaconDevice beacon = new IBeaconDevice(device);
uuid = String.valueOf(beacon.getUUID());
minor = String.valueOf(beacon.getMinor());
major = String.valueOf(beacon.getMajor());
txPower = String.valueOf(beacon.getCalibratedTxPower());
distance = beacon.getDistanceDescriptor().toString().toLowerCase(Locale.US);
accuracy = String.valueOf(beacon.getAccuracy());
} else {
uuid = "";
minor = "";
major = "";
txPower = "";
distance = "";
accuracy = "";
}
sb.append(CsvWriterHelper.addStuff(isIBeacon));
sb.append(CsvWriterHelper.addStuff(uuid));
sb.append(CsvWriterHelper.addStuff(minor));
sb.append(CsvWriterHelper.addStuff(major));
sb.append(CsvWriterHelper.addStuff(txPower));
sb.append(CsvWriterHelper.addStuff(distance));
sb.append(CsvWriterHelper.addStuff(accuracy));
sb.append('\n');
}
return sb.toString();
}
public void shareDataAsEmail(final Context context) {
final long timeInMillis = System.currentTimeMillis();
final String to = null;
final String subject = "";
final String message = "";
final Intent i = new Intent(Intent.ACTION_SEND);
i.setType("plain/text");
try {
final File outputDir = context.getCacheDir();
final File outputFile = File.createTempFile("bluetooth_le_" + timeInMillis, ".csv", outputDir);
outputFile.setReadable(true, false);
generateFile(outputFile, getListAsCsv());
i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(outputFile));
i.putExtra(Intent.EXTRA_EMAIL, new String[]{to});
i.putExtra(Intent.EXTRA_SUBJECT, subject);
i.putExtra(Intent.EXTRA_TEXT, message);
context.startActivity(Intent.createChooser(i, "Email Subject"));
} catch (final IOException e) {
e.printStackTrace();
}
}
private static FileWriter generateFile(final File file, final String contents) {
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.append(contents);
writer.flush();
} catch (final IOException e) {
e.printStackTrace();
} finally {
try {
writer.close();
} catch (final IOException e) {
e.printStackTrace();
}
}
return writer;
}
}

@ -0,0 +1,65 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
import android.bluetooth.BluetoothAdapter;
import android.os.Handler;
import android.util.Log;
public class BluetoothLeScanner {
private final Handler mHandler;
private final BluetoothAdapter.LeScanCallback mLeScanCallback;
private final BluetoothUtils mBluetoothUtils;
private boolean mScanning;
public BluetoothLeScanner(final BluetoothAdapter.LeScanCallback leScanCallback, final BluetoothUtils bluetoothUtils) {
mHandler = new Handler();
mLeScanCallback = leScanCallback;
mBluetoothUtils = bluetoothUtils;
}
public boolean isScanning() {
return mScanning;
}
public void scanLeDevice(final int duration, final boolean enable) {
if (enable) {
if (mScanning) {
return;
}
Log.d("TAG", "~ Starting Scan");
// Stops scanning after a pre-defined scan period.
if (duration > 0) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d("TAG", "~ Stopping Scan (timeout)");
mScanning = false;
mBluetoothUtils.getBluetoothAdapter().stopLeScan(mLeScanCallback);
}
}, duration);
}
mScanning = true;
mBluetoothUtils.getBluetoothAdapter().startLeScan(mLeScanCallback);
} else {
Log.d("TAG", "~ Stopping Scan");
mScanning = false;
mBluetoothUtils.getBluetoothAdapter().stopLeScan(mLeScanCallback);
}
}
}

@ -0,0 +1,60 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
public final class BluetoothUtils {
public final static int REQUEST_ENABLE_BT = 2001;
private final Activity mActivity;
private final BluetoothAdapter mBluetoothAdapter;
public BluetoothUtils(final Activity activity) {
mActivity = activity;
final BluetoothManager btManager = (BluetoothManager) mActivity.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = btManager.getAdapter();
}
public void askUserToEnableBluetoothIfNeeded() {
if (isBluetoothLeSupported() && (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled())) {
final Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
mActivity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
public BluetoothAdapter getBluetoothAdapter() {
return mBluetoothAdapter;
}
public boolean isBluetoothLeSupported() {
return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
public boolean isBluetoothOn() {
if (mBluetoothAdapter == null) {
return false;
} else {
return mBluetoothAdapter.isEnabled();
}
}
}

@ -0,0 +1,43 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
public class CsvWriterHelper {
private static final String QUOTE = "\"";
public static String addStuff(final Integer text) {
return QUOTE + text + QUOTE + ",";
}
public static String addStuff(final Long text) {
return QUOTE + text + QUOTE + ",";
}
public static String addStuff(final boolean value) {
return QUOTE + value + QUOTE + ",";
}
public static String addStuff(String text) {
if (text == null) {
text = "<blank>";
}
text = text.replace(QUOTE, "'");
return QUOTE + text.trim() + QUOTE + ",";
}
}

@ -0,0 +1,154 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
import android.app.Activity;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.text.DecimalFormat;
import agent.sense.android.iot.carbon.wso2.org.wso2_senseagent.R;
import uk.co.alt236.bluetoothlelib.device.BluetoothLeDevice;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconType;
import uk.co.alt236.bluetoothlelib.device.beacon.BeaconUtils;
import uk.co.alt236.bluetoothlelib.device.beacon.ibeacon.IBeaconDevice;
import uk.co.alt236.easycursor.objectcursor.EasyObjectCursor;
public class LeDeviceListAdapter extends SimpleCursorAdapter {
private final LayoutInflater mInflator;
private final Activity mActivity;
public static final DecimalFormat DOUBLE_TWO_DIGIT_ACCURACY = new DecimalFormat("#.##");
public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public LeDeviceListAdapter(final Activity activity, final EasyObjectCursor<BluetoothLeDevice> cursor) {
super(activity, R.layout.list_item_device, cursor, new String[0], new int[0], 0);
mInflator = activity.getLayoutInflater();
mActivity = activity;
}
@SuppressWarnings("unchecked")
@Override
public EasyObjectCursor<BluetoothLeDevice> getCursor() {
return ((EasyObjectCursor<BluetoothLeDevice>) super.getCursor());
}
@Override
public BluetoothLeDevice getItem(final int i) {
return getCursor().getItem(i);
}
@Override
public long getItemId(final int i) {
return i;
}
@Override
public View getView(final int i, View view, final ViewGroup viewGroup) {
final ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.list_item_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
viewHolder.deviceRssi = (TextView) view.findViewById(R.id.device_rssi);
viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon);
viewHolder.deviceLastUpdated = (TextView) view.findViewById(R.id.device_last_update);
viewHolder.ibeaconMajor = (TextView) view.findViewById(R.id.ibeacon_major);
viewHolder.ibeaconMinor = (TextView) view.findViewById(R.id.ibeacon_minor);
viewHolder.ibeaconDistance = (TextView) view.findViewById(R.id.ibeacon_distance);
viewHolder.ibeaconUUID = (TextView) view.findViewById(R.id.ibeacon_uuid);
viewHolder.ibeaconTxPower = (TextView) view.findViewById(R.id.ibeacon_tx_power);
viewHolder.ibeaconSection = view.findViewById(R.id.ibeacon_section);
viewHolder.ibeaconDistanceDescriptor = (TextView) view.findViewById(R.id.ibeacon_distance_descriptor);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
final BluetoothLeDevice device = getCursor().getItem(i);
final String deviceName = device.getName();
final double rssi = device.getRssi();
if (deviceName != null && deviceName.length() > 0) {
viewHolder.deviceName.setText(deviceName + " (" + BeaconMonitoringActivity.itemMap.get(device.getAddress()) + ")");
} else {
viewHolder.deviceName.setText(R.string.unknown_device);
}
if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) {
final IBeaconDevice iBeacon = new IBeaconDevice(device);
final String accuracy = DOUBLE_TWO_DIGIT_ACCURACY.format(iBeacon.getAccuracy());
viewHolder.deviceIcon.setImageResource(R.drawable.beacon);
viewHolder.ibeaconSection.setVisibility(View.VISIBLE);
viewHolder.ibeaconMajor.setText(String.valueOf(iBeacon.getMajor()));
viewHolder.ibeaconMinor.setText(String.valueOf(iBeacon.getMinor()));
viewHolder.ibeaconTxPower.setText(String.valueOf(iBeacon.getCalibratedTxPower()));
viewHolder.ibeaconUUID.setText(iBeacon.getUUID());
viewHolder.ibeaconDistance.setText(
mActivity.getString(R.string.formatter_meters, accuracy));
viewHolder.ibeaconDistanceDescriptor.setText(iBeacon.getDistanceDescriptor().toString());
} else {
if(device.getRssi() > BeaconMonitoringActivity.MINIMUM_DISTANCE){
viewHolder.deviceIcon.setImageResource(R.drawable.beacon_red);
}else{
viewHolder.deviceIcon.setImageResource(R.drawable.beacon);
}
viewHolder.ibeaconSection.setVisibility(View.GONE);
}
final String rssiString =
mActivity.getString(R.string.formatter_db, String.valueOf(rssi));
final String runningAverageRssiString =
mActivity.getString(R.string.formatter_db, String.valueOf(device.getRunningAverageRssi()));
viewHolder.deviceLastUpdated.setText(
android.text.format.DateFormat.format(
TIME_FORMAT, new java.util.Date(device.getTimestamp())));
viewHolder.deviceAddress.setText(device.getAddress());
viewHolder.deviceRssi.setText(rssiString + " / " + runningAverageRssiString);
return view;
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
TextView deviceRssi;
TextView ibeaconUUID;
TextView ibeaconMajor;
TextView ibeaconMinor;
TextView ibeaconTxPower;
TextView ibeaconDistance;
TextView ibeaconDistanceDescriptor;
TextView deviceLastUpdated;
View ibeaconSection;
ImageView deviceIcon;
}
}

@ -0,0 +1,35 @@
/*
*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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 org.wso2.carbon.iot.android.sense.bmonitor;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class TimeFormatter {
private final static String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS zzz";
private final static SimpleDateFormat ISO_FORMATTER = new UtcDateFormatter(ISO_FORMAT, Locale.US);
public static String getIsoDateTime(final Date date) {
return ISO_FORMATTER.format(date);
}
public static String getIsoDateTime(final long millis) {
return getIsoDateTime(new Date(millis));
}
}

@ -0,0 +1,74 @@
/**
* ****************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
* <p/>
* GenieConnect Ltd. ("COMPANY") CONFIDENTIAL
* Unpublished Copyright (c) 2010-2013 GenieConnect Ltd., All Rights Reserved.
* <p/>
* NOTICE:
* All information contained herein is, and remains the property of COMPANY.
* The intellectual and technical concepts contained herein are proprietary to
* COMPANY and may be covered by U.S. and Foreign Patents, patents in process, and
* are protected by trade secret or copyright law. Dissemination of this
* information or reproduction of this material is strictly forbidden unless prior
* written permission is obtained from COMPANY. Access to the source code
* contained herein is hereby forbidden to anyone except current COMPANY employees,
* managers or contractors who have executed Confidentiality and Non-disclosure
* agreements explicitly covering such access.
* <p/>
* The copyright notice above does not evidence any actual or intended publication
* or disclosure of this source code, which includes information that is
* confidential and/or proprietary, and is a trade secret, of COMPANY.
* <p/>
* ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC
* DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS WRITTEN
* CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE LAWS
* AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE CODE
* AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE,
* DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR SELL ANYTHING
* THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
* ****************************************************************************
*/
package org.wso2.carbon.iot.android.sense.bmonitor;
import android.annotation.SuppressLint;
import java.text.DateFormatSymbols;
import java.util.Locale;
import java.util.TimeZone;
public class UtcDateFormatter extends java.text.SimpleDateFormat {
private static final long serialVersionUID = 1L;
private static final String TIME_ZONE_STRING = "UTC";
private static final TimeZone TIME_ZONE_UTC = TimeZone.getTimeZone(TIME_ZONE_STRING);
@SuppressLint("SimpleDateFormat")
public UtcDateFormatter(final String template) {
super(template);
super.setTimeZone(TIME_ZONE_UTC);
}
@SuppressLint("SimpleDateFormat")
public UtcDateFormatter(final String template, final DateFormatSymbols symbols) {
super(template, symbols);
super.setTimeZone(TIME_ZONE_UTC);
}
public UtcDateFormatter(final String template, final Locale locale) {
super(template, locale);
super.setTimeZone(TIME_ZONE_UTC);
}
/*
* This function will throw an UnsupportedOperationException.
* You are not be able to change the TimeZone of this object
*
* (non-Javadoc)
* @see java.text.DateFormat#setTimeZone(java.util.TimeZone)
*/
@Override
public void setTimeZone(final TimeZone timezone) {
throw new UnsupportedOperationException("This SimpleDateFormat can only be in " + TIME_ZONE_STRING);
}
}

@ -38,6 +38,7 @@ import android.widget.Toast;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import org.wso2.carbon.iot.android.sense.RegisterActivity; import org.wso2.carbon.iot.android.sense.RegisterActivity;
import org.wso2.carbon.iot.android.sense.bmonitor.BeaconMonitoringActivity;
import org.wso2.carbon.iot.android.sense.data.publisher.DataPublisherReceiver; import org.wso2.carbon.iot.android.sense.data.publisher.DataPublisherReceiver;
import org.wso2.carbon.iot.android.sense.data.publisher.DataPublisherService; import org.wso2.carbon.iot.android.sense.data.publisher.DataPublisherService;
import org.wso2.carbon.iot.android.sense.event.SenseScheduleReceiver; import org.wso2.carbon.iot.android.sense.event.SenseScheduleReceiver;
@ -146,7 +147,7 @@ public class ActivitySelectSensor extends AppCompatActivity
fbtnBeaconMonitor.setOnClickListener(new View.OnClickListener() { fbtnBeaconMonitor.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MonitoringActivity.class); Intent intent = new Intent(getApplicationContext(), BeaconMonitoringActivity.class);
startActivity(intent); startActivity(intent);

@ -1,55 +0,0 @@
/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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 org.wso2.carbon.iot.android.sense.siddhi.dto;
public class BLE {
int id;
long timeStamp;
String location;
public BLE(int id, String location){
this.id = id;
this.location = location;
timeStamp = System.currentTimeMillis();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}

@ -1,136 +0,0 @@
/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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 org.wso2.carbon.iot.android.sense.siddhi.eventprocessor.core;
import org.wso2.carbon.iot.android.sense.siddhi.dto.BLE;
import org.wso2.carbon.iot.android.sense.siddhi.eventprocessor.wrapper.SidhdhiWrapper;
import org.wso2.siddhi.core.SiddhiManager;
import org.wso2.siddhi.core.event.Event;
import org.wso2.siddhi.core.stream.input.InputHandler;
import org.wso2.siddhi.core.ExecutionPlanRuntime;
import org.wso2.siddhi.core.stream.output.StreamCallback;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SidhdhiQueryExecutor implements Runnable {
protected String query = null;
private static SiddhiManager siddhiManager = new SiddhiManager();
private static final int INTERVAL = 3000;
public volatile Thread sidhdiThread;
private ReadWriteLock rwlock = new ReentrantReadWriteLock();
//TODO have another array initialized so that a list of callbacks can be stored, String[] callbackList
public SidhdhiQueryExecutor(String query){
this.query = query;
}
public void run(){
//TODO the array of callbacks needs to be passed to invoke
//TODO what is retruned should be a map of callbacks and outputs
ExecutionPlan executionPlan = new ExecutionPlan().invoke();
Thread thisThread = Thread.currentThread();
while (sidhdiThread == thisThread) {
InputHandler inputHandler = executionPlan.getInputHandler();
//Sending events to Siddhi
try {
List<BLE> bleReadings = read();
for(BLE ble : bleReadings){
System.out.println("Publishing data...");
inputHandler.send(new Object[]{ble.getId(), ble.getTimeStamp(), ble.getLocation()});
}
thisThread.sleep(INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
public List<BLE> read()
{
List<BLE> bleData;
rwlock.readLock().lock();
try {
//TODO Reading BLE VALUES
bleData = SidhdhiWrapper.getBleData();
} finally {
rwlock.readLock().unlock();
}
return bleData;
}
public void stop(){
sidhdiThread = null;
}
public void start(){
sidhdiThread = new Thread(this);
sidhdiThread.start();
}
private class ExecutionPlan {
private InputHandler inputHandler;
public InputHandler getInputHandler() {
return inputHandler;
}
//TODO should expect an array of callbacks
public ExecutionPlan invoke() {
//Generating runtime
ExecutionPlanRuntime runtime = siddhiManager.createExecutionPlanRuntime(query);
//TODO logic needs to be revised so that array of callbacks are processed
runtime.addCallback("dataOut", new StreamCallback() {
@Override
public void receive(Event[] events) {
System.out.println("Location Match event initiated.");
if (events.length > 0) {
//TODO Configure Event here!
System.out.println("Firing location match event...");
}
}
});
//Retrieving InputHandler to push events into Siddhi
inputHandler = runtime.getInputHandler("dataIn");
//Starting event processing
runtime.start();
System.out.println("Execution Plan Started!");
return this;
}
}
}

@ -1,59 +0,0 @@
/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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 org.wso2.carbon.iot.android.sense.siddhi.eventprocessor.wrapper;
import org.wso2.carbon.iot.android.sense.siddhi.dto.BLE;
import org.wso2.carbon.iot.android.sense.siddhi.eventprocessor.core.SidhdhiQueryExecutor;
import org.wso2.carbon.iot.android.sense.siddhi.reader.BLEReader;
import java.util.List;
import java.util.Map;
public class SidhdhiWrapper {
private static List<BLE> bleData;
public static List<BLE> getBleData() {
return bleData;
}
public static void setBleData(List<BLE> bleData) {
SidhdhiWrapper.bleData = bleData;
}
public static void main(String args[]){
String query = "@Import('iot.sample.input:1.0.0')\n" +
"define stream dataIn (id int, timestamp long, location string);\n" +
"\n" +
"@Export('iot.sample.output:1.0.0')\n" +
"define stream dataOut (action string, timestamp long);\n" +
"\n" +
"from every e1=dataIn[location=='loc_1'] -> e2=dataIn[location=='loc_2'] -> e3=dataIn[location=='loc_3']\n" +
"select 'x' as action, e3.timestamp\n" +
"insert into dataOut;";
BLEReader blEReader = new BLEReader();
blEReader.start();
SidhdhiQueryExecutor queryExecutor = new SidhdhiQueryExecutor(query);
queryExecutor.start();
}
}

@ -1,71 +0,0 @@
/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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 org.wso2.carbon.iot.android.sense.siddhi.reader;
import org.wso2.carbon.iot.android.sense.siddhi.dto.BLE;
import org.wso2.carbon.iot.android.sense.siddhi.eventprocessor.wrapper.SidhdhiWrapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class BLEReader implements Runnable{
private ReadWriteLock rwlock = new ReentrantReadWriteLock();
List<BLE> bleData = new ArrayList<BLE>();
public volatile Thread bleReader;
private static final int INTERVAL = 2000;
public void run(){
Thread thisThread = Thread.currentThread();
while (bleReader == thisThread) {
write();
try {
thisThread.sleep(INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stop(){
bleReader = null;
}
public void start(){
bleReader = new Thread(this);
bleReader.start();
}
public void write()
{
rwlock.writeLock().lock();
try {
bleData.add(new BLE(123, "loc_1"));
bleData.add(new BLE(123, "loc_2"));
bleData.add(new BLE(123, "loc_3"));
SidhdhiWrapper.setBleData(bleData);
} finally {
rwlock.writeLock().unlock();
}
}
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/listContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:orientation="vertical">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical|center_horizontal"
android:text="@string/no_data"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:orientation="horizontal">
<ImageView
android:id="@+id/device_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:src="@drawable/beacon"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"/>
<GridLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnCount="2">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_mac"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:typeface="monospace"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_updated"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_last_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_rssi"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/device_rssi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
</GridLayout>
<GridLayout
android:id="@+id/ibeacon_section"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnCount="4">
<!-- ROW 1 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_uuid"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/ibeacon_uuid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="3"
android:paddingRight="5dp"
android:textSize="12sp"/>
<!-- ROW 2 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_major"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/ibeacon_major"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/label_minor"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/ibeacon_minor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<!-- ROW 3 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_tx_power"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/ibeacon_tx_power"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/label_distance"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/ibeacon_distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textSize="12sp"/>
<!-- ROW 4 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="@string/label_decriptor"
android:textSize="12sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/ibeacon_distance_descriptor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="3"
android:paddingRight="5dp"
android:textSize="12sp"/>
</GridLayout>
</LinearLayout>
</LinearLayout>

@ -11,4 +11,75 @@
<!-- TODO: Remove or change this placeholder text --> <!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string> <string name="hello_blank_fragment">Hello blank fragment</string>
<!-- Labels -->
<string name="about_dialog_text">This is a sample application using the Bluetooth LE Library.\n\nGithub: https://github.com/alt236/Bluetooth-LE-Library---Android\n\nCopyright: Alexandros Schillings</string>
<string name="header_device_info">Device Info</string>
<string name="header_ibeacon_data">iBeacon Data</string>
<string name="header_raw_ad_records">Raw Ad Records</string>
<string name="header_rssi_info">RSSI Info</string>
<string name="header_scan_record">Scan Record</string>
<string name="label_advertisement">Advertisement:</string>
<string name="label_as_array">As Array:</string>
<string name="label_as_string">As String:</string>
<string name="label_bluetooth_le_status">Bluetooth LE:</string>
<string name="label_bluetooth_status">Bluetooth:</string>
<string name="label_bonding_state">Bonding State:</string>
<string name="label_company_id">Company ID:</string>
<string name="label_data">Data:</string>
<string name="label_desc">Desc:</string>
<string name="label_device_address">Device address:</string>
<string name="label_device_class">Device Class:</string>
<string name="label_device_major_class">Major Class:</string>
<string name="label_device_services">Services:</string>
<string name="label_device_name">Device Name:</string>
<string name="label_distance">Distance:</string>
<string name="label_first_rssi">First RSSI:</string>
<string name="label_first_timestamp">First Timestamp:</string>
<string name="label_last_rssi">Last RSSI:</string>
<string name="label_last_timestamp">Last Timestamp:</string>
<string name="label_mac">MAC:</string>
<string name="label_major">Major:</string>
<string name="label_minor">Minor:</string>
<string name="label_rssi">RSSI:</string>
<string name="label_running_average_rssi">Running Average RSSI:</string>
<string name="label_state">State:</string>
<string name="label_tx_power">TX Power:</string>
<string name="label_uuid">UUID:</string>
<string name="label_updated">Updated:</string>
<string name="label_decriptor">Descriptor:</string>
<string name="connected">Connected</string>
<string name="disconnected">Disconnected</string>
<string name="invalid_device_data">Invalid Device Data!</string>
<string name="no_data">No data</string>
<string name="not_supported">Not supported</string>
<string name="off">Off</string>
<string name="on">On</string>
<string name="supported">Supported</string>
<string name="unknown">unknown</string>
<string name="unknown_characteristic">Unknown characteristic</string>
<string name="unknown_device">Unknown device</string>
<string name="unknown_service">Unknown service</string>
<string name="no_known_services">No known services</string>
<string name="formatter_meters">%sm</string>
<string name="formatter_db">%sdb</string>
<string name="formatter_item_count">Items: %s</string>
<!-- Menu items -->
<string name="menu_about">About</string>
<string name="menu_connect">Connect</string>
<string name="menu_disconnect">Disconnect</string>
<string name="menu_scan">Scan</string>
<string name="menu_stop">Stop</string>
<string name="menu_share">Share</string>
<!-- Export Email Strings -->
<string name="exporter_email_device_list_subject">Bluetooth LE Scan Results (%s)</string>
<string name="exporter_email_device_services_subject" formatted="false">Bluetooth LE Device GATT Results (%s, %s)</string>
<string name="exporter_email_device_list_body">Please find attached the scan results.</string>
<string name="exporter_email_device_list_picker_text">Please select your email client:</string>
</resources> </resources>

Loading…
Cancel
Save