├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── turbo │ │ └── beaconmqtt │ │ ├── beaconFactory │ │ └── loadBeaconsTest.java │ │ └── preferencesConverter │ │ └── PreferencesConverterV1Test.java │ ├── debug │ └── res │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ ├── com │ │ │ └── google │ │ │ │ └── gson │ │ │ │ └── typeadapters │ │ │ │ └── RuntimeTypeAdapterFactory.java │ │ └── org │ │ │ └── turbo │ │ │ └── beaconmqtt │ │ │ ├── AboutActivity.java │ │ │ ├── AppCompatPreferenceActivity.java │ │ │ ├── BeaconApplication.java │ │ │ ├── MainActivity.java │ │ │ ├── SearchingActivity.java │ │ │ ├── SettingsActivity.java │ │ │ ├── beacon │ │ │ ├── BaseBeacon.java │ │ │ ├── BaseBleBeacon.java │ │ │ ├── EddystoneUidBeacon.java │ │ │ ├── Helper.java │ │ │ ├── IBeacon.java │ │ │ ├── TransactionBeacon.java │ │ │ └── WifiBeacon.java │ │ │ ├── beaconFactory │ │ │ ├── BeaconFactory.java │ │ │ ├── BeaconFactoryAdapter.java │ │ │ └── BeaconFactoryChangeListener.java │ │ │ ├── broadcaster │ │ │ ├── BaseBroadcaster.java │ │ │ ├── Broadcaster.java │ │ │ ├── BroadcasterChangeListener.java │ │ │ └── MqttBroadcaster.java │ │ │ ├── dialog │ │ │ ├── BaseBeaconDialogFragment.java │ │ │ ├── ContextBeaconDialogFragment.java │ │ │ ├── DeleteBeaconDialogFragment.java │ │ │ ├── IBeaconDialogFragment.java │ │ │ ├── TypeBeaconDialogFragment.java │ │ │ └── WifiBeaconDialogFragment.java │ │ │ ├── newBeacon │ │ │ ├── NewBeaconAdapter.java │ │ │ └── NewBeaconList.java │ │ │ ├── preferencesConverter │ │ │ ├── PreferencesConverter.java │ │ │ └── PreferencesConverterV1.java │ │ │ └── wifi │ │ │ ├── WifiChangeListener.java │ │ │ └── WifiStateReceiver.java │ └── res │ │ ├── drawable-anydpi-v24 │ │ └── ic_notification.xml │ │ ├── drawable-hdpi │ │ └── ic_notification.png │ │ ├── drawable-mdpi │ │ └── ic_notification.png │ │ ├── drawable-nodpi │ │ └── background_image.webp │ │ ├── drawable-xhdpi │ │ └── ic_notification.png │ │ ├── drawable-xxhdpi │ │ └── ic_notification.png │ │ ├── drawable │ │ ├── ic_about_outline.xml │ │ ├── ic_access_point.xml │ │ ├── ic_bluetooth_beacon_w_radiation.xml │ │ ├── ic_bluetooth_beacon_wo_radiation_128dp.xml │ │ ├── ic_bluetooth_radiation_128dp.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_menu.xml │ │ ├── ic_menu_add_beacon_manually.xml │ │ ├── ic_menu_restart_scan.xml │ │ ├── ic_plus_circle.xml │ │ ├── ic_server_offline.xml │ │ ├── ic_server_online.xml │ │ └── ic_settings_outline.xml │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_main.xml │ │ ├── activity_ranging.xml │ │ ├── bottom_toolbar_layout.xml │ │ ├── dialog_base_beacon.xml │ │ ├── dialog_i_beacon.xml │ │ ├── dialog_wifi_beacon.xml │ │ ├── item_beacon_card.xml │ │ ├── item_ibeacon.xml │ │ └── item_new_beacon_card.xml │ │ ├── menu │ │ ├── activity_ranging.xml │ │ └── menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── color.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── settings.xml │ └── pro │ └── res │ └── values │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # IntelliJ 37 | *.iml 38 | .idea 39 | 40 | # Keystore files 41 | # Uncomment the following lines if you do not want to check your keystore files in. 42 | #*.jks 43 | #*.keystore 44 | 45 | # External native build folder generated in Android Studio 2.2 and later 46 | .externalNativeBuild 47 | 48 | # Google Services (e.g. APIs or Firebase) 49 | # google-services.json 50 | 51 | # Freeline 52 | freeline.py 53 | freeline/ 54 | freeline_project_description.json 55 | 56 | # fastlane 57 | fastlane/report.xml 58 | fastlane/Preview.html 59 | fastlane/screenshots 60 | fastlane/test_output 61 | fastlane/readme.md 62 | 63 | # Version control 64 | vcs.xml 65 | 66 | # lint 67 | lint/intermediates/ 68 | lint/generated/ 69 | lint/outputs/ 70 | lint/tmp/ 71 | # lint/reports/ 72 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | android: 3 | components: 4 | - build-tools-30.0.2 5 | - android-30 6 | before_install: 7 | - touch $HOME/.android/repositories.cfg 8 | - yes | sdkmanager "platforms;android-30" 9 | - yes | sdkmanager "build-tools;30.0.2" 10 | script: 11 | - "./gradlew assembleRelease" 12 | branches: 13 | only: 14 | - /^v\d+\.\d+\.\d+.*$/ 15 | deploy: 16 | provider: releases 17 | api_key: 18 | secure: lY9tEIiHiUm2+IvQ2ArCSlLu4tdaQNAd7zyLDNmxiQlpJZA/tx/t7y0hDyioefiXyE0XzBmGCFPj9QUmjRbQInOPqNgUmCvxeL6+t8WTGTMDtX7GpWVt4Wrbm72J4EKinNtAf1w9JVLm4b0odCiq8UimUJOxU6Sny+E/0Wsy/s8T7c16fBmZxeg0FU1BmE0h73QDVFADuMjgmdrKWNk+lhK5zyBoqJhErSsT1GWLiUXEFK5WSGz8hyXQWGjVrM05U45AhlTUjDWArtJL4Px73iS+NoInmyIY9FEVSDfP3jJzTVUJJUv8oe89vomKTLqhAn+FLzutmaC2J4pOcgKREcG8pg7Aa8+1S1scfxY9Ii/6K+zeixwqurJ0fBIRDgS2rMG1iDzbyUzrA/h6wJe0cCB81hT8lGBQlzuByNB7UgqsO2u2xSfDFXfKUxO0+iSJSe4v0LYUe06FT9dSScwtkjxNk6sUb9wNh4fqKm7rfQcrl3nvqAl12vG5lo7gW26UXWYrDwH0qFlxSA/7Q2UQlE2i5CBZQCpeUtUeCJQCL/A0E22SKG/MMzK9g+XHykvD33Ij1z2nqbz2R1ylO76vY7NzyYbpGTxE1jZcLvnJ18O2GxVaN3tjnfVPQL3zTlHgFmcSr+DOcAJ2kw5xG6mWXvape3YW0sp7ZU45oeWYUdA= 19 | file: 20 | - app/build/outputs/apk/release/beacon-mqtt*.apk 21 | - app/build/outputs/mapping/release/mapping.txt 22 | file_glob: true 23 | on: 24 | repo: turbo-lab/android-beacon-mqtt 25 | branch: master 26 | tags: true 27 | skip_cleanup: true 28 | overwrite: true 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # beacon_mqtt 2 | Simple android application for notifying MQTT server when iBeacon is in range or lost. Will be useful for Home Assistant users 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 30 5 | buildToolsVersion '30.0.2' 6 | testBuildType "staging" 7 | 8 | defaultConfig { 9 | applicationId "org.turbo.beaconmqtt" 10 | minSdkVersion 19 11 | targetSdkVersion 30 12 | versionName "0.3" 13 | versionCode 322 14 | vectorDrawables.useSupportLibrary = true 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | shrinkResources true 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | debug { 25 | applicationIdSuffix = ".debug" 26 | versionNameSuffix = "-debug" 27 | debuggable true 28 | } 29 | staging { 30 | applicationIdSuffix = ".staging" 31 | versionNameSuffix = " Staging" 32 | shrinkResources true 33 | minifyEnabled true 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | 38 | applicationVariants.all { variant -> 39 | variant.outputs.all { 40 | outputFileName = "${applicationName}-${defaultConfig.versionName}-${defaultConfig.versionCode}.apk" 41 | } 42 | } 43 | 44 | lintOptions { 45 | checkReleaseBuilds true 46 | // Or, if you prefer, you can continue to check for errors in release builds, 47 | // but continue the build even when errors are found: 48 | abortOnError true 49 | } 50 | 51 | tasks.withType(JavaCompile) { 52 | options.compilerArgs << "-Xlint:unchecked" 53 | } 54 | } 55 | 56 | repositories { 57 | maven { 58 | url "https://repo.eclipse.org/content/repositories/paho-releases/" 59 | } 60 | } 61 | 62 | dependencies { 63 | implementation fileTree(include: ['*.jar'], dir: 'libs') 64 | implementation 'com.android.support:appcompat-v7:28.0.0' 65 | implementation 'org.altbeacon:android-beacon-library:2.16.3' 66 | implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2' 67 | implementation 'com.google.code.gson:gson:2.8.5' 68 | implementation('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') { 69 | exclude module: 'support-v4' 70 | } 71 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 72 | implementation 'com.android.support:design:28.0.0' 73 | implementation 'com.android.support:recyclerview-v7:28.0.0' 74 | implementation 'com.android.support:cardview-v7:28.0.0' 75 | implementation 'com.android.support:support-annotations:28.0.0' 76 | implementation 'net.danlew:android.joda:2.10.3' 77 | implementation 'com.rengwuxian.materialedittext:library:2.1.4' 78 | 79 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 80 | androidTestImplementation 'androidx.test:runner:1.2.0' 81 | androidTestImplementation 'androidx.test:rules:1.2.0' 82 | androidTestImplementation 'androidx.test:core:1.2.0' 83 | } 84 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Gson uses generic type information stored in a class file when working with fields. Proguard 2 | # removes such information by default, so configure it to keep all of it. 3 | -keepattributes Signature 4 | 5 | # For using GSON @Expose annotation 6 | -keepattributes *Annotation* 7 | 8 | # Gson specific classes 9 | -dontwarn sun.misc.** 10 | #-keep class com.google.gson.stream.** { *; } 11 | 12 | # Application classes that will be serialized/deserialized over Gson 13 | -keep class org.turbo.beaconmqtt.beacon.** { ; } 14 | -keep class org.turbo.beaconmqtt.preferencesConverter.** { ; } 15 | 16 | -keepclassmembers,allowobfuscation class org.turbo.beaconmqtt.beaconFactory.BeaconFactory { 17 | public org.turbo.beaconmqtt.beacon.IBeacon getIBeaconById(java.lang.String); 18 | public org.turbo.beaconmqtt.beacon.WifiBeacon getWifiBeaconById(java.lang.String); 19 | public void reloadBeacons(); 20 | } 21 | -keepclassmembers,allowobfuscation class org.turbo.beaconmqtt.preferencesConverter.PreferencesConverter { 22 | public static boolean isCurrentPreferencesRevision(android.content.SharedPreferences); 23 | } 24 | 25 | # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, 26 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) 27 | -keep class * implements com.google.gson.TypeAdapter 28 | -keep class * implements com.google.gson.TypeAdapterFactory 29 | -keep class * implements com.google.gson.JsonSerializer 30 | -keep class * implements com.google.gson.JsonDeserializer 31 | 32 | # Prevent R8 from leaving Data object members always null 33 | -keepclassmembers,allowobfuscation class * { 34 | @com.google.gson.annotations.SerializedName ; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /app/src/androidTest/java/org/turbo/beaconmqtt/beaconFactory/loadBeaconsTest.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beaconFactory; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.turbo.beaconmqtt.BeaconApplication; 11 | import org.turbo.beaconmqtt.beacon.IBeacon; 12 | import org.turbo.beaconmqtt.beacon.WifiBeacon; 13 | 14 | import androidx.test.core.app.ApplicationProvider; 15 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertNotNull; 19 | import static org.turbo.beaconmqtt.beaconFactory.BeaconFactory.BEACONS_KEY; 20 | 21 | 22 | @SuppressWarnings("ALL") 23 | @RunWith(AndroidJUnit4ClassRunner.class) 24 | public class loadBeaconsTest { 25 | 26 | private BeaconFactory beaconFactory; 27 | 28 | @Before 29 | public void before() { 30 | Context appContext = ApplicationProvider.getApplicationContext(); 31 | BeaconApplication application = (BeaconApplication) appContext; 32 | 33 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); 34 | SharedPreferences.Editor prefsEditor = sharedPreferences.edit(); 35 | prefsEditor.clear().commit(); 36 | 37 | prefsEditor.putString(BEACONS_KEY, "[{\"iBeaconData\":{\"major\":\"21794\",\"minor\":\"64000\",\"uuid\":\"74278bda-b644-4520-8f0c-720eaf059935\"},\"mac\":\"D8:A9:8B:CB:74:A5\",\"group\":\"home\",\"id\":\"home-ble\",\"tag\":\"ble\",\"type\":\"iBeacon\"}," + 38 | "{\"wifiBeaconData\":{\"ssid\":\"NTK23-5GHz\"},\"group\":\"work\",\"id\":\"work-wifi\",\"tag\":\"wifi\",\"type\":\"Wi-Fi\"}]"); 39 | 40 | prefsEditor.apply(); 41 | 42 | beaconFactory = application.getBeaconFactory(); 43 | beaconFactory.reloadBeacons(); 44 | } 45 | 46 | @Test 47 | public void testCountOfBeacons() { 48 | assertEquals(2, beaconFactory.getBeaconList().size()); 49 | } 50 | 51 | @Test 52 | public void testIBeacon() { 53 | IBeacon beacon = beaconFactory.getIBeaconById("home-ble"); 54 | 55 | assertNotNull(beacon); 56 | assertEquals("home", beacon.getGroup()); 57 | assertEquals("ble", beacon.getTag()); 58 | assertEquals("74278bda-b644-4520-8f0c-720eaf059935", beacon.getUuid()); 59 | assertEquals("21794", beacon.getMajor()); 60 | assertEquals("64000", beacon.getMinor()); 61 | assertEquals("D8:A9:8B:CB:74:A5", beacon.getMacAddress()); 62 | } 63 | 64 | @Test 65 | public void testWifiBeacon() { 66 | WifiBeacon beacon = beaconFactory.getWifiBeaconById("work-wifi"); 67 | 68 | assertNotNull(beacon); 69 | assertEquals("NTK23-5GHz", beacon.getSsid()); 70 | assertEquals("work", beacon.getGroup()); 71 | assertEquals("wifi", beacon.getTag()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/androidTest/java/org/turbo/beaconmqtt/preferencesConverter/PreferencesConverterV1Test.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.preferencesConverter; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import androidx.test.core.app.ApplicationProvider; 12 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertNull; 16 | import static org.junit.Assert.assertTrue; 17 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_EXIT_TIMEOUT_KEY; 18 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_INACTIVE_TIMEOUT_KEY; 19 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_PAUSE_BETWEEN_SCANS_KEY; 20 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_SCAN_DURATION_KEY; 21 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_ENTER_MESSAGE_KEY; 22 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_EXIT_MESSAGE_KEY; 23 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_TOPIC_KEY; 24 | import static org.turbo.beaconmqtt.beaconFactory.BeaconFactory.BEACONS_KEY; 25 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverter.isCurrentPreferencesRevision; 26 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverter.isPreferencesRevisionChanged; 27 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.BEACON_EXIT_REGION_TIMEOUT_KEY; 28 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.BEACON_PERIOD_BETWEEN_SCANS_KEY; 29 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.BEACON_SCAN_PERIOD_KEY; 30 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.MQTT_ENTER_MESSAGE_KEY; 31 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.MQTT_ENTER_TOPIC_KEY; 32 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.MQTT_EXIT_MESSAGE_KEY; 33 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.MQTT_EXIT_TOPIC_KEY; 34 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.REGION_KEY; 35 | import static org.turbo.beaconmqtt.preferencesConverter.PreferencesConverterV1.REGION_LIST_SIZE; 36 | 37 | @SuppressWarnings("ALL") 38 | @RunWith(AndroidJUnit4ClassRunner.class) 39 | public class PreferencesConverterV1Test { 40 | 41 | private SharedPreferences sharedPreferences; 42 | 43 | @Before 44 | public void before() { 45 | Context appContext = ApplicationProvider.getApplicationContext(); 46 | sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); 47 | SharedPreferences.Editor prefsEditor = sharedPreferences.edit(); 48 | prefsEditor.clear().commit(); 49 | 50 | prefsEditor.putString(REGION_KEY + "1", "{\"id\":\"home\",\"uuid\":\"74278bda-b644-4520-8f0c-720eaf059935\",\"major\":\"21794\", \"minor\": \"64000\"}"); 51 | 52 | prefsEditor.putString(BEACON_PERIOD_BETWEEN_SCANS_KEY, "12000"); 53 | prefsEditor.putString(BEACON_SCAN_PERIOD_KEY, "1000"); 54 | prefsEditor.putString(BEACON_EXIT_REGION_TIMEOUT_KEY, "300000"); 55 | 56 | prefsEditor.putString(MQTT_ENTER_TOPIC_KEY, "location/%mac%"); 57 | prefsEditor.putString(MQTT_EXIT_TOPIC_KEY, "location/%mac%"); 58 | prefsEditor.putString(MQTT_ENTER_MESSAGE_KEY, "%beacon%"); 59 | prefsEditor.putString(MQTT_EXIT_MESSAGE_KEY, "not_home"); 60 | 61 | prefsEditor.apply(); 62 | 63 | @SuppressWarnings("unused") 64 | PreferencesConverter converter = new PreferencesConverter(sharedPreferences); 65 | } 66 | 67 | @Test 68 | public void testPreferencesVersionChanged() { 69 | assertTrue(isPreferencesRevisionChanged(sharedPreferences)); 70 | } 71 | 72 | @Test 73 | public void testCurrentPreferencesRevision() { 74 | assertTrue(isCurrentPreferencesRevision(sharedPreferences)); 75 | } 76 | 77 | @Test 78 | public void testBeacons() { 79 | assertEquals("[{\"iBeaconData\":{\"major\":\"21794\",\"minor\":\"64000\",\"uuid\":\"74278bda-b644-4520-8f0c-720eaf059935\"},\"mac\":\"\",\"group\":\"home\",\"id\":\"home\",\"tag\":\"\",\"type\":\"iBeacon\"}]", 80 | sharedPreferences.getString(BEACONS_KEY, null)); 81 | } 82 | 83 | @Test 84 | public void testTimings() { 85 | assertEquals("12000", 86 | sharedPreferences.getString(BEACON_PAUSE_BETWEEN_SCANS_KEY, null)); 87 | assertEquals("1000", 88 | sharedPreferences.getString(BEACON_SCAN_DURATION_KEY, null)); 89 | assertEquals("300000", 90 | sharedPreferences.getString(BEACON_EXIT_TIMEOUT_KEY, null)); 91 | assertEquals("150000", 92 | sharedPreferences.getString(BEACON_INACTIVE_TIMEOUT_KEY, null)); 93 | } 94 | 95 | @Test 96 | public void testMqttTemplates() { 97 | assertEquals("location/%mac%", 98 | sharedPreferences.getString(MQTT_MASTER_TOPIC_KEY, null)); 99 | assertEquals("%group%", 100 | sharedPreferences.getString(MQTT_MASTER_ENTER_MESSAGE_KEY, null)); 101 | assertEquals("not_home", 102 | sharedPreferences.getString(MQTT_MASTER_EXIT_MESSAGE_KEY, null)); 103 | } 104 | 105 | @Test 106 | public void testInvalidation() { 107 | for (int index = 0; index < REGION_LIST_SIZE; index++) { 108 | assertNull(sharedPreferences.getString(REGION_KEY + index, null)); 109 | } 110 | 111 | assertNull(sharedPreferences.getString(BEACON_PERIOD_BETWEEN_SCANS_KEY, null)); 112 | assertNull(sharedPreferences.getString(BEACON_SCAN_PERIOD_KEY, null)); 113 | assertNull(sharedPreferences.getString(BEACON_EXIT_REGION_TIMEOUT_KEY, null)); 114 | 115 | assertNull(sharedPreferences.getString(MQTT_ENTER_TOPIC_KEY, null)); 116 | assertNull(sharedPreferences.getString(MQTT_EXIT_TOPIC_KEY, null)); 117 | assertNull(sharedPreferences.getString(MQTT_ENTER_MESSAGE_KEY, null)); 118 | assertNull(sharedPreferences.getString(MQTT_EXIT_MESSAGE_KEY, null)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Beacon MQTT debug 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 52 | 53 | 58 | 59 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.gson.typeadapters; 18 | 19 | import java.io.IOException; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | import com.google.gson.Gson; 24 | import com.google.gson.JsonElement; 25 | import com.google.gson.JsonObject; 26 | import com.google.gson.JsonParseException; 27 | import com.google.gson.JsonPrimitive; 28 | import com.google.gson.TypeAdapter; 29 | import com.google.gson.TypeAdapterFactory; 30 | import com.google.gson.internal.Streams; 31 | import com.google.gson.reflect.TypeToken; 32 | import com.google.gson.stream.JsonReader; 33 | import com.google.gson.stream.JsonWriter; 34 | 35 | /** 36 | * Adapts values whose runtime type may differ from their declaration type. This 37 | * is necessary when a field's type is not the same type that GSON should create 38 | * when deserializing that field. For example, consider these types: 39 | *
   {@code
 40 |  *   abstract class Shape {
 41 |  *     int x;
 42 |  *     int y;
 43 |  *   }
 44 |  *   class Circle extends Shape {
 45 |  *     int radius;
 46 |  *   }
 47 |  *   class Rectangle extends Shape {
 48 |  *     int width;
 49 |  *     int height;
 50 |  *   }
 51 |  *   class Diamond extends Shape {
 52 |  *     int width;
 53 |  *     int height;
 54 |  *   }
 55 |  *   class Drawing {
 56 |  *     Shape bottomShape;
 57 |  *     Shape topShape;
 58 |  *   }
 59 |  * }
