Skip to content

Commit

Permalink
Add basic function for recognizing Eddy-stone URL beacons. Add an ent…
Browse files Browse the repository at this point in the history
…rance to the test BLE beacons activity.

Signed-off-by: Chengzhi Hu <tony.hu1213@gmail.com>
  • Loading branch information
Tony080 committed May 21, 2018
1 parent 2c64039 commit 24e5da7
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 6 deletions.
6 changes: 3 additions & 3 deletions mobile/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ buildscript {

apply plugin: 'com.android.application'
apply plugin: 'jacoco-android'
if(!isFoss) {
if (!isFoss) {
apply plugin: 'io.fabric'
}
apply plugin: 'witness'
Expand All @@ -31,8 +31,8 @@ android {
applicationId "org.openhab.habdroid"
minSdkVersion 14
targetSdkVersion 25
versionCode 57
versionName "2.1.8-beta"
versionCode 58
versionName "2.1.9-beta"
multiDexEnabled true
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
Expand Down
13 changes: 11 additions & 2 deletions mobile/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />

<application
android:allowBackup="true"
Expand All @@ -22,7 +28,6 @@
android:label="@string/app_name"
android:resizeableActivity="true"
android:supportsPictureInPicture="false"
android:theme="@style/HABDroid.Light"
android:supportsRtl="true"
android:name=".core.OpenHABApplication">
<activity
Expand Down Expand Up @@ -74,6 +79,10 @@
<activity android:name="org.openhab.habdroid.ui.IntroActivity"
android:label="@string/app_intro"/>
<activity android:name="de.duenndns.ssl.MemorizingActivity" />
<activity
android:name="org.openhab.habdroid.ui.OpenHABBleActivity"
android:label="@string/title_activity_openhab_ble_beacons" />

<service
android:name=".core.OpenHABVoiceService"
android:exported="false" />
Expand All @@ -97,4 +106,4 @@
<meta-data android:name="com.google.android.gms.analytics.globalConfigResource"
android:resource="@xml/global_tracker" /> -->
</application>
</manifest>
</manifest>
25 changes: 25 additions & 0 deletions mobile/src/main/java/org/openhab/habdroid/model/OpenHABBeacon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.openhab.habdroid.model;

import android.os.Parcelable;

import com.google.auto.value.AutoValue;

@AutoValue
public abstract class OpenHABBeacon {
public enum Type{
eddystone,
iBeacon
}

public abstract String name();
public abstract String address();

@AutoValue.Builder
abstract static class Builder {
public abstract Builder name(String name);
public abstract Builder address(String address);

public abstract OpenHABBeacon build();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.openhab.habdroid.ui;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;

import org.openhab.habdroid.util.BleBeaconConnector;
import org.openhab.habdroid.util.Util;

public class OpenHABBleActivity extends AppCompatActivity {

BleBeaconConnector bleBeaconConnector;
@Override
protected void onCreate(Bundle savedInstanceState) {
Util.setActivityTheme(this);
super.onCreate(savedInstanceState);
bleBeaconConnector = BleBeaconConnector.getInstance(this);
bleBeaconConnector.scanLeServiceCompact();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,11 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
case R.id.about:
openAbout();
return true;
case R.id.beacons://TODO - temporarily add an entrance of BLE beacons for debugging usage
Log.d(TAG, "beacons hit!");
Intent bleBeaconIntent = new Intent(OpenHABMainActivity.this, OpenHABBleActivity.class);
startActivity(bleBeaconIntent);
return true;
}
if (item.getGroupId() == GROUP_ID_SITEMAPS) {
OpenHABSitemap sitemap = mSitemapList.get(item.getItemId());
Expand Down
164 changes: 164 additions & 0 deletions mobile/src/main/java/org/openhab/habdroid/util/BleBeaconConnector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package org.openhab.habdroid.util;

import android.Manifest;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.ParcelUuid;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@TargetApi(18)
public class BleBeaconConnector {
private static final String TAG = BleBeaconConnector.class.getSimpleName();

private static final int REQUEST_ENABLE_BT = 0;
private static final int SCAN_PERIOD = 10000;//Scan for 10s
private static final ParcelUuid BASE_UUID =
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
private static final ParcelUuid EDDYSTONE_URL_SERVICE_UUID =
ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
private static final int UUID_BYTES_16_BIT = 2;
private static final int UUID_BYTES_32_BIT = 4;
private static final int UUID_BYTES_128_BIT = 16;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;

private static BleBeaconConnector INSTANCE;

private BluetoothAdapter bluetoothAdapter;
private Handler handler;
private BluetoothAdapter.LeScanCallback leScanCallback = (bluetoothDevice, i, bytes) -> {
if (!isEddyStoneBeacon(bytes)){//TODO add support for iBeacons
return;
}
Log.d(TAG, "Device: " + bluetoothDevice.getName() + " address: " + bluetoothDevice.getAddress());
};

//Enforce singleton by using private constructors
private BleBeaconConnector(){}

private BleBeaconConnector(AppCompatActivity activity){
checkAndRequestPosPermission(activity);
handler = new Handler();
final BluetoothManager manager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = manager.getAdapter();

//Request to open bluetooth ff it's not enabled.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()){//TODO - Crash on the devices don't have Bluetooth. Change this logic later.
Intent enableBluetoothIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BT);
}
}

private void checkAndRequestPosPermission(AppCompatActivity activity){
String[] posPermission = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
if(ContextCompat.checkSelfPermission(activity, posPermission[0]) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, posPermission, REQUEST_ENABLE_BT);
}
}
@SuppressWarnings("deprecation")
public void scanLeServiceCompact(){
handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), SCAN_PERIOD);
bluetoothAdapter.startLeScan(leScanCallback);
}

public static BleBeaconConnector getInstance(AppCompatActivity activity) {
if (INSTANCE == null){
INSTANCE = new BleBeaconConnector(activity);
}
return INSTANCE;
}

private boolean isEddyStoneBeacon(byte[] bytes){
List<ParcelUuid> serviceUuids = parseFromBytes(bytes);
return serviceUuids.contains(EDDYSTONE_URL_SERVICE_UUID);
}

private List<ParcelUuid> parseFromBytes(byte[] bytes){
int length;
int fieldType;
int currentPos = 0;
List<ParcelUuid> serviceUuids = new ArrayList<>();
while (currentPos < bytes.length) {
length = bytes[currentPos++] & 0xFF;
if (length == 0){
break;
}
fieldType = bytes[currentPos++] & 0xFF;
switch (fieldType) {
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
parseServiceUuid(bytes, currentPos, length, UUID_BYTES_16_BIT, serviceUuids);
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
parseServiceUuid(bytes, currentPos, length, UUID_BYTES_32_BIT, serviceUuids);
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
parseServiceUuid(bytes, currentPos, length, UUID_BYTES_128_BIT, serviceUuids);
}
currentPos += length - 1;
}
return serviceUuids;
}

private void parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
int uuidLength, List<ParcelUuid> serviceUuids) {
while (dataLength > 0) {
byte[] uuidBytes = new byte[uuidLength];
System.arraycopy(scanRecord, currentPos, uuidBytes, 0, uuidLength);
serviceUuids.add(parseUuidFrom(uuidBytes));
dataLength -= uuidLength;
currentPos += uuidLength;
}
}

private ParcelUuid parseUuidFrom(byte[] uuidBytes) {
if (uuidBytes == null) {
throw new IllegalArgumentException("uuidBytes cannot be null");
}
int length = uuidBytes.length;
if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT &&
length != UUID_BYTES_128_BIT) {
throw new IllegalArgumentException("uuidBytes length invalid - " + length);
}
// Construct a 128 bit UUID.
if (length == UUID_BYTES_128_BIT) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
long msb = buf.getLong(8);
long lsb = buf.getLong(0);
return new ParcelUuid(new UUID(msb, lsb));
}
// For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
// 128_bit_value = uuid * 2^96 + BASE_UUID
long shortUuid;
if (length == UUID_BYTES_16_BIT) {
shortUuid = uuidBytes[0] & 0xFF;
shortUuid += (uuidBytes[1] & 0xFF) << 8;
} else {
shortUuid = uuidBytes[0] & 0xFF;
shortUuid += (uuidBytes[1] & 0xFF) << 8;
shortUuid += (uuidBytes[2] & 0xFF) << 16;
shortUuid += (uuidBytes[3] & 0xFF) << 24;
}
long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
return new ParcelUuid(new UUID(msb, lsb));
}
}
29 changes: 29 additions & 0 deletions mobile/src/main/res/layout/activity_openhab_ble_beacons.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">