60 | *

Without additional type information, the serialized JSON is ambiguous. Is 61 | * the bottom shape in this drawing a rectangle or a diamond?

   {@code
 62 |  *   {
 63 |  *     "bottomShape": {
 64 |  *       "width": 10,
 65 |  *       "height": 5,
 66 |  *       "x": 0,
 67 |  *       "y": 0
 68 |  *     },
 69 |  *     "topShape": {
 70 |  *       "radius": 2,
 71 |  *       "x": 4,
 72 |  *       "y": 1
 73 |  *     }
 74 |  *   }}
75 | * This class addresses this problem by adding type information to the 76 | * serialized JSON and honoring that type information when the JSON is 77 | * deserialized:
   {@code
 78 |  *   {
 79 |  *     "bottomShape": {
 80 |  *       "type": "Diamond",
 81 |  *       "width": 10,
 82 |  *       "height": 5,
 83 |  *       "x": 0,
 84 |  *       "y": 0
 85 |  *     },
 86 |  *     "topShape": {
 87 |  *       "type": "Circle",
 88 |  *       "radius": 2,
 89 |  *       "x": 4,
 90 |  *       "y": 1
 91 |  *     }
 92 |  *   }}
93 | * Both the type field name ({@code "type"}) and the type labels ({@code 94 | * "Rectangle"}) are configurable. 95 | * 96 | *

Registering Types

97 | * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field 98 | * name to the {@link #of} beaconFactory method. If you don't supply an explicit type 99 | * field name, {@code "type"} will be used.
   {@code
100 |  *   RuntimeTypeAdapterFactory shapeAdapterFactory
101 |  *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
102 |  * }
103 | * Next register all of your subtypes. Every subtype must be explicitly 104 | * registered. This protects your application from injection attacks. If you 105 | * don't supply an explicit type label, the type's simple name will be used. 106 | *
   {@code
107 |  *   shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
108 |  *   shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
109 |  *   shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
110 |  * }
111 | * Finally, register the type adapter beaconFactory in your application's GSON builder: 112 | *
   {@code
113 |  *   Gson gson = new GsonBuilder()
114 |  *       .registerTypeAdapterFactory(shapeAdapterFactory)
115 |  *       .create();
116 |  * }
117 | * Like {@code GsonBuilder}, this API supports chaining:
   {@code
118 |  *   RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
119 |  *       .registerSubtype(Rectangle.class)
120 |  *       .registerSubtype(Circle.class)
121 |  *       .registerSubtype(Diamond.class);
122 |  * }
123 | */ 124 | @SuppressWarnings({"unused", "ConstantConditions"}) 125 | public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { 126 | private final Class baseType; 127 | private final String typeFieldName; 128 | private final Map> labelToSubtype = new LinkedHashMap<>(); 129 | private final Map, String> subtypeToLabel = new LinkedHashMap<>(); 130 | private final boolean maintainType; 131 | 132 | private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { 133 | if (typeFieldName == null || baseType == null) { 134 | throw new NullPointerException(); 135 | } 136 | this.baseType = baseType; 137 | this.typeFieldName = typeFieldName; 138 | this.maintainType = maintainType; 139 | } 140 | 141 | /** 142 | * Creates a new runtime type adapter using for {@code baseType} using {@code 143 | * typeFieldName} as the type field name. Type field names are case sensitive. 144 | * {@code maintainType} flag decide if the type will be stored in pojo or not. 145 | */ 146 | public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { 147 | return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); 148 | } 149 | 150 | /** 151 | * Creates a new runtime type adapter using for {@code baseType} using {@code 152 | * typeFieldName} as the type field name. Type field names are case sensitive. 153 | */ 154 | public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { 155 | return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); 156 | } 157 | 158 | /** 159 | * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as 160 | * the type field name. 161 | */ 162 | public static RuntimeTypeAdapterFactory of(Class baseType) { 163 | return new RuntimeTypeAdapterFactory<>(baseType, "type", false); 164 | } 165 | 166 | /** 167 | * Registers {@code type} identified by {@code label}. Labels are case 168 | * sensitive. 169 | * 170 | * @throws IllegalArgumentException if either {@code type} or {@code label} 171 | * have already been registered on this type adapter. 172 | */ 173 | public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { 174 | if (type == null || label == null) { 175 | throw new NullPointerException(); 176 | } 177 | if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { 178 | throw new IllegalArgumentException("types and labels must be unique"); 179 | } 180 | labelToSubtype.put(label, type); 181 | subtypeToLabel.put(type, label); 182 | return this; 183 | } 184 | 185 | /** 186 | * Registers {@code type} identified by its {@link Class#getSimpleName simple 187 | * name}. Labels are case sensitive. 188 | * 189 | * @throws IllegalArgumentException if either {@code type} or its simple name 190 | * have already been registered on this type adapter. 191 | */ 192 | public RuntimeTypeAdapterFactory registerSubtype(Class type) { 193 | return registerSubtype(type, type.getSimpleName()); 194 | } 195 | 196 | public TypeAdapter create(Gson gson, TypeToken type) { 197 | if (type.getRawType() != baseType) { 198 | return null; 199 | } 200 | 201 | final Map> labelToDelegate 202 | = new LinkedHashMap<>(); 203 | final Map, TypeAdapter> subtypeToDelegate 204 | = new LinkedHashMap<>(); 205 | for (Map.Entry> entry : labelToSubtype.entrySet()) { 206 | TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); 207 | labelToDelegate.put(entry.getKey(), delegate); 208 | subtypeToDelegate.put(entry.getValue(), delegate); 209 | } 210 | 211 | return new TypeAdapter() { 212 | @SuppressWarnings("RedundantThrows") 213 | @Override public R read(JsonReader in) throws IOException { 214 | JsonElement jsonElement = Streams.parse(in); 215 | JsonElement labelJsonElement; 216 | if (maintainType) { 217 | labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); 218 | } else { 219 | labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); 220 | } 221 | 222 | if (labelJsonElement == null) { 223 | throw new JsonParseException("cannot deserialize " + baseType 224 | + " because it does not define a field named " + typeFieldName); 225 | } 226 | String label = labelJsonElement.getAsString(); 227 | @SuppressWarnings("unchecked") // registration requires that subtype extends T 228 | TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); 229 | if (delegate == null) { 230 | throw new JsonParseException("cannot deserialize " + baseType + " subtype named " 231 | + label + "; did you forget to register a subtype?"); 232 | } 233 | return delegate.fromJsonTree(jsonElement); 234 | } 235 | 236 | @Override public void write(JsonWriter out, R value) throws IOException { 237 | Class srcType = value.getClass(); 238 | String label = subtypeToLabel.get(srcType); 239 | @SuppressWarnings("unchecked") // registration requires that subtype extends T 240 | TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); 241 | if (delegate == null) { 242 | throw new JsonParseException("cannot serialize " + srcType.getName() 243 | + "; did you forget to register a subtype?"); 244 | } 245 | JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); 246 | 247 | if (maintainType) { 248 | Streams.write(jsonObject, out); 249 | return; 250 | } 251 | 252 | JsonObject clone = new JsonObject(); 253 | 254 | if (jsonObject.has(typeFieldName)) { 255 | throw new JsonParseException("cannot serialize " + srcType.getName() 256 | + " because it already defines a field named " + typeFieldName); 257 | } 258 | clone.add(typeFieldName, new JsonPrimitive(label)); 259 | 260 | for (Map.Entry e : jsonObject.entrySet()) { 261 | clone.add(e.getKey(), e.getValue()); 262 | } 263 | Streams.write(clone, out); 264 | } 265 | }.nullSafe(); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt; 2 | 3 | import android.content.pm.PackageInfo; 4 | import android.content.pm.PackageManager; 5 | import android.os.Bundle; 6 | import android.support.v7.app.ActionBar; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.text.Html; 9 | import android.text.Spanned; 10 | import android.text.method.LinkMovementMethod; 11 | import android.widget.TextView; 12 | 13 | public class AboutActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_about); 19 | 20 | setupActionBar(); 21 | 22 | setVersionText(); 23 | setHomepageText(); 24 | setGooglePlayText(); 25 | } 26 | 27 | private void setupActionBar() { 28 | ActionBar actionBar = getSupportActionBar(); 29 | if (actionBar != null) { 30 | actionBar.setDisplayHomeAsUpEnabled(true); 31 | } 32 | } 33 | 34 | private void setVersionText() { 35 | String versionName = "unknown"; 36 | int versionBuild = -1; 37 | try { 38 | PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); 39 | versionName = packageInfo.versionName; 40 | versionBuild = packageInfo.versionCode % 100; 41 | } catch (PackageManager.NameNotFoundException e) { 42 | e.printStackTrace(); 43 | } 44 | 45 | TextView textViewVersionInfo = findViewById(R.id.version_text); 46 | textViewVersionInfo.setText(getString(R.string.version_text, versionName, versionBuild)); 47 | } 48 | 49 | private void setHomepageText() { 50 | Spanned policy = Html.fromHtml(getString(R.string.homepage_text)); 51 | TextView homepageText = findViewById(R.id.homepage_text); 52 | homepageText.setText(policy); 53 | homepageText.setMovementMethod(LinkMovementMethod.getInstance()); 54 | } 55 | 56 | private void setGooglePlayText() { 57 | Spanned policy = Html.fromHtml(getString(R.string.google_play)); 58 | TextView homepageText = findViewById(R.id.google_play_text); 59 | homepageText.setText(policy); 60 | homepageText.setMovementMethod(LinkMovementMethod.getInstance()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/AppCompatPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceActivity; 6 | import android.support.annotation.LayoutRes; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.app.ActionBar; 9 | import android.support.v7.app.AppCompatDelegate; 10 | import android.view.MenuInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | /** 15 | * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls 16 | * to be used with AppCompat. 17 | */ 18 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity { 19 | 20 | private AppCompatDelegate mDelegate; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | getDelegate().installViewFactory(); 25 | getDelegate().onCreate(savedInstanceState); 26 | super.onCreate(savedInstanceState); 27 | } 28 | 29 | @Override 30 | protected void onPostCreate(Bundle savedInstanceState) { 31 | super.onPostCreate(savedInstanceState); 32 | getDelegate().onPostCreate(savedInstanceState); 33 | } 34 | 35 | ActionBar getSupportActionBar() { 36 | return getDelegate().getSupportActionBar(); 37 | } 38 | 39 | @NonNull 40 | @Override 41 | public MenuInflater getMenuInflater() { 42 | return getDelegate().getMenuInflater(); 43 | } 44 | 45 | @Override 46 | public void setContentView(@LayoutRes int layoutResID) { 47 | getDelegate().setContentView(layoutResID); 48 | } 49 | 50 | @Override 51 | public void setContentView(View view) { 52 | getDelegate().setContentView(view); 53 | } 54 | 55 | @Override 56 | public void setContentView(View view, ViewGroup.LayoutParams params) { 57 | getDelegate().setContentView(view, params); 58 | } 59 | 60 | @Override 61 | public void addContentView(View view, ViewGroup.LayoutParams params) { 62 | getDelegate().addContentView(view, params); 63 | } 64 | 65 | @Override 66 | protected void onPostResume() { 67 | super.onPostResume(); 68 | getDelegate().onPostResume(); 69 | } 70 | 71 | @Override 72 | protected void onTitleChanged(CharSequence title, int color) { 73 | super.onTitleChanged(title, color); 74 | getDelegate().setTitle(title); 75 | } 76 | 77 | @Override 78 | public void onConfigurationChanged(Configuration newConfig) { 79 | super.onConfigurationChanged(newConfig); 80 | getDelegate().onConfigurationChanged(newConfig); 81 | } 82 | 83 | @Override 84 | protected void onStop() { 85 | super.onStop(); 86 | getDelegate().onStop(); 87 | } 88 | 89 | @Override 90 | protected void onDestroy() { 91 | super.onDestroy(); 92 | getDelegate().onDestroy(); 93 | } 94 | 95 | public void invalidateOptionsMenu() { 96 | getDelegate().invalidateOptionsMenu(); 97 | } 98 | 99 | private AppCompatDelegate getDelegate() { 100 | if (mDelegate == null) { 101 | mDelegate = AppCompatDelegate.create(this, null); 102 | } 103 | return mDelegate; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/BeaconApplication.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.os.Build; 7 | import android.os.VibrationEffect; 8 | import android.os.Vibrator; 9 | import android.preference.PreferenceManager; 10 | import android.support.annotation.NonNull; 11 | 12 | import net.danlew.android.joda.JodaTimeAndroid; 13 | 14 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 15 | import org.turbo.beaconmqtt.beacon.Helper; 16 | import org.turbo.beaconmqtt.beaconFactory.BeaconFactory; 17 | import org.turbo.beaconmqtt.beaconFactory.BeaconFactoryChangeListener; 18 | import org.turbo.beaconmqtt.broadcaster.Broadcaster; 19 | import org.turbo.beaconmqtt.preferencesConverter.PreferencesConverter; 20 | 21 | import java.text.DateFormat; 22 | import java.util.Date; 23 | 24 | import static org.turbo.beaconmqtt.SettingsActivity.NOTIFICATION_VIBRATE_ON_EVENTS_KEY; 25 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_ENTER; 26 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_EXIT; 27 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_INACTIVE; 28 | 29 | public class BeaconApplication extends Application implements BeaconFactoryChangeListener { 30 | @SuppressWarnings("unused") 31 | private static final String TAG = "BeaconMQTT"; 32 | private Broadcaster broadcaster; 33 | private BeaconFactory beaconFactory; 34 | private MainActivity mainActivity = null; 35 | private String debugLog = ""; 36 | private SharedPreferences defaultSharedPreferences; 37 | @SuppressWarnings("FieldCanBeLocal") 38 | private SharedPreferences.OnSharedPreferenceChangeListener onSharedPreferenceChangeListener; 39 | private boolean vibrateOnEvents; 40 | private String activeGroup = ""; 41 | 42 | public void onCreate() { 43 | super.onCreate(); 44 | 45 | JodaTimeAndroid.init(this); 46 | 47 | PreferenceManager.setDefaultValues(this, R.xml.settings, false); 48 | defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); 49 | setUpSettingsChangedListener(defaultSharedPreferences); 50 | 51 | @SuppressWarnings("unused") 52 | PreferencesConverter preferencesConverter = new PreferencesConverter(defaultSharedPreferences); 53 | 54 | beaconFactory = new BeaconFactory(this); 55 | getBeaconFactory().addChangeListener(this); 56 | 57 | broadcaster = new Broadcaster(this); 58 | reapplyVibration(); 59 | } 60 | 61 | private void setUpSettingsChangedListener(final SharedPreferences defaultSharedPreferences) { 62 | onSharedPreferenceChangeListener = new SharedPreferences 63 | .OnSharedPreferenceChangeListener() { 64 | @Override 65 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 66 | if (NOTIFICATION_VIBRATE_ON_EVENTS_KEY.equals(key)) { 67 | vibrateOnEvents = defaultSharedPreferences.getBoolean(NOTIFICATION_VIBRATE_ON_EVENTS_KEY, false); 68 | } 69 | } 70 | }; 71 | defaultSharedPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); 72 | } 73 | 74 | public void setMainActivity(MainActivity activity) { 75 | this.mainActivity = activity; 76 | } 77 | 78 | @SuppressWarnings("unused") 79 | public void clearDebugLog() { 80 | debugLog = ""; 81 | } 82 | 83 | public String getDebugLog() { 84 | return debugLog; 85 | } 86 | 87 | private int getDebugLogLinesCount() { 88 | String[] lines = debugLog.split("\n"); 89 | return lines.length; 90 | } 91 | 92 | public void debugLog(String line) { 93 | int start; 94 | DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); 95 | String currentDateAndTime = df.format(new Date()); 96 | 97 | if (debugLog.length() != 0) { 98 | debugLog += "\n"; 99 | } 100 | debugLog += (currentDateAndTime + ": " + line); 101 | 102 | while (getDebugLogLinesCount() > 100) { 103 | if ((start = debugLog.indexOf('\n')) > 0) { 104 | debugLog = debugLog.substring(start + 1); 105 | } else { 106 | break; 107 | } 108 | } 109 | 110 | if (this.mainActivity != null) { 111 | this.mainActivity.updateDebugLog(debugLog); 112 | } 113 | } 114 | 115 | private void reapplyVibration() { 116 | vibrateOnEvents = defaultSharedPreferences.getBoolean(NOTIFICATION_VIBRATE_ON_EVENTS_KEY, false); 117 | } 118 | 119 | private void vibrate() { 120 | Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 121 | 122 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 123 | v.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE)); 124 | } else { 125 | v.vibrate(500); 126 | } 127 | } 128 | 129 | @Override 130 | public void onChangeFactory() { 131 | // do nothing 132 | } 133 | 134 | @Override 135 | public void onEnterMaster(@NonNull BaseBeacon beacon) { 136 | debugLog("Master is " + beacon.getId()); 137 | 138 | /* Specific case when user didn't assigned the groups */ 139 | if (activeGroup.isEmpty() && beacon.getGroup().isEmpty()) { 140 | getBroadcaster().publishEnterMaster(beacon); 141 | } 142 | /* The app doesn't need to resend the message in the same location. */ 143 | else if (!activeGroup.equals(beacon.getGroup())) { 144 | activeGroup = beacon.getGroup(); 145 | getBroadcaster().publishEnterMaster(beacon); 146 | } 147 | 148 | if (vibrateOnEvents) { 149 | vibrate(); 150 | } 151 | } 152 | 153 | @Override 154 | public void onExitMaster(@NonNull BaseBeacon beacon) { 155 | debugLog("Master " + beacon.getId() + " lost"); 156 | 157 | activeGroup = ""; 158 | 159 | getBroadcaster().publishExitMaster(beacon); 160 | 161 | if (vibrateOnEvents) { 162 | vibrate(); 163 | } 164 | } 165 | 166 | @Override 167 | public void onChangeBeacon(@NonNull BaseBeacon beacon, Helper.BeaconState state) { 168 | if (BEACON_STATE_EXIT.equals(state)) { 169 | debugLog("Exit " + beacon.getId()); 170 | getBroadcaster().publishState(beacon); 171 | } else if (BEACON_STATE_INACTIVE.equals(state)) { 172 | debugLog("Timeout " + beacon.getId()); 173 | } else if (BEACON_STATE_ENTER.equals(state)) { 174 | debugLog("Enter " + beacon.getId()); 175 | getBroadcaster().publishState(beacon); 176 | } 177 | } 178 | 179 | @Override 180 | public void onTrackBeacon(@NonNull BaseBeacon beacon) { 181 | getBroadcaster().publishTrack(beacon); 182 | } 183 | 184 | public Broadcaster getBroadcaster() { 185 | return broadcaster; 186 | } 187 | 188 | public BeaconFactory getBeaconFactory() { 189 | return beaconFactory; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/SearchingActivity.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt; 2 | 3 | import android.os.Bundle; 4 | import android.os.RemoteException; 5 | import android.support.v7.app.ActionBar; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.Log; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.animation.AlphaAnimation; 14 | import android.view.animation.Animation; 15 | import android.widget.ImageView; 16 | import android.widget.LinearLayout; 17 | 18 | import org.altbeacon.beacon.Beacon; 19 | import org.altbeacon.beacon.BeaconConsumer; 20 | import org.altbeacon.beacon.BeaconManager; 21 | import org.altbeacon.beacon.RangeNotifier; 22 | import org.altbeacon.beacon.Region; 23 | import org.turbo.beaconmqtt.beacon.TransactionBeacon; 24 | import org.turbo.beaconmqtt.beaconFactory.BeaconFactory; 25 | import org.turbo.beaconmqtt.dialog.TypeBeaconDialogFragment; 26 | import org.turbo.beaconmqtt.newBeacon.NewBeaconAdapter; 27 | 28 | import java.util.Collection; 29 | 30 | public class SearchingActivity extends AppCompatActivity implements BeaconConsumer { 31 | private static final String TAG = "SearchingActivity"; 32 | 33 | private static final String FAKE_REGION_ID_FOR_RANGING = "myRangingUniqueId"; 34 | private BeaconManager beaconManager; 35 | private BeaconFactory beaconFactory; 36 | private NewBeaconAdapter beaconAdapter; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | Log.d(TAG, "onCreate"); 41 | 42 | super.onCreate(savedInstanceState); 43 | 44 | beaconManager = BeaconManager.getInstanceForApplication(this); 45 | beaconFactory = ((BeaconApplication) getApplication()).getBeaconFactory(); 46 | 47 | setContentView(R.layout.activity_ranging); 48 | 49 | setupActionBar(); 50 | 51 | RecyclerView beaconRecyclerView = findViewById(R.id.list_beacon); 52 | beaconRecyclerView.setHasFixedSize(true); 53 | RecyclerView.LayoutManager beaconLayoutManager = new LinearLayoutManager(this); 54 | beaconRecyclerView.setLayoutManager(beaconLayoutManager); 55 | 56 | beaconAdapter = new NewBeaconAdapter(this, getApplicationContext()); 57 | beaconRecyclerView.setAdapter(beaconAdapter); 58 | 59 | showEmptyView(); 60 | } 61 | 62 | @Override 63 | protected void onDestroy() { 64 | Log.d(TAG, "onDestroy"); 65 | super.onDestroy(); 66 | } 67 | 68 | @Override 69 | protected void onPause() { 70 | Log.d(TAG, "onPause"); 71 | super.onPause(); 72 | 73 | // todo: https://github.com/AltBeacon/android-beacon-library/issues/614 74 | try { 75 | beaconManager.stopRangingBeaconsInRegion(new Region(FAKE_REGION_ID_FOR_RANGING, null, null, null)); 76 | } catch (RemoteException e) { 77 | e.printStackTrace(); 78 | } 79 | 80 | beaconManager.unbind(this); 81 | } 82 | 83 | @Override 84 | protected void onResume() { 85 | super.onResume(); 86 | beaconManager.bind(this); 87 | } 88 | 89 | private void setupActionBar() { 90 | ActionBar actionBar = getSupportActionBar(); 91 | if (actionBar != null) { 92 | actionBar.setDisplayHomeAsUpEnabled(true); 93 | } 94 | } 95 | 96 | @Override 97 | public boolean onCreateOptionsMenu(Menu menu) { 98 | getMenuInflater().inflate(R.menu.activity_ranging, menu); 99 | return true; 100 | } 101 | 102 | @Override 103 | public boolean onOptionsItemSelected(MenuItem item) { 104 | switch (item.getItemId()) { 105 | case R.id.action_add_manual: 106 | beaconFactory.setTransactionBeacon(new TransactionBeacon()); 107 | TypeBeaconDialogFragment newFragment = TypeBeaconDialogFragment.newInstance(); 108 | newFragment.show(getSupportFragmentManager(), "TypeBeaconDialog"); 109 | return true; 110 | case R.id.action_restart_scan: 111 | beaconAdapter.clear(); 112 | showEmptyView(); 113 | return true; 114 | } 115 | return false; 116 | } 117 | 118 | @Override 119 | public void onBeaconServiceConnect() { 120 | Log.d(TAG, "onBeaconServiceConnect"); 121 | RangeNotifier rangeNotifier = new RangeNotifier() { 122 | @Override 123 | public void didRangeBeaconsInRegion(Collection beacons, Region region) { 124 | //Log.d(TAG, "didRangeBeaconsInRegion called with beacon count: " + beacons.size()); 125 | for (Beacon beacon : beacons) { 126 | if (beaconFactory.getBeaconById(region.getUniqueId()) == null) { 127 | beaconAdapter.addBeacon(beacon); 128 | hideEmptyView(); 129 | } 130 | } 131 | } 132 | }; 133 | try { 134 | beaconManager.startRangingBeaconsInRegion(new Region(FAKE_REGION_ID_FOR_RANGING, null, null, null)); 135 | beaconManager.addRangeNotifier(rangeNotifier); 136 | } catch (RemoteException e) { 137 | e.printStackTrace(); 138 | } 139 | } 140 | 141 | private void hideEmptyView() { 142 | ImageView progressIcon = findViewById(R.id.image_radiation); 143 | progressIcon.clearAnimation(); 144 | 145 | LinearLayout emptyView = findViewById(R.id.empty_view); 146 | emptyView.setVisibility(View.GONE); 147 | } 148 | 149 | private void showEmptyView() { 150 | ImageView progressIcon = findViewById(R.id.image_radiation); 151 | AlphaAnimation animation1 = new AlphaAnimation(0.0f, 1.0f); 152 | animation1.setDuration(1000); 153 | animation1.setRepeatMode(Animation.REVERSE); 154 | animation1.setRepeatCount(Animation.INFINITE); 155 | progressIcon.startAnimation(animation1); 156 | 157 | LinearLayout emptyView = findViewById(R.id.empty_view); 158 | emptyView.setVisibility(View.VISIBLE); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.preference.ListPreference; 6 | import android.preference.Preference; 7 | import android.preference.PreferenceManager; 8 | import android.support.v7.app.ActionBar; 9 | import android.view.MenuItem; 10 | import android.widget.Toast; 11 | 12 | import java.util.Locale; 13 | import java.util.Objects; 14 | 15 | public class SettingsActivity extends AppCompatPreferenceActivity { 16 | 17 | public static final String BEACON_PAUSE_BETWEEN_SCANS_KEY = "beacon_pause_between_scans"; 18 | public static final String BEACON_SCAN_DURATION_KEY = "beacon_scan_duration"; 19 | public static final String BEACON_INACTIVE_TIMEOUT_KEY = "beacon_inactive_timeout"; 20 | public static final String BEACON_EXIT_TIMEOUT_KEY = "beacon_exit_timeout"; 21 | public static final String BEACON_HYSTERESIS_FACTOR_KEY = "beacon_hysteresis_factor"; 22 | public static final String MQTT_SERVER_KEY = "mqtt_server"; 23 | public static final String MQTT_PORT_KEY = "mqtt_port"; 24 | public static final String MQTT_USER_KEY = "mqtt_user"; 25 | public static final String MQTT_PASS_KEY = "mqtt_pass"; 26 | public static final String MQTT_BEACON_STATE_TOPIC_KEY = "mqtt_beacon_state_topic"; 27 | public static final String MQTT_BEACON_STATE_MESSAGE_KEY = "mqtt_beacon_state_message"; 28 | public static final String MQTT_MASTER_TOPIC_KEY = "mqtt_master_topic"; 29 | public static final String MQTT_MASTER_ENTER_MESSAGE_KEY = "mqtt_master_enter_message"; 30 | public static final String MQTT_MASTER_EXIT_MESSAGE_KEY = "mqtt_master_exit_message"; 31 | public static final String MQTT_TRACK_TOPIC_KEY = "mqtt_track_topic"; 32 | public static final String MQTT_TRACK_MESSAGE_KEY = "mqtt_track_message"; 33 | public static final String NOTIFICATION_SHOW_LOG = "show_log"; 34 | public static final String NOTIFICATION_VIBRATE_ON_EVENTS_KEY = "notification_vibrate_on_events"; 35 | private static final Long BEACON_PAUSE_BETWEEN_SCANS_MIN = 0L; 36 | private static final Long BEACON_PAUSE_BETWEEN_SCANS_MAX = 55000L; 37 | private static final Long BEACON_SCAN_DURATION_MIN = 500L; 38 | private static final Long BEACON_SCAN_DURATION_MAX = 5000L; 39 | private static final Long BEACON_INACTIVE_TIMEOUT_MIN = 15000L; 40 | private static final Long BEACON_INACTIVE_TIMEOUT_MAX = 300000L; 41 | private static final Long BEACON_EXIT_TIMEOUT_MIN = 30000L; 42 | private static final Long BEACON_EXIT_TIMEOUT_MAX = 600000L; 43 | private static final Double BEACON_HYSTERESIS_FACTOR_MIN = 1.0d; 44 | private static final Double BEACON_HYSTERESIS_FACTOR_MAX = 5.0d; 45 | private static final Long MQTT_PORT_MIN = 1L; 46 | private static final Long MQTT_PORT_MAX = 65535L; 47 | private static boolean isActive = false; 48 | 49 | /** 50 | * A preference value change listener that updates the preference's summary 51 | * to reflect its new value. 52 | */ 53 | private final Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { 54 | @Override 55 | public boolean onPreferenceChange(Preference preference, Object value) { 56 | String stringValue = value.toString(); 57 | 58 | if (!validatePreference(preference, stringValue)) { 59 | return false; 60 | } 61 | 62 | if (preference instanceof ListPreference) { 63 | // For list preferences, look up the correct display value in 64 | // the preference's 'entries' list. 65 | ListPreference listPreference = (ListPreference) preference; 66 | int index = listPreference.findIndexOfValue(stringValue); 67 | 68 | // Set the summary to reflect the new value. 69 | preference.setSummary( 70 | index >= 0 71 | ? listPreference.getEntries()[index] 72 | : null); 73 | } else if (preference.getKey().equals(MQTT_PASS_KEY)) { 74 | //noinspection ReplaceAllDot 75 | preference.setSummary(stringValue.replaceAll(".", "•")); 76 | } else { 77 | // For all other preferences, set the summary to the value's 78 | // simple string representation. 79 | preference.setSummary(stringValue); 80 | } 81 | return true; 82 | } 83 | }; 84 | 85 | public static boolean isActive() { 86 | return isActive; 87 | } 88 | 89 | @SuppressWarnings("deprecation") 90 | @Override 91 | protected void onCreate(Bundle savedInstanceState) { 92 | super.onCreate(savedInstanceState); 93 | addPreferencesFromResource(R.xml.settings); 94 | setupActionBar(); 95 | 96 | bindPreferenceSummaryToValue(findPreference(BEACON_PAUSE_BETWEEN_SCANS_KEY)); 97 | bindPreferenceSummaryToValue(findPreference(BEACON_SCAN_DURATION_KEY)); 98 | bindPreferenceSummaryToValue(findPreference(BEACON_INACTIVE_TIMEOUT_KEY)); 99 | bindPreferenceSummaryToValue(findPreference(BEACON_EXIT_TIMEOUT_KEY)); 100 | bindPreferenceSummaryToValue(findPreference(BEACON_HYSTERESIS_FACTOR_KEY)); 101 | 102 | bindPreferenceSummaryToValue(findPreference(MQTT_SERVER_KEY)); 103 | bindPreferenceSummaryToValue(findPreference(MQTT_PORT_KEY)); 104 | bindPreferenceSummaryToValue(findPreference(MQTT_USER_KEY)); 105 | bindPreferenceSummaryToValue(findPreference(MQTT_PASS_KEY)); 106 | 107 | bindPreferenceSummaryToValue(findPreference(MQTT_BEACON_STATE_TOPIC_KEY)); 108 | bindPreferenceSummaryToValue(findPreference(MQTT_BEACON_STATE_MESSAGE_KEY)); 109 | bindPreferenceSummaryToValue(findPreference(MQTT_MASTER_TOPIC_KEY)); 110 | bindPreferenceSummaryToValue(findPreference(MQTT_MASTER_ENTER_MESSAGE_KEY)); 111 | bindPreferenceSummaryToValue(findPreference(MQTT_MASTER_EXIT_MESSAGE_KEY)); 112 | bindPreferenceSummaryToValue(findPreference(MQTT_TRACK_TOPIC_KEY)); 113 | bindPreferenceSummaryToValue(findPreference(MQTT_TRACK_MESSAGE_KEY)); 114 | } 115 | 116 | private boolean validateLong(String string, Long min, Long max) { 117 | try { 118 | Long value = Long.parseLong(string); 119 | 120 | if (value >= min && value <= max) { 121 | return true; 122 | } 123 | 124 | } catch (NumberFormatException e) { 125 | e.printStackTrace(); 126 | } 127 | 128 | Toast.makeText(getApplicationContext(), 129 | getString(R.string.pref_message_invalid_value, 130 | String.format(Locale.getDefault(), "%d ... %d", min, max)), 131 | Toast.LENGTH_LONG).show(); 132 | 133 | return false; 134 | } 135 | 136 | @SuppressWarnings("SameParameterValue") 137 | private boolean validateDouble(String string, Double min, Double max) { 138 | try { 139 | Double value = Double.parseDouble(string); 140 | 141 | if (value >= min && value <= max) { 142 | return true; 143 | } 144 | 145 | } catch (NumberFormatException e) { 146 | e.printStackTrace(); 147 | } 148 | 149 | Toast.makeText(getApplicationContext(), 150 | getString(R.string.pref_message_invalid_value, 151 | String.format(Locale.getDefault(), "%.1f ... %.1f", min, max)), 152 | Toast.LENGTH_LONG).show(); 153 | 154 | return false; 155 | } 156 | 157 | private boolean validatePreference(Preference preference, String stringValue) { 158 | switch (preference.getKey()) { 159 | case BEACON_PAUSE_BETWEEN_SCANS_KEY: 160 | return validateLong(stringValue, 161 | BEACON_PAUSE_BETWEEN_SCANS_MIN, 162 | BEACON_PAUSE_BETWEEN_SCANS_MAX); 163 | case BEACON_SCAN_DURATION_KEY: 164 | return validateLong(stringValue, 165 | BEACON_SCAN_DURATION_MIN, 166 | BEACON_SCAN_DURATION_MAX); 167 | case BEACON_INACTIVE_TIMEOUT_KEY: 168 | return validateLong(stringValue, 169 | BEACON_INACTIVE_TIMEOUT_MIN, 170 | BEACON_INACTIVE_TIMEOUT_MAX); 171 | case BEACON_EXIT_TIMEOUT_KEY: 172 | return validateLong(stringValue, 173 | BEACON_EXIT_TIMEOUT_MIN, 174 | BEACON_EXIT_TIMEOUT_MAX); 175 | case BEACON_HYSTERESIS_FACTOR_KEY: 176 | return validateDouble(stringValue, 177 | BEACON_HYSTERESIS_FACTOR_MIN, 178 | BEACON_HYSTERESIS_FACTOR_MAX); 179 | case MQTT_PORT_KEY: 180 | return validateLong(stringValue, 181 | MQTT_PORT_MIN, 182 | MQTT_PORT_MAX); 183 | } 184 | return true; 185 | } 186 | 187 | @Override 188 | public void onResume() { 189 | super.onResume(); 190 | isActive = true; 191 | } 192 | 193 | @Override 194 | public void onPause() { 195 | super.onPause(); 196 | isActive = false; 197 | } 198 | 199 | private void setupActionBar() { 200 | ActionBar actionBar = getSupportActionBar(); 201 | if (actionBar != null) { 202 | // Show the Up button in the action bar. 203 | actionBar.setDisplayHomeAsUpEnabled(true); 204 | actionBar.setTitle("Settings"); 205 | } 206 | } 207 | 208 | @Override 209 | public boolean onOptionsItemSelected(MenuItem item) { 210 | if (item.getItemId() == android.R.id.home) { 211 | onBackPressed(); 212 | return true; 213 | } 214 | return super.onOptionsItemSelected(item); 215 | } 216 | 217 | private void bindPreferenceSummaryToValue(Preference preference) { 218 | // Set the listener to watch for value changes. 219 | preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); 220 | 221 | // Trigger the listener immediately with the preference's 222 | // current value. 223 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, 224 | Objects.requireNonNull(PreferenceManager 225 | .getDefaultSharedPreferences(preference.getContext()) 226 | .getString(preference.getKey(), ""))); 227 | } 228 | 229 | 230 | } 231 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/BaseBeacon.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.gson.annotations.Expose; 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | import org.joda.time.DateTime; 9 | import org.joda.time.Days; 10 | import org.joda.time.Hours; 11 | import org.joda.time.Minutes; 12 | import org.joda.time.Seconds; 13 | import org.turbo.beaconmqtt.R; 14 | import org.turbo.beaconmqtt.beaconFactory.BeaconFactory; 15 | 16 | import java.util.Locale; 17 | 18 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState; 19 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_ENTER; 20 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_EXIT; 21 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_INACTIVE; 22 | 23 | public abstract class BaseBeacon { 24 | @SuppressWarnings("unused") 25 | private static final String TAG = BaseBeacon.class.getName(); 26 | @Expose 27 | @SerializedName("id") 28 | String mId; 29 | @Expose 30 | @SerializedName("group") 31 | String mGroup; 32 | @Expose 33 | @SerializedName("tag") 34 | String mTag; 35 | @Expose 36 | @SerializedName("type") 37 | String mType; 38 | int mRssi; 39 | private BeaconFactory beaconFactory = null; 40 | private int mIndex; 41 | private boolean mMaster; 42 | private BeaconState mState = BEACON_STATE_EXIT; 43 | private DateTime mLastSeenTimeStamp; 44 | private String mLastSeenString; 45 | 46 | public String getId() { 47 | return mId; 48 | } 49 | 50 | public void setId(String id) { 51 | if (!id.equals(mId)) { 52 | this.mId = id; 53 | changeBeaconNotifyListeners(); 54 | } 55 | } 56 | 57 | public String getGroup() { 58 | return mGroup; 59 | } 60 | 61 | public void setGroup(String group) { 62 | if (!group.equals(mGroup)) { 63 | this.mGroup = group; 64 | changeBeaconNotifyListeners(); 65 | } 66 | } 67 | 68 | public String getTag() { 69 | return mTag; 70 | } 71 | 72 | public void setTag(String tag) { 73 | if (!tag.equals(mTag)) { 74 | this.mTag = tag; 75 | changeBeaconNotifyListeners(); 76 | } 77 | } 78 | 79 | public String getType() { 80 | return mType; 81 | } 82 | 83 | public String getUuid() { 84 | return ""; 85 | } 86 | 87 | public String getMajor() { 88 | return ""; 89 | } 90 | 91 | public String getMinor() { 92 | return ""; 93 | } 94 | 95 | public String getMacAddress() { 96 | return ""; 97 | } 98 | 99 | public String getSsid() { 100 | return ""; 101 | } 102 | 103 | public double getDistance() { 104 | // TODO 105 | return 1000.0d; 106 | } 107 | 108 | public int getRssi() { 109 | return mRssi; 110 | } 111 | 112 | public int getIndex() { 113 | return mIndex; 114 | } 115 | 116 | public void setIndex(int index) { 117 | this.mIndex = index; 118 | } 119 | 120 | public String getSortOrder() { 121 | return mGroup + mId; 122 | } 123 | 124 | public BeaconState getState() { 125 | return mState; 126 | } 127 | 128 | public void setState(BeaconState state) { 129 | if (mState != state) { 130 | if (state == BEACON_STATE_EXIT) { 131 | mMaster = false; 132 | } 133 | mState = state; 134 | changeBeaconNotifyListeners(state); 135 | } 136 | } 137 | 138 | private int getTimeout() { 139 | if (mLastSeenTimeStamp != null) { 140 | DateTime now = new DateTime(); 141 | return Seconds.secondsBetween(mLastSeenTimeStamp, now).getSeconds(); 142 | } 143 | 144 | return -1; 145 | } 146 | 147 | public String getLastSeenString() { 148 | return mLastSeenString; 149 | } 150 | 151 | private void updateLastSeenString() { 152 | if (mLastSeenTimeStamp != null) { 153 | Context context = beaconFactory.getApplicationContext(); 154 | DateTime now = new DateTime(); 155 | 156 | String format = context.getString(R.string.beacon_last_seen_time_format); 157 | String day = context.getString(R.string.beacon_last_seen_day); 158 | String days = context.getString(R.string.beacon_last_seen_days); 159 | String hour = context.getString(R.string.beacon_last_seen_hour); 160 | String hours = context.getString(R.string.beacon_last_seen_hours); 161 | String minute = context.getString(R.string.beacon_last_seen_minute); 162 | String minutes = context.getString(R.string.beacon_last_seen_minutes); 163 | String justNow = context.getString(R.string.beacon_last_seen_just_now); 164 | String newLastSeenString; 165 | 166 | int d = Days.daysBetween(mLastSeenTimeStamp, now).getDays(); 167 | int h = Hours.hoursBetween(mLastSeenTimeStamp, now).getHours(); 168 | int m = Minutes.minutesBetween(mLastSeenTimeStamp, now).getMinutes(); 169 | 170 | if (d > 0) { 171 | newLastSeenString = String.format(Locale.getDefault(), format, d, d > 1 ? days : day); 172 | } else if (h > 0) { 173 | newLastSeenString = String.format(Locale.getDefault(), format, h, h > 1 ? hours : hour); 174 | } else if (m > 0) { 175 | newLastSeenString = String.format(Locale.getDefault(), format, m, m > 1 ? minutes : minute); 176 | } else { 177 | newLastSeenString = justNow; 178 | } 179 | 180 | if (!newLastSeenString.equals(mLastSeenString)) { 181 | mLastSeenString = newLastSeenString; 182 | changeBeaconNotifyListeners(); 183 | } 184 | } 185 | } 186 | 187 | public boolean isMaster() { 188 | return mMaster; 189 | } 190 | 191 | public void setMaster(boolean master) { 192 | if (this.mMaster != master) { 193 | this.mMaster = master; 194 | changeBeaconNotifyListeners(); 195 | } 196 | } 197 | 198 | void see() { 199 | this.mLastSeenTimeStamp = new DateTime(); 200 | setState(BEACON_STATE_ENTER); 201 | } 202 | 203 | public void expire() { 204 | if (beaconFactory != null) { 205 | long inactiveTimeout = beaconFactory.getInactiveTimeout() / 1000; 206 | if (mState == BEACON_STATE_ENTER && 207 | getTimeout() > inactiveTimeout) { 208 | setState(BEACON_STATE_INACTIVE); 209 | } else if (mState == BEACON_STATE_EXIT) { 210 | updateLastSeenString(); 211 | } 212 | } 213 | } 214 | 215 | public void setBeaconFactory(BeaconFactory beaconFactory) { 216 | this.beaconFactory = beaconFactory; 217 | } 218 | 219 | void changeBeaconNotifyListeners() { 220 | if (beaconFactory != null) { 221 | beaconFactory.changeBeaconNotifyListeners(this, null); 222 | } 223 | } 224 | 225 | private void changeBeaconNotifyListeners(BeaconState state) { 226 | if (beaconFactory != null) { 227 | beaconFactory.changeBeaconNotifyListeners(this, state); 228 | } 229 | } 230 | 231 | public boolean isValid() { 232 | boolean isValid = Helper.validateId(mId); 233 | isValid &= Helper.validateGroup(mGroup); 234 | isValid &= Helper.validateTag(mTag); 235 | return isValid; 236 | } 237 | } 238 | 239 | 240 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/BaseBleBeacon.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import org.altbeacon.beacon.Beacon; 7 | 8 | public abstract class BaseBleBeacon extends BaseBeacon { 9 | @SuppressWarnings("unused") 10 | private static final String TAG = "BseBleBeacon"; 11 | 12 | @Expose 13 | @SerializedName("mac") 14 | String mBluetoothAddress; 15 | private transient double mDistance; 16 | 17 | public void setRunningData(Beacon beacon) { 18 | see(); 19 | mDistance = beacon.getDistance(); 20 | mRssi = beacon.getRssi(); 21 | changeBeaconNotifyListeners(); 22 | } 23 | 24 | @Override 25 | public double getDistance() { 26 | return mDistance; 27 | } 28 | 29 | public String getMacAddress() { 30 | return mBluetoothAddress; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/EddystoneUidBeacon.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | @SuppressWarnings("unused") 4 | final public class EddystoneUidBeacon extends BaseBleBeacon { 5 | // TODO: Put proper layout here 6 | public final static String BEACON_LAYOUT = ""; 7 | public final static String BEACON_EDDYSTONE_UID = "Eddystone-UID"; 8 | 9 | private String mNamespace; 10 | private String mInstance; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/Helper.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | import org.altbeacon.beacon.Beacon; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | import static java.lang.Integer.parseInt; 8 | import static org.turbo.beaconmqtt.beacon.EddystoneUidBeacon.BEACON_EDDYSTONE_UID; 9 | 10 | public class Helper { 11 | private static final String STATE_EXIT = "exit"; 12 | private static final String STATE_ENTER = "enter"; 13 | private static final String STATE_UNKNOWN = "unknown"; 14 | private static final int BEACON_TYPE_CODE_IBEACON = 0x4C000215; 15 | private static final int BEACON_TYPE_CODE_ALTBEACON = 0xBEAC; 16 | private static final int BEACON_TYPE_CODE_EDDYSTONE_UID = 0x00; 17 | private static final int BEACON_SERVICE_UUID_EDDYSTONE = 0xFEAA; 18 | private static final String BEACON_UNKNOWN = "unknown"; 19 | 20 | public static String getBleBeaconString(Beacon beacon) { 21 | if (beacon.getServiceUuid() == BEACON_SERVICE_UUID_EDDYSTONE) { 22 | if (beacon.getBeaconTypeCode() == BEACON_TYPE_CODE_EDDYSTONE_UID) { 23 | return BEACON_EDDYSTONE_UID; 24 | } 25 | } else { 26 | if (beacon.getBeaconTypeCode() == BEACON_TYPE_CODE_IBEACON) { 27 | return IBeacon.BEACON_IBEACON; 28 | } else if (beacon.getBeaconTypeCode() == BEACON_TYPE_CODE_ALTBEACON) { 29 | // TODO: Altbeacon 30 | return BEACON_UNKNOWN; 31 | } 32 | } 33 | return BEACON_UNKNOWN; 34 | } 35 | 36 | public static String getStateString(BeaconState state) { 37 | if (state.equals(Helper.BeaconState.BEACON_STATE_EXIT)) { 38 | return STATE_EXIT; 39 | } else //noinspection StatementWithEmptyBody 40 | if (state.equals(Helper.BeaconState.BEACON_STATE_INACTIVE)) { 41 | } else if (state.equals(Helper.BeaconState.BEACON_STATE_ENTER)) { 42 | return STATE_ENTER; 43 | } 44 | return STATE_UNKNOWN; 45 | } 46 | 47 | public static boolean validateUuid(String uuid) { 48 | if (uuid == null) return false; 49 | final Pattern pattern = Pattern.compile("^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$"); 50 | return pattern.matcher(uuid).matches(); 51 | } 52 | 53 | public static boolean validateInt(String string, int min, int max) { 54 | try { 55 | int value = parseInt(string); 56 | 57 | if (value >= min && value <= max) { 58 | return true; 59 | } 60 | 61 | } catch (NumberFormatException e) { 62 | e.printStackTrace(); 63 | } 64 | 65 | return false; 66 | } 67 | 68 | public static boolean validateMacAddress(String macAddress) { 69 | if (macAddress == null) return false; 70 | if (macAddress.isEmpty()) return true; 71 | final Pattern pattern = Pattern.compile("^[0-9a-zA-Z]{2}:[0-9a-zA-Z]{2}:[0-9a-zA-Z]{2}:[0-9a-zA-Z]{2}:[0-9a-zA-Z]{2}:[0-9a-zA-Z]{2}$"); 72 | return pattern.matcher(macAddress).matches(); 73 | } 74 | 75 | private static boolean validateString(String string, String regex) { 76 | if (string == null) return false; 77 | final Pattern pattern = Pattern.compile(regex); 78 | return pattern.matcher(string).matches(); 79 | } 80 | 81 | public static boolean validateSsid(String ssid) { 82 | return validateString(ssid, ".{1,32}$"); 83 | } 84 | 85 | public static boolean validateId(String id) { 86 | return validateString(id, ".{1,32}$"); 87 | } 88 | 89 | public static boolean validateGroup(String group) { 90 | return validateString(group, ".{0,32}$"); 91 | } 92 | 93 | public static boolean validateTag(String tag) { 94 | return validateString(tag, ".{0,32}$"); 95 | } 96 | 97 | public enum BeaconState { 98 | BEACON_STATE_EXIT, 99 | BEACON_STATE_INACTIVE, 100 | BEACON_STATE_ENTER 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/IBeacon.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.gson.annotations.Expose; 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | import org.altbeacon.beacon.Region; 9 | 10 | public final class IBeacon extends BaseBleBeacon { 11 | public final static String BEACON_LAYOUT = "m:0-3=4c000215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"; 12 | public final static String BEACON_IBEACON = "iBeacon"; 13 | @Expose 14 | private final IBeaconData iBeaconData = new IBeaconData(); 15 | 16 | public IBeacon() { 17 | mType = BEACON_IBEACON; 18 | } 19 | 20 | public IBeacon(String id, String group, String tag, 21 | String uuid, String major, String minor, String mac) { 22 | mId = id; 23 | mGroup = group; 24 | mTag = tag; 25 | mType = BEACON_IBEACON; 26 | iBeaconData.mUuid = uuid; 27 | iBeaconData.mMajor = major; 28 | iBeaconData.mMinor = minor; 29 | mBluetoothAddress = mac.trim(); 30 | } 31 | 32 | public IBeacon(String id, String group, String tag, 33 | @NonNull Region region) { 34 | mId = id; 35 | mGroup = group; 36 | mTag = tag; 37 | mType = BEACON_IBEACON; 38 | iBeaconData.mUuid = region.getId1().toString(); 39 | iBeaconData.mMajor = region.getId2().toString(); 40 | iBeaconData.mMinor = region.getId3().toString(); 41 | mBluetoothAddress = region.getBluetoothAddress(); 42 | } 43 | 44 | public IBeacon(TransactionBeacon transactionBeacon) { 45 | mId = transactionBeacon.getId(); 46 | mGroup = transactionBeacon.getGroup(); 47 | mTag = transactionBeacon.getTag(); 48 | mType = BEACON_IBEACON; 49 | iBeaconData.mUuid = transactionBeacon.getUuid(); 50 | iBeaconData.mMajor = transactionBeacon.getMajor(); 51 | iBeaconData.mMinor = transactionBeacon.getMinor(); 52 | mBluetoothAddress = transactionBeacon.getMacAddress(); 53 | } 54 | 55 | @Override 56 | public String getUuid() { 57 | return iBeaconData.mUuid; 58 | } 59 | 60 | @Override 61 | public String getMajor() { 62 | return iBeaconData.mMajor; 63 | } 64 | 65 | @Override 66 | public String getMinor() { 67 | return iBeaconData.mMinor; 68 | } 69 | 70 | public boolean isValid() { 71 | boolean isValid = super.isValid(); 72 | isValid &= Helper.validateUuid(iBeaconData.mUuid); 73 | isValid &= Helper.validateInt(iBeaconData.mMajor, 1, 65535); 74 | isValid &= Helper.validateInt(iBeaconData.mMinor, 1, 65535); 75 | isValid &= Helper.validateMacAddress(mBluetoothAddress); 76 | return isValid; 77 | } 78 | 79 | private class IBeaconData { 80 | @Expose 81 | @SerializedName("uuid") 82 | private String mUuid; 83 | @Expose 84 | @SerializedName("major") 85 | private String mMajor; 86 | @Expose 87 | @SerializedName("minor") 88 | private String mMinor; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/TransactionBeacon.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | import static org.turbo.beaconmqtt.beacon.IBeacon.BEACON_IBEACON; 4 | import static org.turbo.beaconmqtt.beacon.WifiBeacon.BEACON_WIFI; 5 | 6 | public class TransactionBeacon extends BaseBeacon { 7 | private String mUuid; 8 | private String mMajor; 9 | private String mMinor; 10 | private String mBluetoothAddress; 11 | private String mSsid; 12 | 13 | public TransactionBeacon() { 14 | } 15 | 16 | public TransactionBeacon(BaseBeacon beacon) { 17 | try { 18 | mType = beacon.getType(); 19 | mId = beacon.getId(); 20 | mGroup = beacon.getGroup(); 21 | mTag = beacon.getTag(); 22 | if (beacon instanceof IBeacon) { 23 | mUuid = beacon.getUuid(); 24 | mMajor = beacon.getMajor(); 25 | mMinor = beacon.getMinor(); 26 | mBluetoothAddress = beacon.getMacAddress(); 27 | } else if (beacon instanceof WifiBeacon) { 28 | mSsid = beacon.getSsid(); 29 | } 30 | } catch (NullPointerException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | @Override 36 | public String getUuid() { 37 | return mUuid; 38 | } 39 | 40 | public void setUuid(String uuid) { 41 | this.mUuid = uuid; 42 | } 43 | 44 | @Override 45 | public String getMajor() { 46 | return mMajor; 47 | } 48 | 49 | public void setMajor(String major) { 50 | this.mMajor = major; 51 | } 52 | 53 | @Override 54 | public String getMinor() { 55 | return mMinor; 56 | } 57 | 58 | public void setMinor(String minor) { 59 | this.mMinor = minor; 60 | } 61 | 62 | @Override 63 | public String getMacAddress() { 64 | return mBluetoothAddress; 65 | } 66 | 67 | public void setMacAddress(String bluetoothAddress) { 68 | this.mBluetoothAddress = bluetoothAddress.trim(); 69 | } 70 | 71 | @Override 72 | public String getSsid() { 73 | return mSsid; 74 | } 75 | 76 | public void setSsid(String ssid) { 77 | this.mSsid = ssid; 78 | } 79 | 80 | public void setType(String type) { 81 | this.mType = type; 82 | } 83 | 84 | public boolean isValid() { 85 | boolean isValid = true; 86 | 87 | if (BEACON_IBEACON.equals(mType)) { 88 | isValid = Helper.validateUuid(mUuid); 89 | isValid &= Helper.validateInt(mMajor, 1, 65535); 90 | isValid &= Helper.validateInt(mMinor, 1, 65535); 91 | isValid &= Helper.validateMacAddress(mBluetoothAddress); 92 | } else if (BEACON_WIFI.equals(mType)) { 93 | isValid = Helper.validateSsid(mSsid); 94 | } 95 | 96 | return isValid; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beacon/WifiBeacon.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beacon; 2 | 3 | import android.net.wifi.WifiInfo; 4 | 5 | import com.google.gson.annotations.Expose; 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | public final class WifiBeacon extends BaseBeacon { 9 | public final static String BEACON_WIFI = "Wi-Fi"; 10 | @Expose 11 | private final WifiBeaconData wifiBeaconData = new WifiBeaconData(); 12 | 13 | public WifiBeacon() { 14 | mType = BEACON_WIFI; 15 | } 16 | 17 | public WifiBeacon(String id, 18 | String group, 19 | String tag, 20 | String ssid) { 21 | mId = id; 22 | mGroup = group; 23 | mTag = tag; 24 | mType = BEACON_WIFI; 25 | wifiBeaconData.mSsid = ssid; 26 | } 27 | 28 | public WifiBeacon(TransactionBeacon transactionBeacon) { 29 | mId = transactionBeacon.getId(); 30 | mGroup = transactionBeacon.getGroup(); 31 | mTag = transactionBeacon.getTag(); 32 | mType = BEACON_WIFI; 33 | wifiBeaconData.mSsid = transactionBeacon.getSsid(); 34 | } 35 | 36 | @Override 37 | public String getSsid() { 38 | return wifiBeaconData.mSsid; 39 | } 40 | 41 | public void setRunningData(WifiInfo wifiInfo) { 42 | see(); 43 | mRssi = wifiInfo.getRssi(); 44 | changeBeaconNotifyListeners(); 45 | } 46 | 47 | public boolean isValid() { 48 | boolean isValid = super.isValid(); 49 | isValid &= Helper.validateSsid(wifiBeaconData.mSsid); 50 | return isValid; 51 | } 52 | 53 | private class WifiBeaconData { 54 | @Expose 55 | @SerializedName("ssid") 56 | private String mSsid; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beaconFactory/BeaconFactoryAdapter.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beaconFactory; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.CardView; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import org.turbo.beaconmqtt.R; 15 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 16 | import org.turbo.beaconmqtt.beacon.BaseBleBeacon; 17 | import org.turbo.beaconmqtt.beacon.WifiBeacon; 18 | import org.turbo.beaconmqtt.dialog.ContextBeaconDialogFragment; 19 | 20 | import java.util.List; 21 | import java.util.Locale; 22 | 23 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_ENTER; 24 | import static org.turbo.beaconmqtt.beacon.Helper.BeaconState.BEACON_STATE_INACTIVE; 25 | 26 | public class BeaconFactoryAdapter extends RecyclerView.Adapter { 27 | @SuppressWarnings("unused") 28 | private static final String TAG = "BeaconFactoryAdapter"; 29 | final private Context activityContext; 30 | 31 | private final List beaconList; 32 | 33 | public BeaconFactoryAdapter(Context activityContext, List beaconList) { 34 | this.activityContext = activityContext; 35 | this.beaconList = beaconList; 36 | } 37 | 38 | public void updateFactory() { 39 | notifyDataSetChanged(); 40 | } 41 | 42 | public void updateBeacon(int position) { 43 | notifyItemChanged(position, "dummy"); 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | return beaconList.size(); 49 | } 50 | 51 | @NonNull 52 | @Override 53 | public BeaconFactoryAdapter.BeaconViewHolder onCreateViewHolder(ViewGroup viewGroup, final int position) { 54 | View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_beacon_card, 55 | viewGroup, 56 | false); 57 | return new BeaconViewHolder(v); 58 | } 59 | 60 | private void drawIBeaconCard(@NonNull BeaconFactoryAdapter.BeaconViewHolder beaconViewHolder, 61 | BaseBeacon beacon) { 62 | beaconViewHolder.beaconIcon.setImageResource(R.drawable.ic_bluetooth_beacon_w_radiation); 63 | 64 | if (beacon.getDistance() > 0.0f) { 65 | if (beacon.getState() == BEACON_STATE_ENTER || beacon.getState() == BEACON_STATE_INACTIVE) { 66 | beaconViewHolder.beaconDetails 67 | .setText(String.format(Locale.ROOT, "%.1f m", beacon.getDistance())); 68 | } 69 | } 70 | } 71 | 72 | private void drawWifiBeaconCard(@NonNull BeaconFactoryAdapter.BeaconViewHolder beaconViewHolder, 73 | BaseBeacon beacon) { 74 | beaconViewHolder.beaconIcon.setImageResource(R.drawable.ic_access_point); 75 | 76 | if (beacon.getState() == BEACON_STATE_ENTER || beacon.getState() == BEACON_STATE_INACTIVE) { 77 | beaconViewHolder.beaconDetails 78 | .setText(String.format(Locale.ROOT, "%d dBm", beacon.getRssi())); 79 | } 80 | } 81 | 82 | private void drawExitBeaconCard(@NonNull BeaconFactoryAdapter.BeaconViewHolder beaconViewHolder, 83 | BaseBeacon beacon) { 84 | beaconViewHolder.beaconIcon.setAlpha(0.0f); 85 | String lastSeen = beacon.getLastSeenString(); 86 | if (lastSeen != null) { 87 | beaconViewHolder.beaconDetails.setText(activityContext.getString(R.string.beacon_last_seen_format, lastSeen)); 88 | } 89 | 90 | beaconViewHolder.beaconDetails.setAlpha(0.5f); 91 | } 92 | 93 | private void drawEnterBeaconCard(@NonNull BeaconFactoryAdapter.BeaconViewHolder beaconViewHolder, 94 | BaseBeacon beacon) { 95 | if (beacon.isMaster()) { 96 | beaconViewHolder.beaconIcon.setAlpha(1.0f); 97 | } else { 98 | beaconViewHolder.beaconIcon.setAlpha(0.5f); 99 | } 100 | beaconViewHolder.beaconDetails.setAlpha(1.0f); 101 | } 102 | 103 | @SuppressWarnings("unused") 104 | private void drawInactiveBeaconCard(@NonNull BeaconFactoryAdapter.BeaconViewHolder beaconViewHolder, 105 | BaseBeacon beacon) { 106 | 107 | beaconViewHolder.beaconIcon.setAlpha(0.5f); 108 | beaconViewHolder.beaconDetails.setAlpha(0.5f); 109 | } 110 | 111 | @Override 112 | public void onBindViewHolder(@NonNull BeaconFactoryAdapter.BeaconViewHolder beaconViewHolder, 113 | final int position) { 114 | if (position == 0) { 115 | ViewGroup.MarginLayoutParams layoutParams = 116 | (ViewGroup.MarginLayoutParams) beaconViewHolder.cv.getLayoutParams(); 117 | int marign = layoutParams.getMarginEnd(); 118 | layoutParams.setMargins(marign, marign, marign, marign); 119 | beaconViewHolder.cv.setLayoutParams(layoutParams); 120 | } 121 | 122 | beaconViewHolder.cv.setTag(position); 123 | beaconViewHolder.cv.setOnLongClickListener(new BeaconFactoryAdapter.OnBeaconLongClickListener()); 124 | 125 | BaseBeacon beacon = beaconList.get(position); 126 | 127 | beaconViewHolder.beaconIcon.setImageResource(0); 128 | 129 | beaconViewHolder.beaconId.setAlpha(1.0f); 130 | beaconViewHolder.beaconId.setText(beacon.getId().toUpperCase(Locale.ROOT)); 131 | beaconViewHolder.beaconDetails.setText(""); 132 | 133 | if (beacon.isValid()) { 134 | // Ble-specific case 135 | if (beacon instanceof BaseBleBeacon) { 136 | drawIBeaconCard(beaconViewHolder, beacon); 137 | } 138 | // Wifi-specific case 139 | else if (beacon instanceof WifiBeacon) { 140 | drawWifiBeaconCard(beaconViewHolder, beacon); 141 | } 142 | 143 | // State-specific cases 144 | switch (beacon.getState()) { 145 | case BEACON_STATE_EXIT: 146 | drawExitBeaconCard(beaconViewHolder, beacon); 147 | break; 148 | case BEACON_STATE_ENTER: 149 | drawEnterBeaconCard(beaconViewHolder, beacon); 150 | break; 151 | case BEACON_STATE_INACTIVE: 152 | drawInactiveBeaconCard(beaconViewHolder, beacon); 153 | break; 154 | } 155 | } 156 | // Invalid-specific case 157 | else { 158 | beaconViewHolder.beaconDetails.setText(R.string.invalid_config); 159 | beaconViewHolder.beaconDetails.setAlpha(0.5f); 160 | beaconViewHolder.beaconId.setAlpha(0.5f); 161 | } 162 | } 163 | 164 | @Override 165 | public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 166 | super.onAttachedToRecyclerView(recyclerView); 167 | } 168 | 169 | public static class BeaconViewHolder extends RecyclerView.ViewHolder { 170 | final CardView cv; 171 | 172 | final ImageView beaconIcon; 173 | final TextView beaconId; 174 | final TextView beaconDetails; 175 | 176 | BeaconViewHolder(View itemView) { 177 | super(itemView); 178 | 179 | cv = itemView.findViewById(R.id.cv); 180 | 181 | beaconIcon = itemView.findViewById(R.id.beacon_icon); 182 | beaconId = itemView.findViewById(R.id.beacon_id); 183 | beaconDetails = itemView.findViewById(R.id.beacon_details); 184 | } 185 | } 186 | 187 | private class OnBeaconLongClickListener implements View.OnLongClickListener { 188 | @Override 189 | public boolean onLongClick(View view) { 190 | final int position = (int) view.getTag(); 191 | final BaseBeacon beacon = beaconList.get(position); 192 | 193 | ContextBeaconDialogFragment newFragment = ContextBeaconDialogFragment.newInstance( 194 | beacon.getId()); 195 | newFragment.show(((AppCompatActivity) activityContext).getSupportFragmentManager(), 196 | "ContextBeaconDialog"); 197 | 198 | return true; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/beaconFactory/BeaconFactoryChangeListener.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.beaconFactory; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 6 | import org.turbo.beaconmqtt.beacon.Helper; 7 | 8 | public interface BeaconFactoryChangeListener { 9 | void onChangeFactory(); 10 | 11 | void onChangeBeacon(@NonNull BaseBeacon beacon, Helper.BeaconState state); 12 | 13 | void onTrackBeacon(@NonNull BaseBeacon beacon); 14 | 15 | void onEnterMaster(@NonNull BaseBeacon beacon); 16 | 17 | void onExitMaster(@NonNull BaseBeacon beacon); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/broadcaster/BaseBroadcaster.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.broadcaster; 2 | 3 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 4 | import org.turbo.beaconmqtt.beacon.Helper; 5 | 6 | import java.net.NetworkInterface; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Locale; 10 | 11 | public abstract class BaseBroadcaster { 12 | private final transient String mMacAddress; 13 | String mName; 14 | transient boolean mState; 15 | @SuppressWarnings("unused") 16 | private int mPort; 17 | 18 | BaseBroadcaster() { 19 | mMacAddress = getMacAddress(); 20 | } 21 | 22 | private static String getMacAddress() { 23 | try { 24 | List all = Collections.list(NetworkInterface.getNetworkInterfaces()); 25 | for (NetworkInterface nif : all) { 26 | if (!nif.getName().equalsIgnoreCase("wlan0")) continue; 27 | 28 | byte[] macBytes = nif.getHardwareAddress(); 29 | if (macBytes == null) { 30 | return ""; 31 | } 32 | 33 | StringBuilder res1 = new StringBuilder(); 34 | for (byte b : macBytes) { 35 | res1.append(String.format("%02X", b)); 36 | } 37 | 38 | return res1.toString(); 39 | } 40 | } catch (Exception ex) { 41 | ex.printStackTrace(); 42 | } 43 | 44 | return "020000000000"; 45 | } 46 | 47 | public boolean getState() { 48 | return mState; 49 | } 50 | 51 | public String getName() { 52 | return mName; 53 | } 54 | 55 | @SuppressWarnings("unused") 56 | public int getPort() { 57 | return mPort; 58 | } 59 | 60 | public abstract void state(BaseBeacon beacon); 61 | 62 | public abstract void enterMaster(BaseBeacon beacon); 63 | 64 | public abstract void exitMaster(BaseBeacon beacon); 65 | 66 | public abstract void track(BaseBeacon beacon); 67 | 68 | String parseStaticTokens(String string, BaseBeacon beacon) { 69 | string = string.replaceAll("%beacon%", beacon.getId()); 70 | string = string.replaceAll("%group%", beacon.getGroup()); 71 | string = string.replaceAll("%tag%", beacon.getTag()); 72 | string = string.replaceAll("%state%", Helper.getStateString(beacon.getState())); 73 | string = string.replaceAll("%mac%", mMacAddress.toLowerCase(Locale.ROOT)); 74 | return string; 75 | } 76 | 77 | String parseDynamicTokens(String string, BaseBeacon beacon) { 78 | string = string.replaceAll("%rssi%", 79 | String.format(Locale.getDefault(), "%d", beacon.getRssi())); 80 | string = string.replaceAll("%distance%", 81 | String.format(Locale.getDefault(), "%.1f", beacon.getDistance())); 82 | return string; 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/broadcaster/Broadcaster.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.broadcaster; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class Broadcaster { 12 | private static final String TAG = Broadcaster.class.getName(); 13 | private final List broadcasters = new ArrayList<>(); 14 | 15 | private final List listeners = new ArrayList<>(); 16 | 17 | public Broadcaster(final Context context) { 18 | broadcasters.add(new MqttBroadcaster(this, context)); 19 | } 20 | 21 | public void publishState(BaseBeacon beacon) { 22 | for (BaseBroadcaster broadcaster : broadcasters) { 23 | broadcaster.state(beacon); 24 | } 25 | } 26 | 27 | public void publishEnterMaster(BaseBeacon beacon) { 28 | Log.i(TAG, "publishEnterMaster"); 29 | for (BaseBroadcaster broadcaster : broadcasters) { 30 | broadcaster.enterMaster(beacon); 31 | } 32 | } 33 | 34 | public void publishExitMaster(BaseBeacon beacon) { 35 | for (BaseBroadcaster broadcaster : broadcasters) { 36 | broadcaster.exitMaster(beacon); 37 | } 38 | } 39 | 40 | public void publishTrack(BaseBeacon beacon) { 41 | for (BaseBroadcaster broadcaster : broadcasters) { 42 | broadcaster.track(beacon); 43 | } 44 | } 45 | 46 | public List getBroadcasters() { 47 | return broadcasters; 48 | } 49 | 50 | public void notifyListeners() { 51 | for (BroadcasterChangeListener listener : listeners) { 52 | listener.onChangedBroadcaster(broadcasters); 53 | } 54 | } 55 | 56 | public void addChangeListener(BroadcasterChangeListener listener) { 57 | listeners.add(listener); 58 | } 59 | 60 | public void removeChangeListener(BroadcasterChangeListener listener) { 61 | listeners.remove(listener); 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/broadcaster/BroadcasterChangeListener.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.broadcaster; 2 | 3 | import java.util.List; 4 | 5 | public interface BroadcasterChangeListener { 6 | void onChangedBroadcaster(List broadcasters); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/broadcaster/MqttBroadcaster.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.broadcaster; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import android.widget.Toast; 9 | 10 | import org.eclipse.paho.android.service.MqttAndroidClient; 11 | import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; 12 | import org.eclipse.paho.client.mqttv3.IMqttActionListener; 13 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 14 | import org.eclipse.paho.client.mqttv3.IMqttToken; 15 | import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; 16 | import org.eclipse.paho.client.mqttv3.MqttClient; 17 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 18 | import org.eclipse.paho.client.mqttv3.MqttException; 19 | import org.eclipse.paho.client.mqttv3.MqttMessage; 20 | import org.turbo.beaconmqtt.BeaconApplication; 21 | import org.turbo.beaconmqtt.SettingsActivity; 22 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 23 | 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.Objects; 26 | 27 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_BEACON_STATE_MESSAGE_KEY; 28 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_BEACON_STATE_TOPIC_KEY; 29 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_ENTER_MESSAGE_KEY; 30 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_EXIT_MESSAGE_KEY; 31 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_TOPIC_KEY; 32 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_PASS_KEY; 33 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_PORT_KEY; 34 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_SERVER_KEY; 35 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_TRACK_MESSAGE_KEY; 36 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_TRACK_TOPIC_KEY; 37 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_USER_KEY; 38 | 39 | public class MqttBroadcaster extends BaseBroadcaster { 40 | private static final String TAG = MqttBroadcaster.class.getName(); 41 | @SuppressWarnings("unused") 42 | private static final int QOS_LEVEL = 2; 43 | private final Context context; 44 | private final Broadcaster broadcaster; 45 | private final SharedPreferences defaultSharedPreferences; 46 | private MqttAndroidClient client; 47 | @SuppressWarnings("FieldCanBeLocal") 48 | private MqttCallbackExtended callback; 49 | private int mqttMessageId = 0; 50 | @SuppressWarnings("FieldCanBeLocal") 51 | private SharedPreferences.OnSharedPreferenceChangeListener onSharedPreferenceChangeListener; 52 | 53 | public MqttBroadcaster(final Broadcaster broadcaster, final Context context) { 54 | this.broadcaster = broadcaster; 55 | this.context = context; 56 | defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); 57 | 58 | registerSettingsChangeListener(); 59 | 60 | mName = defaultSharedPreferences.getString(MQTT_SERVER_KEY, null); 61 | String mqttPort = defaultSharedPreferences.getString(MQTT_PORT_KEY, null); 62 | String mqttUser = defaultSharedPreferences.getString(MQTT_USER_KEY, null); 63 | String mqttPassword = defaultSharedPreferences.getString(MQTT_PASS_KEY, null); 64 | 65 | connectToMqttServer(mName, mqttPort, mqttUser, mqttPassword); 66 | } 67 | 68 | private void registerSettingsChangeListener() { 69 | onSharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { 70 | @Override 71 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 72 | if (MQTT_SERVER_KEY.equals(key) || MQTT_PORT_KEY.equals(key) || 73 | MQTT_USER_KEY.equals(key) || MQTT_PASS_KEY.equals(key)) { 74 | mName = defaultSharedPreferences.getString(MQTT_SERVER_KEY, null); 75 | String mqttPort = defaultSharedPreferences.getString(MQTT_PORT_KEY, null); 76 | String mqttUser = defaultSharedPreferences.getString(MQTT_USER_KEY, null); 77 | String mqttPassword = defaultSharedPreferences.getString(MQTT_PASS_KEY, null); 78 | connectToMqttServer(mName, mqttPort, mqttUser, mqttPassword); 79 | broadcaster.notifyListeners(); 80 | } 81 | } 82 | }; 83 | defaultSharedPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); 84 | } 85 | 86 | private void connectToMqttServer(final String mqttServer, String mqttPort, String mqttUser, String mqttPassword) { 87 | if (mqttServer != null && mqttPort != null) { 88 | final String serverUri = "tcp://" + mqttServer + ":" + mqttPort; 89 | String clientId = MqttClient.generateClientId(); 90 | client = new MqttAndroidClient(context, serverUri, 91 | clientId); 92 | 93 | MqttConnectOptions options = new MqttConnectOptions(); 94 | //noinspection StatementWithEmptyBody 95 | if (TextUtils.isEmpty(mqttUser) || TextUtils.isEmpty(mqttPassword)) { 96 | } else { 97 | options.setUserName(mqttUser); 98 | options.setPassword(mqttPassword.toCharArray()); 99 | } 100 | options.setAutomaticReconnect(true); 101 | options.setCleanSession(false); 102 | 103 | try { 104 | IMqttToken token = client.connect(options); 105 | token.setActionCallback(new IMqttActionListener() { 106 | @Override 107 | public void onSuccess(IMqttToken asyncActionToken) { 108 | //debugLog("MQTT connected!"); 109 | DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions(); 110 | disconnectedBufferOptions.setBufferEnabled(true); 111 | disconnectedBufferOptions.setBufferSize(100); 112 | disconnectedBufferOptions.setPersistBuffer(false); 113 | disconnectedBufferOptions.setDeleteOldestMessages(false); 114 | client.setBufferOpts(disconnectedBufferOptions); 115 | } 116 | 117 | @Override 118 | public void onFailure(IMqttToken asyncActionToken, Throwable exception) { 119 | //debugLog("MQTT connection failed!"); 120 | 121 | if (SettingsActivity.isActive()) { 122 | Toast.makeText(context, "MQTT connection failed!", Toast.LENGTH_LONG).show(); 123 | } 124 | mState = false; 125 | broadcaster.notifyListeners(); 126 | } 127 | 128 | }); 129 | } catch (MqttException e) { 130 | e.printStackTrace(); 131 | } 132 | 133 | callback = new MqttCallbackExtended() { 134 | @Override 135 | public void connectionLost(Throwable t) { 136 | //debugLog("MQTT connection lost!"); 137 | mState = false; 138 | broadcaster.notifyListeners(); 139 | } 140 | 141 | //Notice to application that connection is up 142 | @Override 143 | public void connectComplete(boolean reconnect, String url) { 144 | //noinspection StatementWithEmptyBody 145 | if (reconnect) { 146 | //debugLog("MQTT reconnected to " + url); 147 | } else { 148 | //debugLog("MQTT connected to " + url); 149 | if (SettingsActivity.isActive()) { 150 | Toast.makeText(context, "MQTT connected to " + url, Toast.LENGTH_LONG).show(); 151 | } 152 | } 153 | mState = true; 154 | broadcaster.notifyListeners(); 155 | } 156 | 157 | @Override 158 | public void messageArrived(String topic, MqttMessage message) { 159 | //debugLog("MQTT messageArrived() called"); 160 | } 161 | 162 | @Override 163 | public void deliveryComplete(IMqttDeliveryToken token) { 164 | BeaconApplication application = ((BeaconApplication) context.getApplicationContext()); 165 | int messageId = -1; 166 | 167 | try { 168 | messageId = token.getMessage().getId(); 169 | } catch (Exception e) { 170 | e.printStackTrace(); 171 | } 172 | 173 | application.debugLog("MQTT message ID:" + messageId + " delivered"); 174 | } 175 | }; 176 | 177 | client.setCallback(callback); 178 | } 179 | } 180 | 181 | 182 | private void mqttPublish(String topic, String payload, String method) { 183 | BeaconApplication application = ((BeaconApplication) context.getApplicationContext()); 184 | byte[] encodedPayload; 185 | 186 | try { 187 | encodedPayload = payload.getBytes(StandardCharsets.UTF_8); 188 | MqttMessage message = new MqttMessage(encodedPayload); 189 | message.setId(mqttMessageId++); 190 | message.setRetained(true); 191 | 192 | if (client != null) { 193 | client.publish(topic, message); 194 | application.debugLog("Send " + method + " MQTT message ID:" + message.getId()); 195 | } 196 | } catch (NullPointerException | MqttException e) { 197 | e.printStackTrace(); 198 | } 199 | } 200 | 201 | @Override 202 | public void state(BaseBeacon beacon) { 203 | String topic = defaultSharedPreferences.getString(MQTT_BEACON_STATE_TOPIC_KEY, ""); 204 | 205 | if (!Objects.requireNonNull(topic).isEmpty()) { 206 | topic = parseStaticTokens(topic, beacon); 207 | 208 | String payload = defaultSharedPreferences.getString(MQTT_BEACON_STATE_MESSAGE_KEY, ""); 209 | payload = parseStaticTokens(payload, beacon); 210 | 211 | mqttPublish(topic, payload, "state"); 212 | } 213 | } 214 | 215 | @Override 216 | public void enterMaster(BaseBeacon beacon) { 217 | Log.i(TAG, "enterMaster"); 218 | String topic = defaultSharedPreferences.getString(MQTT_MASTER_TOPIC_KEY, ""); 219 | 220 | 221 | if (!Objects.requireNonNull(topic).isEmpty()) { 222 | topic = parseStaticTokens(topic, beacon); 223 | 224 | String payload = defaultSharedPreferences.getString(MQTT_MASTER_ENTER_MESSAGE_KEY, ""); 225 | payload = parseStaticTokens(payload, beacon); 226 | 227 | Log.i(TAG, "topic: " + topic + ", payload: " + payload); 228 | 229 | mqttPublish(topic, payload, "onEnterMaster"); 230 | } 231 | 232 | } 233 | 234 | @Override 235 | public void exitMaster(BaseBeacon beacon) { 236 | String topic = defaultSharedPreferences.getString(MQTT_MASTER_TOPIC_KEY, ""); 237 | 238 | if (!Objects.requireNonNull(topic).isEmpty()) { 239 | topic = parseStaticTokens(topic, beacon); 240 | 241 | String payload = defaultSharedPreferences.getString(MQTT_MASTER_EXIT_MESSAGE_KEY, ""); 242 | payload = parseStaticTokens(payload, beacon); 243 | 244 | mqttPublish(topic, payload, "onExitMaster"); 245 | } 246 | 247 | } 248 | 249 | @Override 250 | public void track(BaseBeacon beacon) { 251 | String topic = defaultSharedPreferences.getString(MQTT_TRACK_TOPIC_KEY, ""); 252 | 253 | if (!Objects.requireNonNull(topic).isEmpty()) { 254 | topic = parseStaticTokens(topic, beacon); 255 | 256 | String payload = defaultSharedPreferences.getString(MQTT_TRACK_MESSAGE_KEY, ""); 257 | payload = parseStaticTokens(payload, beacon); 258 | payload = parseDynamicTokens(payload, beacon); 259 | 260 | mqttPublish(topic, payload, "track"); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/dialog/ContextBeaconDialogFragment.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.DialogFragment; 8 | import android.support.v7.app.AlertDialog; 9 | 10 | import org.turbo.beaconmqtt.BeaconApplication; 11 | import org.turbo.beaconmqtt.R; 12 | import org.turbo.beaconmqtt.beacon.TransactionBeacon; 13 | 14 | import java.util.Objects; 15 | 16 | public class ContextBeaconDialogFragment extends DialogFragment { 17 | @SuppressWarnings("unused") 18 | final protected String TAG = ContextBeaconDialogFragment.class.getName(); 19 | 20 | public static ContextBeaconDialogFragment newInstance(String beaconId) { 21 | ContextBeaconDialogFragment fragment = new ContextBeaconDialogFragment(); 22 | Bundle args = new Bundle(); 23 | args.putString("beaconId", beaconId); 24 | fragment.setArguments(args); 25 | return fragment; 26 | } 27 | 28 | @NonNull 29 | @Override 30 | public Dialog onCreateDialog(Bundle savedInstanceState) { 31 | final String beaconId = Objects.requireNonNull(getArguments()).getString("beaconId"); 32 | final String[] catNamesArray = {getString(R.string.edit_beacon), getString(R.string.delete_beacon)}; 33 | 34 | AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getActivity())); 35 | 36 | builder.setItems(catNamesArray, new DialogInterface.OnClickListener() { 37 | public void onClick(DialogInterface dialog, int which) { 38 | if (catNamesArray[which].equals(getString(R.string.edit_beacon))) { 39 | editBeacon(beaconId); 40 | } else if (catNamesArray[which].equals(getString(R.string.delete_beacon))) { 41 | deleteBeacon(beaconId); 42 | } 43 | } 44 | }); 45 | 46 | return builder.create(); 47 | } 48 | 49 | private void deleteBeacon(final String beaconId) { 50 | DeleteBeaconDialogFragment newFragment = DeleteBeaconDialogFragment 51 | .newInstance(beaconId); 52 | newFragment.show(Objects.requireNonNull(getActivity()).getSupportFragmentManager(), 53 | "DeleteBeaconDialog"); 54 | } 55 | 56 | private void editBeacon(final String beaconId) { 57 | BeaconApplication application = (BeaconApplication) Objects.requireNonNull(getActivity()).getApplication(); 58 | 59 | TransactionBeacon transactionBeacon = new 60 | TransactionBeacon(application.getBeaconFactory().getBeaconById(beaconId)); 61 | application.getBeaconFactory().setTransactionBeacon(transactionBeacon); 62 | 63 | BaseBeaconDialogFragment newFragment = BaseBeaconDialogFragment 64 | .newInstance(beaconId); 65 | newFragment.show(getActivity().getSupportFragmentManager(), 66 | "EditBeaconDialog"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/dialog/DeleteBeaconDialogFragment.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.DialogFragment; 8 | import android.support.v7.app.AlertDialog; 9 | 10 | import org.turbo.beaconmqtt.BeaconApplication; 11 | import org.turbo.beaconmqtt.R; 12 | 13 | import java.util.Objects; 14 | 15 | public class DeleteBeaconDialogFragment extends DialogFragment { 16 | public static DeleteBeaconDialogFragment newInstance(String beaconName) { 17 | DeleteBeaconDialogFragment fragment = new DeleteBeaconDialogFragment(); 18 | Bundle args = new Bundle(); 19 | args.putString("beaconId", beaconName); 20 | fragment.setArguments(args); 21 | return fragment; 22 | } 23 | 24 | @NonNull 25 | @Override 26 | public Dialog onCreateDialog(Bundle savedInstanceState) { 27 | final String beaconId = Objects.requireNonNull(getArguments()).getString("beaconId"); 28 | 29 | AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getActivity())); 30 | 31 | builder.setTitle(R.string.delete_beacon); 32 | builder.setMessage(getString(R.string.are_you_sure_to_delete, beaconId)); 33 | builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { 34 | public void onClick(DialogInterface dialog, int id) { 35 | BeaconApplication application = ((BeaconApplication) Objects.requireNonNull(getActivity()) 36 | .getApplicationContext()); 37 | application.getBeaconFactory().removeBeacon(beaconId); 38 | } 39 | }); 40 | builder.setNegativeButton(R.string.cancel, null); 41 | 42 | return builder.create(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/dialog/IBeaconDialogFragment.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.dialog; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v7.app.AlertDialog; 11 | import android.view.View; 12 | import android.view.inputmethod.InputMethodManager; 13 | 14 | import com.rengwuxian.materialedittext.MaterialEditText; 15 | import com.rengwuxian.materialedittext.validation.METValidator; 16 | 17 | import org.turbo.beaconmqtt.BeaconApplication; 18 | import org.turbo.beaconmqtt.R; 19 | import org.turbo.beaconmqtt.beacon.Helper; 20 | import org.turbo.beaconmqtt.beacon.TransactionBeacon; 21 | 22 | import java.util.Locale; 23 | import java.util.Objects; 24 | 25 | public class IBeaconDialogFragment extends DialogFragment { 26 | @SuppressWarnings("unused") 27 | final protected String TAG = "IBeaconDialog"; 28 | 29 | public static IBeaconDialogFragment newInstance(String beaconId) { 30 | IBeaconDialogFragment fragment = new IBeaconDialogFragment(); 31 | Bundle args = new Bundle(); 32 | if (beaconId != null) { 33 | args.putString("beaconId", beaconId); 34 | } 35 | fragment.setArguments(args); 36 | return fragment; 37 | } 38 | 39 | @NonNull 40 | @Override 41 | public Dialog onCreateDialog(Bundle savedInstanceState) { 42 | super.onCreateDialog(savedInstanceState); 43 | final BeaconApplication application = ((BeaconApplication) Objects.requireNonNull(getActivity()) 44 | .getApplicationContext()); 45 | 46 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 47 | @SuppressLint("InflateParams") final View dialogLayout = getActivity().getLayoutInflater() 48 | .inflate(R.layout.dialog_i_beacon, null); 49 | final InputMethodManager imm = (InputMethodManager) getActivity() 50 | .getSystemService(Context.INPUT_METHOD_SERVICE); 51 | 52 | final String beaconId = Objects.requireNonNull(getArguments()).getString("beaconId"); 53 | final TransactionBeacon transactionBeacon = application.getBeaconFactory() 54 | .getTransactionBeacon(); 55 | 56 | final MaterialEditText uuidView = dialogLayout 57 | .findViewById(R.id.text_beacon_uuid); 58 | final MaterialEditText majorView = dialogLayout 59 | .findViewById(R.id.text_beacon_major); 60 | final MaterialEditText minorView = dialogLayout 61 | .findViewById(R.id.text_beacon_minor); 62 | final MaterialEditText macView = dialogLayout 63 | .findViewById(R.id.text_beacon_mac); 64 | 65 | uuidView.setAutoValidate(true); 66 | majorView.setAutoValidate(true); 67 | minorView.setAutoValidate(true); 68 | macView.setAutoValidate(true); 69 | 70 | uuidView.addValidator(new METValidator(getString(R.string.invalid_value)) { 71 | @Override 72 | public boolean isValid(@NonNull CharSequence text, boolean isEmpty) { 73 | return Helper.validateUuid(text.toString()); 74 | } 75 | }); 76 | majorView.addValidator(new METValidator(getString(R.string.invalid_value)) { 77 | @Override 78 | public boolean isValid(@NonNull CharSequence text, boolean isEmpty) { 79 | return Helper.validateInt(text.toString(), 1, 65535); 80 | } 81 | }); 82 | minorView.addValidator(new METValidator(getString(R.string.invalid_value)) { 83 | @Override 84 | public boolean isValid(@NonNull CharSequence text, boolean isEmpty) { 85 | return Helper.validateInt(text.toString(), 1, 65535); 86 | } 87 | }); 88 | macView.addValidator(new METValidator(getString(R.string.invalid_value)) { 89 | @Override 90 | public boolean isValid(@NonNull CharSequence text, boolean isEmpty) { 91 | if (isEmpty) { 92 | return true; 93 | } else { 94 | return Helper.validateMacAddress(text.toString()); 95 | } 96 | } 97 | }); 98 | 99 | uuidView.setText(transactionBeacon.getUuid()); 100 | majorView.setText(transactionBeacon.getMajor()); 101 | minorView.setText(transactionBeacon.getMinor()); 102 | macView.setText(transactionBeacon.getMacAddress()); 103 | 104 | builder.setView(dialogLayout) 105 | .setTitle(R.string.advanced_parameters) 106 | .setPositiveButton(R.string.back, new DialogInterface.OnClickListener() { 107 | @Override 108 | public void onClick(DialogInterface dialog, int which) { 109 | transactionBeacon.setUuid(Objects.requireNonNull(uuidView.getText()) 110 | .toString().toLowerCase(Locale.getDefault())); 111 | transactionBeacon.setMajor(Objects.requireNonNull(majorView.getText()).toString()); 112 | transactionBeacon.setMinor(Objects.requireNonNull(minorView.getText()).toString()); 113 | transactionBeacon.setMacAddress(Objects.requireNonNull(macView.getText()) 114 | .toString().toUpperCase(Locale.getDefault())); 115 | 116 | BaseBeaconDialogFragment newFragment = BaseBeaconDialogFragment 117 | .newInstance(beaconId); 118 | newFragment.show(Objects.requireNonNull(getFragmentManager()), 119 | "BaseBeaconDialog"); 120 | } 121 | }); 122 | 123 | AlertDialog dialog = builder.create(); 124 | dialog.show(); 125 | 126 | uuidView.postDelayed(new Runnable() { 127 | @Override 128 | public void run() { 129 | uuidView.requestFocus(); 130 | uuidView.setSelection(Objects.requireNonNull(uuidView.getText()).length()); 131 | imm.showSoftInput(uuidView, 0); 132 | } 133 | }, 100); 134 | 135 | return dialog; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/dialog/TypeBeaconDialogFragment.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.DialogFragment; 8 | import android.support.v7.app.AlertDialog; 9 | 10 | import org.turbo.beaconmqtt.BeaconApplication; 11 | import org.turbo.beaconmqtt.R; 12 | import org.turbo.beaconmqtt.beacon.TransactionBeacon; 13 | import org.turbo.beaconmqtt.beaconFactory.BeaconFactory; 14 | 15 | import java.util.Objects; 16 | 17 | public class TypeBeaconDialogFragment extends DialogFragment { 18 | @SuppressWarnings("unused") 19 | final protected String TAG = "TypeBeaconDialog"; 20 | 21 | public static TypeBeaconDialogFragment newInstance() { 22 | TypeBeaconDialogFragment fragment = new TypeBeaconDialogFragment(); 23 | Bundle args = new Bundle(); 24 | fragment.setArguments(args); 25 | return fragment; 26 | } 27 | 28 | @NonNull 29 | @Override 30 | public Dialog onCreateDialog(Bundle savedInstanceState) { 31 | super.onCreateDialog(savedInstanceState); 32 | final BeaconApplication application = ((BeaconApplication) Objects.requireNonNull(getActivity()) 33 | .getApplicationContext()); 34 | final TransactionBeacon transactionBeacon = application.getBeaconFactory().getTransactionBeacon(); 35 | 36 | final String[] types = BeaconFactory.getBeaconTypes(); 37 | transactionBeacon.setType(types[0]); 38 | 39 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 40 | 41 | builder.setTitle(R.string.type_of_beacon) 42 | .setSingleChoiceItems(types, 0, new DialogInterface.OnClickListener() { 43 | @Override 44 | public void onClick(DialogInterface dialog, int which) { 45 | transactionBeacon.setType(types[which]); 46 | } 47 | }) 48 | .setPositiveButton(R.string.next, new DialogInterface.OnClickListener() { 49 | @Override 50 | public void onClick(DialogInterface dialog, int which) { 51 | BaseBeaconDialogFragment newFragment = BaseBeaconDialogFragment 52 | .newInstance(null); 53 | newFragment.show(Objects.requireNonNull(getActivity()).getSupportFragmentManager(), 54 | "new_beacon_dialog"); 55 | } 56 | }) 57 | .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { 58 | public void onClick(DialogInterface dialog, int which) { 59 | dismiss(); 60 | } 61 | }); 62 | 63 | AlertDialog dialog = builder.create(); 64 | dialog.show(); 65 | 66 | return dialog; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/dialog/WifiBeaconDialogFragment.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.dialog; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v7.app.AlertDialog; 11 | import android.view.View; 12 | import android.view.inputmethod.InputMethodManager; 13 | 14 | import com.rengwuxian.materialedittext.MaterialEditText; 15 | import com.rengwuxian.materialedittext.validation.METValidator; 16 | 17 | import org.turbo.beaconmqtt.BeaconApplication; 18 | import org.turbo.beaconmqtt.R; 19 | import org.turbo.beaconmqtt.beacon.Helper; 20 | import org.turbo.beaconmqtt.beacon.TransactionBeacon; 21 | 22 | import java.util.Objects; 23 | 24 | public class WifiBeaconDialogFragment extends DialogFragment { 25 | @SuppressWarnings("unused") 26 | final protected String TAG = "WifiBeaconDialog"; 27 | 28 | public static WifiBeaconDialogFragment newInstance(String beaconId) { 29 | WifiBeaconDialogFragment fragment = new WifiBeaconDialogFragment(); 30 | Bundle args = new Bundle(); 31 | if (beaconId != null) { 32 | args.putString("beaconId", beaconId); 33 | } 34 | fragment.setArguments(args); 35 | return fragment; 36 | } 37 | 38 | @NonNull 39 | @Override 40 | public Dialog onCreateDialog(Bundle savedInstanceState) { 41 | super.onCreateDialog(savedInstanceState); 42 | final BeaconApplication application = ((BeaconApplication) Objects.requireNonNull(getActivity()) 43 | .getApplicationContext()); 44 | 45 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 46 | @SuppressLint("InflateParams") final View dialogLayout = getActivity().getLayoutInflater() 47 | .inflate(R.layout.dialog_wifi_beacon, null); 48 | final InputMethodManager imm = (InputMethodManager) getActivity() 49 | .getSystemService(Context.INPUT_METHOD_SERVICE); 50 | 51 | final String beaconId = Objects.requireNonNull(getArguments()).getString("beaconId"); 52 | final TransactionBeacon transactionBeacon = application.getBeaconFactory() 53 | .getTransactionBeacon(); 54 | 55 | 56 | final MaterialEditText ssidView = dialogLayout 57 | .findViewById(R.id.text_beacon_ssid); 58 | 59 | ssidView.setAutoValidate(true); 60 | 61 | ssidView.addValidator(new METValidator(getString(R.string.invalid_value)) { 62 | @Override 63 | public boolean isValid(@NonNull CharSequence text, boolean isEmpty) { 64 | return Helper.validateSsid(text.toString()); 65 | } 66 | }); 67 | 68 | ssidView.setText(transactionBeacon.getSsid()); 69 | 70 | builder.setView(dialogLayout) 71 | .setTitle(R.string.advanced_parameters) 72 | .setPositiveButton(R.string.back, new DialogInterface.OnClickListener() { 73 | @Override 74 | public void onClick(DialogInterface dialog, int id) { 75 | transactionBeacon.setSsid(Objects.requireNonNull(ssidView.getText()).toString()); 76 | 77 | BaseBeaconDialogFragment newFragment = BaseBeaconDialogFragment 78 | .newInstance(beaconId); 79 | newFragment.show(Objects.requireNonNull(getFragmentManager()), 80 | "BaseBeaconDialog"); 81 | } 82 | }); 83 | 84 | AlertDialog dialog = builder.create(); 85 | dialog.show(); 86 | 87 | ssidView.postDelayed(new Runnable() { 88 | @Override 89 | public void run() { 90 | ssidView.requestFocus(); 91 | ssidView.setSelection(Objects.requireNonNull(ssidView.getText()).length()); 92 | imm.showSoftInput(ssidView, 0); 93 | } 94 | }, 100); 95 | 96 | return dialog; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/newBeacon/NewBeaconAdapter.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.newBeacon; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.constraint.ConstraintLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.CardView; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | 14 | import org.altbeacon.beacon.Beacon; 15 | import org.turbo.beaconmqtt.BeaconApplication; 16 | import org.turbo.beaconmqtt.R; 17 | import org.turbo.beaconmqtt.beacon.Helper; 18 | import org.turbo.beaconmqtt.beacon.IBeacon; 19 | import org.turbo.beaconmqtt.beacon.TransactionBeacon; 20 | import org.turbo.beaconmqtt.dialog.BaseBeaconDialogFragment; 21 | 22 | import java.util.Locale; 23 | 24 | public class NewBeaconAdapter extends RecyclerView.Adapter { 25 | 26 | final private Context activityContext; 27 | final private Context applicationContext; 28 | 29 | private final NewBeaconList beaconList; 30 | 31 | public NewBeaconAdapter(Context activityContext, Context applicationContext) { 32 | this.activityContext = activityContext; 33 | this.applicationContext = applicationContext; 34 | this.beaconList = new NewBeaconList(); 35 | } 36 | 37 | @Override 38 | public int getItemCount() { 39 | return beaconList.getSize(); 40 | } 41 | 42 | public void clear() { 43 | beaconList.clear(); 44 | notifyDataSetChanged(); 45 | } 46 | 47 | public void addBeacon(Beacon beacon) { 48 | beaconList.addBeacon(beacon); 49 | notifyDataSetChanged(); 50 | } 51 | 52 | @Override 53 | public @NonNull 54 | BeaconViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, final int position) { 55 | View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_new_beacon_card, 56 | viewGroup, 57 | false); 58 | return new BeaconViewHolder(v); 59 | } 60 | 61 | @Override 62 | public void onBindViewHolder(@NonNull BeaconViewHolder beaconViewHolder, final int position) { 63 | 64 | final Beacon beacon = beaconList.getBeacon(position); 65 | 66 | if (position == 0) { 67 | ViewGroup.MarginLayoutParams layoutParams = 68 | (ViewGroup.MarginLayoutParams) beaconViewHolder.cv.getLayoutParams(); 69 | int marign = layoutParams.getMarginEnd(); 70 | layoutParams.setMargins(marign, marign, marign, marign); 71 | beaconViewHolder.cv.setLayoutParams(layoutParams); 72 | } 73 | 74 | beaconViewHolder.beaconType.setText(Helper.getBleBeaconString(beacon)); 75 | beaconViewHolder.distance.setText(String 76 | .format(Locale.getDefault(), "%.02f", beacon.getDistance())); 77 | 78 | if (IBeacon.BEACON_IBEACON.equals(Helper.getBleBeaconString(beacon))) { 79 | beaconViewHolder.uuid.setText(activityContext 80 | .getString(R.string.card_text_uuid, beacon.getId1().toString())); 81 | beaconViewHolder.major.setText(activityContext 82 | .getString(R.string.card_text_major, beacon.getId2().toString())); 83 | beaconViewHolder.minor.setText(activityContext 84 | .getString(R.string.card_text_minor, beacon.getId3().toString())); 85 | beaconViewHolder.ibeaconView.setVisibility(View.VISIBLE); 86 | } 87 | 88 | beaconViewHolder.cv.setOnClickListener(new View.OnClickListener() { 89 | @Override 90 | public void onClick(View v) { 91 | BeaconApplication application = (BeaconApplication) applicationContext; 92 | TransactionBeacon transactionBeacon = new TransactionBeacon(); 93 | 94 | transactionBeacon.setType(IBeacon.BEACON_IBEACON); 95 | transactionBeacon.setUuid(beacon.getId1().toString()); 96 | transactionBeacon.setMajor(beacon.getId2().toString()); 97 | transactionBeacon.setMinor(beacon.getId3().toString()); 98 | transactionBeacon.setMacAddress(beacon.getBluetoothAddress()); 99 | 100 | application.getBeaconFactory().setTransactionBeacon(transactionBeacon); 101 | 102 | BaseBeaconDialogFragment newFragment = BaseBeaconDialogFragment 103 | .newInstance(null); 104 | newFragment.show(((AppCompatActivity) activityContext).getSupportFragmentManager(), 105 | "BaseBeaconDialog"); 106 | } 107 | }); 108 | } 109 | 110 | @Override 111 | public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 112 | super.onAttachedToRecyclerView(recyclerView); 113 | } 114 | 115 | public static class BeaconViewHolder extends RecyclerView.ViewHolder { 116 | final CardView cv; 117 | 118 | final TextView beaconType; 119 | final TextView distance; 120 | 121 | final ConstraintLayout ibeaconView; 122 | final TextView uuid; 123 | final TextView minor; 124 | final TextView major; 125 | 126 | BeaconViewHolder(View itemView) { 127 | super(itemView); 128 | 129 | cv = itemView.findViewById(R.id.cv); 130 | 131 | beaconType = itemView.findViewById(R.id.text_beacon_type); 132 | distance = itemView.findViewById(R.id.text_distance); 133 | 134 | ibeaconView = itemView.findViewById(R.id.ibeacon_item); 135 | uuid = itemView.findViewById(R.id.text_uuid); 136 | major = itemView.findViewById(R.id.text_major); 137 | minor = itemView.findViewById(R.id.text_minor); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/newBeacon/NewBeaconList.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.newBeacon; 2 | 3 | import org.altbeacon.beacon.Beacon; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | class NewBeaconList { 9 | @SuppressWarnings("unused") 10 | protected static final String TAG = "NewBeaconList"; 11 | 12 | private final Map beaconList; 13 | 14 | public NewBeaconList() { 15 | beaconList = new LinkedHashMap<>(); 16 | } 17 | 18 | public void addBeacon(Beacon beacon) { 19 | // todo: we need new beacon object 20 | beaconList.put(beacon.hashCode(), beacon); 21 | } 22 | 23 | public int getSize() { 24 | return beaconList.size(); 25 | } 26 | 27 | public Beacon getBeacon(int position) { 28 | return (Beacon) beaconList.values().toArray()[position]; 29 | } 30 | 31 | public void clear() { 32 | beaconList.clear(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/preferencesConverter/PreferencesConverter.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.preferencesConverter; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | public class PreferencesConverter { 6 | private static final String PREFERENCES_REVISION_KEY = "preferences_revision"; 7 | private static final String PREFERENCES_REVISION_CHANGED_KEY = "preferences_revision_changed"; 8 | private static final String PREFERENCES_REVISION_1 = "1"; 9 | private static final String PREFERENCES_REVISION_CURRENT = "2"; 10 | 11 | public PreferencesConverter(SharedPreferences defaultSharedPreferences) { 12 | SharedPreferences.Editor prefsEditor = defaultSharedPreferences.edit(); 13 | 14 | if (isPreferencesRevisionV1(defaultSharedPreferences)) { 15 | PreferencesConverterV1 converter = new PreferencesConverterV1(defaultSharedPreferences); 16 | if (converter.convert()) { 17 | prefsEditor.putString(PREFERENCES_REVISION_CHANGED_KEY, ""); 18 | prefsEditor.apply(); 19 | } 20 | } 21 | 22 | prefsEditor.putString(PREFERENCES_REVISION_KEY, PREFERENCES_REVISION_CURRENT); 23 | prefsEditor.apply(); 24 | } 25 | 26 | public static void hidePreferencesRevisionChanged(SharedPreferences defaultSharedPreferences) { 27 | SharedPreferences.Editor prefsEditor = defaultSharedPreferences.edit(); 28 | prefsEditor.remove(PREFERENCES_REVISION_CHANGED_KEY); 29 | prefsEditor.apply(); 30 | } 31 | 32 | public static boolean isPreferencesRevisionChanged(SharedPreferences defaultSharedPreferences) { 33 | return defaultSharedPreferences 34 | .getString(PREFERENCES_REVISION_CHANGED_KEY, null) != null; 35 | } 36 | 37 | private static boolean isPreferencesRevisionV1(SharedPreferences defaultSharedPreferences) { 38 | return PREFERENCES_REVISION_1.equals(defaultSharedPreferences 39 | .getString(PREFERENCES_REVISION_KEY, PREFERENCES_REVISION_1)); 40 | } 41 | 42 | public static boolean isCurrentPreferencesRevision(SharedPreferences defaultSharedPreferences) { 43 | return PREFERENCES_REVISION_CURRENT.equals(defaultSharedPreferences 44 | .getString(PREFERENCES_REVISION_KEY, null)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/preferencesConverter/PreferencesConverterV1.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.preferencesConverter; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | 8 | import org.turbo.beaconmqtt.beacon.BaseBeacon; 9 | import org.turbo.beaconmqtt.beacon.IBeacon; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Locale; 14 | 15 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_EXIT_TIMEOUT_KEY; 16 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_INACTIVE_TIMEOUT_KEY; 17 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_PAUSE_BETWEEN_SCANS_KEY; 18 | import static org.turbo.beaconmqtt.SettingsActivity.BEACON_SCAN_DURATION_KEY; 19 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_ENTER_MESSAGE_KEY; 20 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_EXIT_MESSAGE_KEY; 21 | import static org.turbo.beaconmqtt.SettingsActivity.MQTT_MASTER_TOPIC_KEY; 22 | import static org.turbo.beaconmqtt.beaconFactory.BeaconFactory.BEACONS_KEY; 23 | 24 | /* 25 | * PreferencesConverterV1 shared preference format 26 | * 27 | * beacon_period_between_scans -> beacon_pause_between_scans 28 | * beacon_scan_period -> beacon_scan_duration 29 | * beacon_exit_region_timeout -> beacon_exit_timeout 30 | * beacon_exit_region_timeout / 2 -> beacon_inactive_timeout 31 | * 32 | * mqtt_server -> no change 33 | * mqtt_port -> no change 34 | * mqtt_user -> no change 35 | * mqtt_pass -> no change 36 | * 37 | * mqtt_exit_topic == mqtt_enter_topic -> mqtt_master_topic 38 | * mqtt_enter_message -> mqtt_master_enter_message 39 | * mqtt_exit_message -> mqtt_master_exit_message 40 | * 41 | * regionObject0...3 -> beaconList 42 | */ 43 | 44 | class PreferencesConverterV1 { 45 | static final int REGION_LIST_SIZE = 4; 46 | static final String REGION_KEY = "regionObject"; 47 | static final String BEACON_PERIOD_BETWEEN_SCANS_KEY = "beacon_period_between_scans"; 48 | static final String BEACON_SCAN_PERIOD_KEY = "beacon_scan_period"; 49 | static final String BEACON_EXIT_REGION_TIMEOUT_KEY = "beacon_exit_region_timeout"; 50 | static final String MQTT_ENTER_TOPIC_KEY = "mqtt_enter_topic"; 51 | static final String MQTT_EXIT_TOPIC_KEY = "mqtt_exit_topic"; 52 | static final String MQTT_ENTER_MESSAGE_KEY = "mqtt_enter_message"; 53 | static final String MQTT_EXIT_MESSAGE_KEY = "mqtt_exit_message"; 54 | private final SharedPreferences defaultSharedPreferences; 55 | private final List beaconList = new ArrayList<>(); 56 | 57 | public PreferencesConverterV1(SharedPreferences defaultSharedPreferences) { 58 | this.defaultSharedPreferences = defaultSharedPreferences; 59 | } 60 | 61 | public boolean convert() { 62 | boolean changed = false; 63 | 64 | for (int index = 0; index < REGION_LIST_SIZE; index++) { 65 | String json = defaultSharedPreferences.getString(REGION_KEY + index, null); 66 | if (json != null) { 67 | changed = true; 68 | try { 69 | Gson gson = new Gson(); 70 | LegacyBeacon legacyBeacon = gson.fromJson(json, LegacyBeacon.class); 71 | 72 | IBeacon iBeacon = new IBeacon(legacyBeacon.id, legacyBeacon.id, "", 73 | legacyBeacon.uuid, legacyBeacon.major, legacyBeacon.minor, ""); 74 | 75 | beaconList.add(iBeacon); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | 82 | String backgroundBetweenScanPeriod = defaultSharedPreferences 83 | .getString(BEACON_PERIOD_BETWEEN_SCANS_KEY, null); 84 | String backgroundScanPeriod = defaultSharedPreferences 85 | .getString(BEACON_SCAN_PERIOD_KEY, null); 86 | String regionExitPeriod = defaultSharedPreferences 87 | .getString(BEACON_EXIT_REGION_TIMEOUT_KEY, null); 88 | 89 | String mqttEnterTopic = defaultSharedPreferences 90 | .getString(MQTT_ENTER_TOPIC_KEY, null); 91 | String mqttExitTopic = defaultSharedPreferences 92 | .getString(MQTT_EXIT_TOPIC_KEY, null); 93 | String mqttEnterMessage = defaultSharedPreferences 94 | .getString(MQTT_ENTER_MESSAGE_KEY, null); 95 | String mqttExitMessage = defaultSharedPreferences 96 | .getString(MQTT_EXIT_MESSAGE_KEY, null); 97 | 98 | SharedPreferences.Editor prefsEditor = defaultSharedPreferences.edit(); 99 | invalidate(prefsEditor); 100 | 101 | Gson gson = new GsonBuilder() 102 | .excludeFieldsWithoutExposeAnnotation() 103 | .create(); 104 | 105 | String json = gson.toJson(beaconList); 106 | prefsEditor.putString(BEACONS_KEY, json); 107 | 108 | if (backgroundBetweenScanPeriod != null) { 109 | changed = true; 110 | prefsEditor.putString(BEACON_PAUSE_BETWEEN_SCANS_KEY, backgroundBetweenScanPeriod); 111 | } 112 | 113 | if (backgroundScanPeriod != null) { 114 | changed = true; 115 | prefsEditor.putString(BEACON_SCAN_DURATION_KEY, backgroundScanPeriod); 116 | } 117 | 118 | if (regionExitPeriod != null) { 119 | changed = true; 120 | prefsEditor.putString(BEACON_EXIT_TIMEOUT_KEY, regionExitPeriod); 121 | try { 122 | long regionExitPeriodValue = Long.parseLong(regionExitPeriod); 123 | prefsEditor.putString(BEACON_INACTIVE_TIMEOUT_KEY, 124 | String.format(Locale.getDefault(), "%d", regionExitPeriodValue / 2)); 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | } 128 | } 129 | 130 | if (mqttEnterTopic != null && mqttExitTopic != null) { 131 | changed = true; 132 | if (mqttEnterTopic.equals(mqttExitTopic)) { 133 | prefsEditor.putString(MQTT_MASTER_TOPIC_KEY, mqttEnterTopic); 134 | prefsEditor.putString(MQTT_MASTER_EXIT_MESSAGE_KEY, mqttExitMessage); 135 | if (mqttEnterMessage != null && mqttEnterMessage.equals("%beacon%")) { 136 | prefsEditor.putString(MQTT_MASTER_ENTER_MESSAGE_KEY, "%group%"); 137 | } else { 138 | prefsEditor.putString(MQTT_MASTER_ENTER_MESSAGE_KEY, mqttEnterMessage); 139 | } 140 | } 141 | } 142 | 143 | prefsEditor.apply(); 144 | return changed; 145 | } 146 | 147 | private void invalidate(SharedPreferences.Editor prefsEditor) { 148 | for (int index = 0; index < REGION_LIST_SIZE; index++) { 149 | prefsEditor.remove(REGION_KEY + index); 150 | } 151 | 152 | prefsEditor.remove(BEACON_PERIOD_BETWEEN_SCANS_KEY); 153 | prefsEditor.remove(BEACON_SCAN_PERIOD_KEY); 154 | prefsEditor.remove(BEACON_EXIT_REGION_TIMEOUT_KEY); 155 | 156 | prefsEditor.remove(MQTT_ENTER_TOPIC_KEY); 157 | prefsEditor.remove(MQTT_EXIT_TOPIC_KEY); 158 | prefsEditor.remove(MQTT_ENTER_MESSAGE_KEY); 159 | prefsEditor.remove(MQTT_EXIT_MESSAGE_KEY); 160 | 161 | prefsEditor.apply(); 162 | } 163 | 164 | @SuppressWarnings("unused") 165 | private class LegacyBeacon { 166 | String id; 167 | String uuid; 168 | String major; 169 | String minor; 170 | transient String state; 171 | transient String timestamp; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/wifi/WifiChangeListener.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.wifi; 2 | 3 | import android.net.wifi.WifiInfo; 4 | 5 | public interface WifiChangeListener { 6 | void onWifiConnected(String ssid); 7 | 8 | void onWifiUpdated(String ssid, WifiInfo wifiInfo); 9 | 10 | void onWifiDisconnected(String ssid); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/org/turbo/beaconmqtt/wifi/WifiStateReceiver.java: -------------------------------------------------------------------------------- 1 | package org.turbo.beaconmqtt.wifi; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.net.NetworkInfo; 8 | import android.net.wifi.SupplicantState; 9 | import android.net.wifi.WifiInfo; 10 | import android.net.wifi.WifiManager; 11 | import android.os.Handler; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | public class WifiStateReceiver extends BroadcastReceiver { 20 | @SuppressWarnings("unused") 21 | private static final String TAG = "WifiStateReceiver"; 22 | private static final String INVALID_SSID = "invalid_ssid_string"; 23 | private final Context context; 24 | private final List listeners = new ArrayList<>(); 25 | private final Handler handler = new Handler(); 26 | private String activeSsid; 27 | private String lastSsid; 28 | private final Runnable disconnectRunnable = new Runnable() { 29 | public void run() { 30 | for (WifiChangeListener listener : listeners) { 31 | listener.onWifiDisconnected(lastSsid); 32 | } 33 | lastSsid = INVALID_SSID; 34 | } 35 | }; 36 | private long exitTimeout; 37 | 38 | public WifiStateReceiver() { 39 | this.context = null; 40 | } 41 | 42 | public WifiStateReceiver(Context context) { 43 | this.context = context; 44 | 45 | IntentFilter intentFilter = new IntentFilter(); 46 | intentFilter.addAction(android.net.wifi.WifiManager.NETWORK_STATE_CHANGED_ACTION); 47 | context.registerReceiver(this, intentFilter); 48 | 49 | lastSsid = activeSsid = INVALID_SSID; 50 | } 51 | 52 | @Override 53 | public void onReceive(Context context, Intent intent) { 54 | final String action = intent.getAction(); 55 | 56 | if (Objects.requireNonNull(action).equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 57 | NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 58 | if (networkInfo.isConnected()) { 59 | WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); 60 | 61 | WifiInfo wifiInfo = wifiManager.getConnectionInfo(); 62 | if (wifiInfo.getSupplicantState() == SupplicantState.COMPLETED) { 63 | String ssid = wifiInfo.getSSID(); 64 | 65 | try { 66 | Pattern p = Pattern.compile("\"([^\"]*)\""); 67 | Matcher m = p.matcher(ssid); 68 | //noinspection ResultOfMethodCallIgnored 69 | m.find(); 70 | 71 | notifyWifiConnected(m.group(1)); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } 75 | } 76 | } else { 77 | // wifi connection was lost 78 | notifyWifiDisconnected(); 79 | } 80 | } 81 | } 82 | 83 | public void setExitTimeout(long exitTimeout) { 84 | this.exitTimeout = exitTimeout; 85 | } 86 | 87 | private void notifyWifiConnected(String ssid) { 88 | if (lastSsid.equals(INVALID_SSID)) { 89 | // if disconnected long time ago 90 | // just send connected 91 | for (WifiChangeListener listener : listeners) { 92 | listener.onWifiConnected(ssid); 93 | } 94 | } else //noinspection StatementWithEmptyBody 95 | if (lastSsid.equals(ssid)) { 96 | // if disconnected within DISCONNECT_PERIOD from the same ssid 97 | // do nothing 98 | } else { 99 | // if disconnected within DISCONNECT_PERIOD but from other ssid 100 | // first send disconnected from last 101 | // and after that send connected connected to new 102 | for (WifiChangeListener listener : listeners) { 103 | listener.onWifiDisconnected(lastSsid); 104 | listener.onWifiConnected(ssid); 105 | } 106 | } 107 | 108 | if (!lastSsid.equals(ssid)) { 109 | for (WifiChangeListener listener : listeners) { 110 | listener.onWifiConnected(ssid); 111 | } 112 | } 113 | lastSsid = activeSsid = ssid; 114 | handler.removeCallbacks(disconnectRunnable); 115 | } 116 | 117 | private void notifyWifiDisconnected() { 118 | //BeaconApplication application = (BeaconApplication) context.getApplicationContext(); 119 | //application.debugLog("notifyWifiDisconnected"); 120 | if (!activeSsid.equals(INVALID_SSID)) { 121 | activeSsid = INVALID_SSID; 122 | handler.postDelayed(disconnectRunnable, exitTimeout); 123 | } 124 | } 125 | 126 | public void touch() { 127 | if (!activeSsid.equals(INVALID_SSID)) { 128 | WifiManager wifiManager = (WifiManager) context.getApplicationContext() 129 | .getSystemService(Context.WIFI_SERVICE); 130 | WifiInfo wifiInfo = wifiManager.getConnectionInfo(); 131 | 132 | for (WifiChangeListener listener : listeners) { 133 | listener.onWifiUpdated(activeSsid, wifiInfo); 134 | } 135 | } 136 | } 137 | 138 | public void addChangeListener(WifiChangeListener listener) { 139 | listeners.add(listener); 140 | } 141 | 142 | @SuppressWarnings("unused") 143 | public void removeChangeListener(WifiChangeListener listener) { 144 | listeners.remove(listener); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/background_image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/drawable-nodpi/background_image.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_access_point.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bluetooth_beacon_w_radiation.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bluetooth_beacon_wo_radiation_128dp.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bluetooth_radiation_128dp.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_add_beacon_manually.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_restart_scan.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_plus_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_server_offline.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_server_online.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_outline.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 45 | 46 | 51 | 52 | 58 | 59 | 65 | 66 | 70 | 71 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 48 | 49 | 56 | 57 | 58 | 59 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_ranging.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 24 | 25 | 29 | 30 | 37 | 38 | 45 | 46 | 47 | 58 | 59 | 60 | 61 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_toolbar_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_base_beacon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_i_beacon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 35 | 36 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_wifi_beacon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_beacon_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 19 | 20 | 28 | 29 | 41 | 42 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_ibeacon.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 30 | 31 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_new_beacon_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 23 | 24 | 33 | 34 | 43 | 44 | 56 | 57 | 64 | 65 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_ranging.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | 18 | 25 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #76be2f 4 | #289b23 5 | #f9a825 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #76BE2F 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Beacon MQTT 4 | %s build %d 5 | 6 | 7 | Add beacon 8 | Settings 9 | About 10 | last seen %s 11 | %d %s ago 12 | day 13 | days 14 | hour 15 | hours 16 | min 17 | mins 18 | just now 19 | 20 | You can have only %d beacon 21 | You can have only %d beacons 22 | 23 | invalid config 24 | 25 | 26 | Set up your server first! 27 | 28 | 29 | Bluetooth beacons settings 30 | MQTT server settings 31 | MQTT templates 32 | Interface settings 33 | Notification settings 34 | Beacon state topic 35 | Beacon state message 36 | Master topic 37 | Master enter message 38 | Master exit message 39 | Track topic 40 | Track message 41 | Show log on main screen 42 | Vibrate on events 43 | Pause between scans in background 44 | Time period between scanning cycles. Used when the app is running in the background.\nValid range: 0 … 55000ms. Default: 8000ms 45 | Scan duration 46 | Time period to scan. Usually set to 3 times beacon’s advertising interval.\nValid range: 500 … 5000ms. Default: 2000ms 47 | Inactive timeout 48 | Time to retain current master role. Used when the app is not actively receiving advertising packets. Usually set to half the exit timeout value.\nValid range: 15000 … 300000ms. Default: 90000ms 49 | Exit timeout 50 | Send exit message when the app is not actively receiving advertising packets.\nValid range: 30000 … 600000ms. Default: 180000ms 51 | Concurrent hysteresis factor 52 | Prevents the flapping of master status between equidistance beacons. To pick up the master role a beacon must be X times closer than the current master beacon.\nValid range: 1.0 … 5. Default: 1.5 53 | The value should be in range of %s 54 | Tokens available:\n\t• Beacon’s name - %beacon%\n\t• Beacon’s group: - %group%\n\t• Beacon’s tag - %tag%\n\t• Beacon’s state: - %state%\n\t• Phone’s MAC address: - %mac% 55 | Tokens available:\n\t• Beacon’s name - %beacon%\n\t• Beacon’s group: - %group%\n\t• Beacon’s tag - %tag%\n\t• Beacon’s state: - %state%\n\t• Phone’s MAC address: - %mac%\n\t• Beacon’s RSSI (dBm) - %rssi%\n\t• Distance to beacon (meters): - %distance%\n 56 | 57 | 58 | Add beacon> 59 | New beacons have not been found yet.\n\nYou can add a beacon manually\nby clicking the plus button. 60 | Add beacon manually 61 | Restart scan 62 | m 63 | UUID • %1$s 64 | Major • %1$s 65 | Minor • %1$s 66 | 67 | 68 | About Beacon MQTT> 69 | The documentation, typical usecases, list of known issues are available on a <a href=https://turbo-lab.github.io/android-beacon-mqtt/>project\'s homepage</a>. 70 | Like this app? Rate it in <a href=market://details?id=org.turbo.beaconmqtt>Google Play</a>. 71 | Feedback 72 | Acknowledgments 73 | Many thanks to Simon Bunn, Ronald Dehuysser, Cyn, Philip and others who helped me make Beacon MQTT better. 74 | Homepage 75 | Beacon MQTT is the simple Android app for presence detection of a person. Beacons have a known location. When an app detects or loses a beacon, it sends a MQTT message to home automation system.\n\nPlease, note the beacon detection is a time critical process. You have to disable any activity restrictions for Beacon MQTT in your Android. Without this you will have lots of false beacon losses. 76 | Version %s build %d 77 | 78 | 79 | Type of beacon 80 | Basic parameters 81 | Advanced parameters 82 | Beacon\'s name 83 | Beacon\'s group 84 | Beacon\'s tag 85 | Beacon\'s MAC address, optional 86 | Beacon\'s UUID 87 | Beacon\'s major 88 | Beacon\'s minor 89 | Beacon\'s SSID 90 | Save 91 | Advanced 92 | Next 93 | Back 94 | Cancel 95 | Error 96 | Warning 97 | Beacon %1$s is already exists! 98 | Beacon %1$s with the same parameters is already exists! 99 | Close 100 | Are you sure to delete %1$s beacon? 101 | Delete 102 | invalid value 103 | The application has been updated to version %1$s. There are some breaking changes. Not all settings can be converted automatically. \n\nPlease check on your settings. 104 | Please grant location access 105 | This app needs location access to be able to detect BLE beacons in the background and send this information to your MQTT server.\n\nRest assured, the application does not store location information and does not share it with third parties. 106 | Since location access has not been granted, this app will not be able to discover beacons when in the background. 107 | Please enable bluetooth in settings and restart this application. 108 | Sorry, this device does not support Bluetooth LE. 109 | 110 | 111 | Edit beacon 112 | Delete beacon 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 16 | 17 | 25 | 26 | 34 | 35 | 43 | 44 | 52 | 53 | 54 | 55 | 58 | 59 | 66 | 67 | 75 | 76 | 83 | 84 | 85 | 92 | 93 | 94 | 95 | 98 | 99 | 107 | 108 | 116 | 117 | 125 | 126 | 134 | 135 | 143 | 144 | 152 | 153 | 161 | 162 | 163 | 164 | 167 | 168 | 172 | 173 | 174 | 175 | 178 | 179 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /app/src/pro/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Beacon MQTT Pro 4 | 5 | 6 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.2' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | flatDir { 21 | dirs 'libs' 22 | } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | applicationName = beacon-mqtt 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbo-lab/android-beacon-mqtt/83cf53158ec24016d616e2cc5232fdd817930c4b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 21 09:42:11 NOVT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | //include ':android-beacon-library' 2 | include ':app' 3 | 4 | --------------------------------------------------------------------------------