<android.support.v7.widget.Toolbar
android:id="@+id/openhab_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="8dp"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

<FrameLayout
android:id="@+id/ble_frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</FrameLayout>

</LinearLayout>
5 changes: 5 additions & 0 deletions mobile/src/main/res/menu/left_drawer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
android:id="@+id/notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/app_notifications" />
<!--TODO - temporarily add an entrance of BLE beacons for debugging usage-->
<item
android:id="@+id/beacons"
android:icon="@drawable/ic_nfc_black_180dp"
android:title="@string/mainmenu_openhab_ble_beacons" />
<item
android:id="@+id/settings"
android:icon="@drawable/ic_settings_black_24dp"
Expand Down
6 changes: 5 additions & 1 deletion mobile/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
<string name="mainmenu_openhab_preferences">Settings</string>
<string name="mainmenu_openhab_selectsitemap">Select default sitemap</string>
<string name="mainmenu_openhab_clearcache">Clear images cache</string>
<string name="mainmenu_openhab_ble_beacons">Test BLE Beacons</string>

<!-- TODO - debug useage BLE beacon activity name-->
<string name="title_activity_openhab_ble_beacons">BLE Beacons</string>

<!-- App settings strings -->
<string name="settings_connection_title">Connection</string>
Expand Down Expand Up @@ -119,7 +123,7 @@
<string name="mtm_decision_abort">Abort</string>
<string name="mtm_notification">Certificate verification</string>
<string name="settings_openhab_none">None</string>

<!-- Themes -->
<string name="theme_value_light" translatable="false">light</string>
<string name="theme_value_dark" translatable="false">dark</string>
Expand Down

0 comments on commit 24e5da7

Please sign in to comment.