diff --git a/BLE Indoor Positioning/build.gradle b/BLE Indoor Positioning/build.gradle
index 2c5c061..8fbe31b 100644
--- a/BLE Indoor Positioning/build.gradle
+++ b/BLE Indoor Positioning/build.gradle
@@ -8,8 +8,8 @@ buildscript {
}
plugins {
- id 'net.researchgate.release' version '2.4.0'
- id 'com.jfrog.bintray' version '1.7.3'
+ id 'net.researchgate.release' version '2.7.0'
+ id 'com.jfrog.bintray' version '1.8.4'
}
apply plugin: 'java-library'
@@ -21,6 +21,7 @@ apply plugin: 'jacoco'
dependencies {
compile 'com.lemmingapex.trilateration:trilateration:1.0.2'
compile 'com.google.code.gson:gson:2.8.5'
+
testCompile 'net.steppschuh.markdowngenerator:markdowngenerator:1.3.0.0'
testImplementation 'junit:junit:4.12'
}
@@ -97,6 +98,21 @@ def pomConfig = {
}
}
+task sourcesJar(type: Jar) {
+ classifier = 'sources'
+ from sourceSets.main.allJava
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+}
+
+artifacts {
+ archives sourcesJar
+ archives javadocJar
+}
+
publishing {
publications {
mavenJava(MavenPublication) {
@@ -123,21 +139,6 @@ publishing {
}
}
-task sourcesJar(type: Jar) {
- classifier = 'sources'
- from sourceSets.main.allJava
-}
-
-task javadocJar(type: Jar, dependsOn: javadoc) {
- classifier = 'javadoc'
- from javadoc.destinationDir
-}
-
-artifacts {
- archives sourcesJar
- archives javadocJar
-}
-
// configure release plugin. See https://github.com/researchgate/gradle-release#configuration
release {
failOnUnversionedFiles = false
diff --git a/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/BeaconInfo.java b/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/BeaconInfo.java
index 7b2e19c..355b578 100644
--- a/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/BeaconInfo.java
+++ b/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/BeaconInfo.java
@@ -2,13 +2,19 @@
public class BeaconInfo {
+ public static final String KEY_BEACON_NAME = "beaconName";
+ public static final String KEY_BEACON_MODEL = "beaconModel";
+ public static final String KEY_BEACON_MANUFACTURER = "beaconManufacturer";
+ public static final String KEY_BEACON_ADVERTISING_FREQUENCY = "beaconAdvertisingFrequency";
+ public static final String KEY_BEACON_TRANSMISSION_POWER = "beaconTransmissionPower";
+
private String name;
private String model;
private String manufacturer;
- private int advertizingFrequency;
+ private int advertisingFrequency;
private int transmissionPower;
@@ -39,12 +45,12 @@ public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
- public int getAdvertizingFrequency() {
- return advertizingFrequency;
+ public int getAdvertisingFrequency() {
+ return advertisingFrequency;
}
- public void setAdvertizingFrequency(int advertizingFrequency) {
- this.advertizingFrequency = advertizingFrequency;
+ public void setAdvertisingFrequency(int advertisingFrequency) {
+ this.advertisingFrequency = advertisingFrequency;
}
public int getTransmissionPower() {
diff --git a/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/DeviceInfo.java b/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/DeviceInfo.java
index c40d57f..da14621 100644
--- a/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/DeviceInfo.java
+++ b/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/DeviceInfo.java
@@ -2,7 +2,12 @@
public class DeviceInfo {
- private String name;
+ public static final String KEY_DEVICE_ID = "deviceName";
+ public static final String KEY_DEVICE_MODEL = "deviceModel";
+ public static final String KEY_DEVICE_MANUFACTURER = "deviceManufacturer";
+ public static final String KEY_DEVICE_OS_VERSION = "deviceOsVersion";
+
+ private String id;
private String model;
@@ -13,12 +18,12 @@ public class DeviceInfo {
public DeviceInfo() {
}
- public String getName() {
- return name;
+ public String getId() {
+ return id;
}
- public void setName(String name) {
- this.name = name;
+ public void setId(String id) {
+ this.id = id;
}
public String getModel() {
diff --git a/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/RssiMeasurements.java b/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/RssiMeasurements.java
index 187aa24..9a3dc4a 100644
--- a/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/RssiMeasurements.java
+++ b/BLE Indoor Positioning/src/main/java/com/nexenio/bleindoorpositioning/testutil/benchmark/RssiMeasurements.java
@@ -2,11 +2,21 @@
public class RssiMeasurements {
+ public static final String KEY_DEVICE_INFO = "deviceInfo";
+ public static final String KEY_BEACON_INFO = "beaconInfo";
+ public static final String KEY_START_TIMESTAMP = "startTimestamp";
+ public static final String KEY_END_TIMESTAMP = "endTimestamp";
+ public static final String KEY_NOTES = "notes";
+ public static final String KEY_DISTANCE = "distance";
+ public static final String KEY_RSSIS = "rssis";
+
private DeviceInfo deviceInfo;
private BeaconInfo beaconInfo;
- private long timestamp;
+ private long startTimestamp;
+
+ private long endTimestamp;
private String notes;
@@ -33,12 +43,20 @@ public void setBeaconInfo(BeaconInfo beaconInfo) {
this.beaconInfo = beaconInfo;
}
- public long getTimestamp() {
- return timestamp;
+ public long getStartTimestamp() {
+ return startTimestamp;
+ }
+
+ public void setStartTimestamp(long startTimestamp) {
+ this.startTimestamp = startTimestamp;
+ }
+
+ public long getEndTimestamp() {
+ return endTimestamp;
}
- public void setTimestamp(long timestamp) {
- this.timestamp = timestamp;
+ public void setEndTimestamp(long endTimestamp) {
+ this.endTimestamp = endTimestamp;
}
public String getNotes() {
diff --git a/app/build.gradle b/app/build.gradle
index 42415c1..0fbfd8e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ android {
targetSdkVersion 28
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -31,19 +31,21 @@ android {
dependencies {
implementation project(':BLE Indoor Positioning')
- implementation 'com.android.support:appcompat-v7:28.0.0'
- implementation 'com.android.support:support-v4:28.0.0'
- implementation 'com.android.support:support-media-compat:28.0.0'
- implementation 'com.android.support:design:28.0.0'
- implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.media:media:1.0.0'
+ implementation 'com.google.android.material:material:1.1.0-alpha01'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+
+ implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.android.gms:play-services-location:16.0.0'
implementation "com.polidea.rxandroidble:rxandroidble:1.4.3"
testImplementation 'junit:junit:4.12'
- testImplementation "com.android.support.test:runner:1.0.2"
- testImplementation "com.android.support.test:rules:1.0.2"
- testImplementation 'org.robolectric:robolectric:4.0-beta-1'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+ testImplementation 'androidx.test:runner:1.1.0'
+ testImplementation 'androidx.test:rules:1.1.0'
+ testImplementation 'org.robolectric:robolectric:4.2.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b4d2e16..a12268f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
+
+
@@ -26,6 +32,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ExternalStorageUtils.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ExternalStorageUtils.java
new file mode 100644
index 0000000..3fb9a42
--- /dev/null
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ExternalStorageUtils.java
@@ -0,0 +1,228 @@
+package com.nexenio.bleindoorpositioningdemo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.StatFs;
+import android.widget.Toast;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.FileProvider;
+
+/**
+ * Created by steppschuh on 04/11/2016.
+ */
+
+public abstract class ExternalStorageUtils {
+
+ public static final int BUFFER_SIZE = 2048;
+
+ /**
+ * Checks if external storage is available for read and write
+ */
+ public static boolean isExternalStorageWritable() {
+ String state;
+ try {
+ state = Environment.getExternalStorageState();
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return false;
+ }
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ /**
+ * Checks if external storage is available to at least read
+ */
+ public static boolean isExternalStorageReadable() {
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state) ||
+ Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ return true;
+ }
+ return false;
+ }
+
+ public static File getDocumentsDirectory(String subFolder) {
+ File file;
+ if (subFolder != null) {
+ file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), subFolder);
+ } else {
+ file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+ }
+ file.mkdirs();
+ return file;
+ }
+
+ public static File getCacheDirectory(Context context, String subFolder) {
+ File file;
+ if (subFolder != null) {
+ file = new File(context.getExternalCacheDir(), subFolder);
+ } else {
+ file = context.getExternalCacheDir();
+ }
+ file.mkdirs();
+ return file;
+ }
+
+ public static long getFileSize(@NonNull File file) {
+ long size = file.length();
+ if (file.isDirectory()) {
+ for (File f : file.listFiles()) {
+ if (f.isDirectory()) {
+ size += getFileSize(f);
+ }
+ size += f.length();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Emits all {@link File}s (that are not directories) in the specified directory. Also includes
+ * files in sub directories.
+ */
+ public static List getFilesInDirectory(@NonNull File directory) {
+ List files = new ArrayList<>();
+ for (File file : directory.listFiles()) {
+ if (file.isDirectory()) {
+ files.addAll(getFilesInDirectory(file));
+ } else {
+ files.add(file);
+ }
+ }
+ return files;
+ }
+
+ public static void zip(List files, String zipFile) throws IOException {
+ BufferedInputStream origin;
+ try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) {
+ byte[] data = new byte[BUFFER_SIZE];
+
+ for (File file : files) {
+ FileInputStream fi = new FileInputStream(file);
+ origin = new BufferedInputStream(fi, BUFFER_SIZE);
+
+ String filePath = file.getAbsolutePath();
+
+ try {
+ ZipEntry entry = new ZipEntry(filePath.substring(filePath.lastIndexOf("/") + 1));
+ out.putNextEntry(entry);
+ int count;
+ while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
+ out.write(data, 0, count);
+ }
+ } finally {
+ origin.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of {@link File}s which have the .json extension in the specified directory.
+ * Also includes files in subfolders.
+ */
+ public static List getJsonFilesInDirectory(File directory) {
+ return getFilesInDirectoryForExtension(directory, ".json");
+ }
+
+ /**
+ * Returns a list of {@link File}s which have the specified extension in the specified
+ * directory. Also includes files in subfolders.
+ */
+ public static List getFilesInDirectoryForExtension(File directory, String extension) {
+ ArrayList jsonFiles = new ArrayList<>();
+ File[] files = directory.listFiles();
+ for (File file : files) {
+ if (file.isDirectory()) {
+ jsonFiles.addAll(getJsonFilesInDirectory(file));
+ } else {
+ if (file.getName().endsWith(extension)) {
+ jsonFiles.add(file);
+ }
+ }
+ }
+ return jsonFiles;
+ }
+
+ /**
+ * Removes the given files from the external storage.
+ */
+ public static boolean removeFiles(List files) {
+ boolean allFilesRemoved = true;
+ for (File file : files) {
+ allFilesRemoved &= file.delete();
+ }
+ return allFilesRemoved;
+ }
+
+ /**
+ * Invokes an implicit share intent for the specified file.
+ */
+ public static void shareFile(File file, Context context) {
+ shareFile(file, context, file.getName(), file.getName());
+ }
+
+ /**
+ * Invokes an implicit share intent for the specified file.
+ */
+ public static void shareFile(File file, Context context, String subject, String text) {
+ if (file == null || !file.canRead()) {
+ return;
+ }
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ Uri contentUri = FileProvider.getUriForFile(context, "com.nexenio.bleindoorpositioning.fileprovider", file);
+
+ shareIntent.setTypeAndNormalize(context.getContentResolver().getType(contentUri));
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, text);
+ shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
+ // Todo: Remove
+ shareIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"marvin.mirtschin@nexenio.com"});
+
+ Intent chooserIntent = Intent.createChooser(shareIntent, "Share file");
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (shareIntent.resolveActivity(context.getPackageManager()) != null) {
+ context.startActivity(chooserIntent);
+ } else {
+ Toast.makeText(context, "Unable to share file", Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Writes the contents of a string to the specified file.
+ *
+ * @param data the string that the file should contain
+ * @param file the file that the string should be written to
+ * @param append if false, file contents will be overwritten
+ */
+ public static void writeStringToFile(String data, File file, boolean append) throws IOException {
+ try (FileWriter fw = new FileWriter(file, append)) {
+ fw.write(data);
+ }
+ }
+
+ /**
+ * Returns the amount of bytes that are available in the specified directory.
+ */
+ public static long getAvailableMemory(@NonNull File directory) {
+ StatFs stats = new StatFs(directory.getAbsolutePath());
+ return stats.getAvailableBlocksLong() * stats.getBlockSizeLong();
+ }
+
+}
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/HomeActivity.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/HomeActivity.java
index 4aa8109..2c9c148 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/HomeActivity.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/HomeActivity.java
@@ -3,17 +3,18 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.design.widget.BottomNavigationView;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.Fragment;
-import android.support.v7.app.AppCompatActivity;
+import androidx.annotation.NonNull;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import com.google.android.material.snackbar.Snackbar;
+import androidx.fragment.app.Fragment;
+import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.EditText;
import com.nexenio.bleindoorpositioningdemo.bluetooth.BluetoothClient;
import com.nexenio.bleindoorpositioningdemo.location.AndroidLocationProvider;
@@ -56,8 +57,12 @@ public boolean onCreateOptionsMenu(Menu menu) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case R.id.menu_record:
+ Intent intent = new Intent(this, RecordingActivity.class);
+ startActivity(intent);
+ return true;
case R.id.menu_filter:
- Log.w(TAG, "BeaconFilter");
+ Log.w(TAG, "Filter");
return true;
default:
break;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/RecordingActivity.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/RecordingActivity.java
new file mode 100644
index 0000000..c90b421
--- /dev/null
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/RecordingActivity.java
@@ -0,0 +1,488 @@
+package com.nexenio.bleindoorpositioningdemo;
+
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.snackbar.BaseTransientBottomBar;
+import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import com.nexenio.bleindoorpositioning.ble.advertising.IBeaconAdvertisingPacket;
+import com.nexenio.bleindoorpositioning.ble.beacon.BeaconManager;
+import com.nexenio.bleindoorpositioning.ble.beacon.FilteredBeaconUpdateListener;
+import com.nexenio.bleindoorpositioning.ble.beacon.IBeacon;
+import com.nexenio.bleindoorpositioning.ble.beacon.filter.IBeaconFilter;
+import com.nexenio.bleindoorpositioning.testutil.benchmark.BeaconInfo;
+import com.nexenio.bleindoorpositioning.testutil.benchmark.DeviceInfo;
+import com.nexenio.bleindoorpositioning.testutil.benchmark.RssiMeasurements;
+import com.nexenio.bleindoorpositioningdemo.bluetooth.BluetoothClient;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+public class RecordingActivity extends AppCompatActivity {
+
+ private static final String TAG = RecordingActivity.class.getSimpleName();
+ private static final String RECORDING_DIRECTORY_NAME = "Rssi_Recordings";
+ public static final int REQUEST_CODE_STORAGE_PERMISSIONS = 1;
+
+ private final static UUID RECORDING_UUID = UUID.fromString("61a0523a-a733-4789-ae8f-4f55fcff64f2");
+
+ private RssiMeasurements rssiMeasurements = new RssiMeasurements();
+
+ @Nullable
+ private List recordedRssiValues;
+ private FilteredBeaconUpdateListener> recordingBeaconUpdateListener;
+
+ private SharedPreferences sharedPreferences;
+
+ private TextInputEditText distanceEditText;
+ private TextInputEditText notesEditText;
+
+ private TextInputEditText deviceIdEditText;
+ private TextInputEditText deviceModelEditText;
+ private TextInputEditText deviceManufacturerEditText;
+ private TextInputEditText deviceOsVersionEditText;
+
+ private TextInputEditText beaconNameEditText;
+ private TextInputEditText beaconModelEditText;
+ private TextInputEditText beaconManufacturerEditText;
+ private TextInputEditText beaconTransmissionPowerEditText;
+ private TextInputEditText beaconAdvertisingFrequencyEditText;
+
+ private TextInputEditText recordingUuidCopyEditText;
+
+ private MaterialButton recordButton;
+
+ private boolean isRecording;
+
+ @Nullable
+ protected Snackbar errorSnackBar;
+ protected CoordinatorLayout coordinatorLayout;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_recording);
+
+ sharedPreferences = getPreferences(Context.MODE_PRIVATE);
+
+ initializeLayout();
+ restoreFormValues();
+ initializeBluetoothScanning();
+
+ if (!hasStoragePermission()) {
+ requestStoragePermission();
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case REQUEST_CODE_STORAGE_PERMISSIONS: {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Storage permission granted");
+ } else {
+ showStoragePermissionMissingError();
+ }
+ break;
+ }
+ }
+ }
+
+ private void initializeLayout() {
+ coordinatorLayout = getWindow().getDecorView().findViewById(R.id.coordinatorLayout);
+
+ recordButton = findViewById(R.id.recordButton);
+ distanceEditText = findViewById(R.id.recordDistanceEditText);
+ notesEditText = findViewById(R.id.recordNotesEditText);
+
+ deviceIdEditText = findViewById(R.id.recordDeviceIdEditText);
+ deviceModelEditText = findViewById(R.id.recordDeviceModelEditText);
+ deviceManufacturerEditText = findViewById(R.id.recordDeviceManufacturerEditText);
+ deviceOsVersionEditText = findViewById(R.id.recordDeviceOsVersionEditText);
+
+ beaconNameEditText = findViewById(R.id.recordBeaconNameEditText);
+ beaconModelEditText = findViewById(R.id.recordBeaconModelEditText);
+ beaconManufacturerEditText = findViewById(R.id.recordBeaconManufacturerEditText);
+ beaconTransmissionPowerEditText = findViewById(R.id.recordBeaconTransmissionPowerEditText);
+ beaconAdvertisingFrequencyEditText = findViewById(R.id.recordBeaconAdvertisingFrequencyEditText);
+
+ recordingUuidCopyEditText = findViewById(R.id.recordingUuidCopyEditText);
+ setupUuidEditText();
+
+ // Limit numbers to avoid overflow
+ InputFilter[] FilterArray = new InputFilter[1];
+ FilterArray[0] = new InputFilter.LengthFilter(6);
+
+ distanceEditText.setFilters(FilterArray);
+ beaconTransmissionPowerEditText.setFilters(FilterArray);
+ beaconAdvertisingFrequencyEditText.setFilters(FilterArray);
+ }
+
+ private void initializeBluetoothScanning() {
+ Log.d(TAG, "Initializing Bluetooth scanning");
+
+ BluetoothClient.initialize(this);
+ IBeaconFilter> recordingBeaconFilter = new IBeaconFilter<>(RECORDING_UUID);
+ recordingBeaconUpdateListener = new FilteredBeaconUpdateListener>(recordingBeaconFilter) {
+ @Override
+ public void onMatchingBeaconUpdated(IBeacon beacon) {
+ onRecordingBeaconUpdated(beacon);
+ }
+ };
+ }
+
+ @Override
+ protected void onStop() {
+ stopRecording();
+ super.onStop();
+ }
+
+ public void onRecordButtonClicked(View view) {
+ persistFormValues();
+ if (isRecording) {
+ stopRecording();
+ } else {
+ startRecording();
+ }
+ }
+
+ public void onClearRecordingsButtonClicked(View view) {
+ new AlertDialog.Builder(this)
+ .setTitle("Remove Recordings")
+ .setMessage("Do you really want to remove all recordings?")
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ File documentsDirectory = ExternalStorageUtils.getDocumentsDirectory(RECORDING_DIRECTORY_NAME);
+ List files = ExternalStorageUtils.getFilesInDirectory(documentsDirectory);
+ ExternalStorageUtils.removeFiles(files);
+ Toast.makeText(RecordingActivity.this, "Removed " + files.size() + " file(s)", Toast.LENGTH_SHORT).show();
+ }
+ })
+ .setNegativeButton(android.R.string.no, null).show();
+ }
+
+ private RssiMeasurements createRssiMeasurements() {
+ RssiMeasurements rssiMeasurements = new RssiMeasurements();
+ rssiMeasurements.setDeviceInfo(createDeviceInfo());
+ rssiMeasurements.setBeaconInfo(createBeaconInfo());
+ rssiMeasurements.setDistance(Float.parseFloat(distanceEditText.getText().toString()));
+
+ Editable optionalNotes = notesEditText.getText();
+ if (optionalNotes != null) {
+ rssiMeasurements.setNotes(optionalNotes.toString());
+ }
+
+ rssiMeasurements.setStartTimestamp(System.currentTimeMillis());
+ return rssiMeasurements;
+ }
+
+ private DeviceInfo createDeviceInfo() {
+ DeviceInfo deviceInfo = new DeviceInfo();
+ deviceInfo.setId(deviceIdEditText.getText().toString());
+ deviceInfo.setModel(deviceModelEditText.getText().toString());
+ deviceInfo.setManufacturer(deviceManufacturerEditText.getText().toString());
+ deviceInfo.setOsVersion(deviceOsVersionEditText.getText().toString());
+ return deviceInfo;
+ }
+
+ private BeaconInfo createBeaconInfo() {
+ BeaconInfo beaconInfo = new BeaconInfo();
+ beaconInfo.setName(beaconNameEditText.getText().toString());
+ beaconInfo.setModel(beaconModelEditText.getText().toString());
+ beaconInfo.setManufacturer(beaconManufacturerEditText.getText().toString());
+ beaconInfo.setTransmissionPower(Integer.valueOf(beaconTransmissionPowerEditText.getText().toString()));
+ beaconInfo.setAdvertisingFrequency(Integer.valueOf(beaconAdvertisingFrequencyEditText.getText().toString()));
+ return beaconInfo;
+ }
+
+ private void startRecording() {
+ List invalidTextEdits = getInvalidTextEdits();
+ if (!invalidTextEdits.isEmpty()) {
+ showInvalidFields(invalidTextEdits);
+ return;
+ }
+
+ rssiMeasurements = createRssiMeasurements();
+
+ if (!hasStoragePermission()) {
+ requestStoragePermission();
+ return;
+ }
+
+ Log.d(TAG, "Starting recording");
+ recordedRssiValues = new ArrayList<>();
+
+ BluetoothClient.startScanning();
+ BeaconManager.registerBeaconUpdateListener(recordingBeaconUpdateListener);
+
+ isRecording = true;
+ recordButton.setText(getString(R.string.action_stop_recording));
+ }
+
+ private void stopRecording() {
+ if (!isRecording) {
+ return;
+ }
+ Log.d(TAG, "Stopping recording");
+ isRecording = false;
+ recordButton.setText(getString(R.string.action_start_recording));
+
+ BluetoothClient.stopScanning();
+ BeaconManager.unregisterBeaconUpdateListener(recordingBeaconUpdateListener);
+
+ rssiMeasurements.setEndTimestamp(System.currentTimeMillis());
+ rssiMeasurements.setRssis(convertIntArray(recordedRssiValues));
+
+ if (recordedRssiValues != null) {
+ recordedRssiValues.clear();
+ }
+
+ Log.i(TAG, "RSSI measurements:\n" + rssiMeasurements);
+ exportMeasurements();
+ }
+
+ private void setupUuidEditText() {
+ recordingUuidCopyEditText.setText(RECORDING_UUID.toString());
+ recordingUuidCopyEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("Recording Uuid", ((TextInputEditText) v).getText());
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(RecordingActivity.this, "Copied to Clipboard", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ private List getInvalidTextEdits() {
+ List textInputEditTexts = getNecessaryTextInputEditTexts();
+ List invalidInputEdits = new ArrayList<>();
+ for (TextInputEditText textInputEditText : textInputEditTexts) {
+ if (textInputEditText.getText() != null && textInputEditText.getText().toString().equals("")) {
+ invalidInputEdits.add(textInputEditText);
+ }
+ }
+ return invalidInputEdits;
+ }
+
+ /**
+ * Indicate text edit fields with missing text.
+ */
+ private void showInvalidFields(List textInputEditTexts) {
+ Toast.makeText(this, "Not all information were provided", Toast.LENGTH_LONG).show();
+ String errorString = "This field cannot be empty";
+ for (TextInputEditText textInputEditText : textInputEditTexts) {
+ textInputEditText.setError(errorString);
+ }
+ }
+
+ private List getNecessaryTextInputEditTexts() {
+ return Arrays.asList(distanceEditText,
+ deviceIdEditText,
+ deviceModelEditText,
+ deviceManufacturerEditText,
+ deviceOsVersionEditText,
+ beaconNameEditText,
+ beaconModelEditText,
+ beaconManufacturerEditText,
+ beaconTransmissionPowerEditText,
+ beaconAdvertisingFrequencyEditText);
+ }
+
+ public void onExportAllRecordingsButtonClicked(View view) {
+ exportAllRecordings();
+ }
+
+ private void exportAllRecordings() {
+ File documentsDirectory = ExternalStorageUtils.getDocumentsDirectory(RECORDING_DIRECTORY_NAME);
+ List jsonFiles = ExternalStorageUtils.getJsonFilesInDirectory(documentsDirectory);
+
+ String fileName = "measurements.zip";
+ File zipFile = new File(documentsDirectory, fileName);
+
+ if (zipFile.exists()) {
+ zipFile.delete();
+ }
+
+ try {
+ ExternalStorageUtils.zip(jsonFiles, zipFile.getAbsolutePath());
+ } catch (IOException e) {
+ Toast.makeText(this, "Unable to export files", Toast.LENGTH_LONG).show();
+ Log.e(TAG, "Unable to export measurements", e);
+ return;
+ }
+ ExternalStorageUtils.shareFile(zipFile, this);
+ }
+
+ private void exportMeasurements() {
+ String jsonString = createJsonString(rssiMeasurements);
+ String fileName = "rssiMeasurements_" + rssiMeasurements.getStartTimestamp() + "_" + rssiMeasurements.getEndTimestamp() + ".json";
+ File file = persistJsonFile(fileName, jsonString);
+
+ ExternalStorageUtils.shareFile(file, this);
+ }
+
+ private static String createJsonString(RssiMeasurements rssiMeasurements) {
+ GsonBuilder gsonBuilder = new GsonBuilder()
+ .setPrettyPrinting();
+
+ Gson gson = gsonBuilder.create();
+ return gson.toJson(rssiMeasurements);
+ }
+
+ private File persistJsonFile(String fileName, String jsonString) {
+ File documentsDirectory = ExternalStorageUtils.getDocumentsDirectory(RECORDING_DIRECTORY_NAME);
+ File file = new File(documentsDirectory, fileName);
+ try {
+ if (!documentsDirectory.exists()) {
+ documentsDirectory.mkdirs();
+ }
+ ExternalStorageUtils.writeStringToFile(jsonString, file, false);
+ } catch (IOException e) {
+ // TODO: log + toast
+ e.printStackTrace();
+ }
+ return file;
+ }
+
+ private boolean hasStoragePermission() {
+ int permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ return permissionResult == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @SuppressLint("CheckResult")
+ private void requestStoragePermission() {
+ Log.d(TAG, "Requesting storage permission");
+ ActivityCompat.requestPermissions(this, new String[]{
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ }, REQUEST_CODE_STORAGE_PERMISSIONS);
+ }
+
+ private void showStoragePermissionMissingError() {
+ // find a container for the snackbar
+ if (errorSnackBar != null) {
+ errorSnackBar.dismiss();
+ errorSnackBar = null;
+ }
+
+ View container = coordinatorLayout;
+ if (container == null) {
+ container = getWindow().getDecorView();
+ }
+ // create the snackbar
+ errorSnackBar = Snackbar.make(container, "Permission not granted", Snackbar.LENGTH_LONG);
+
+ errorSnackBar.setDuration(BaseTransientBottomBar.LENGTH_INDEFINITE)
+ .setAction(getString(R.string.record_retry), new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ requestStoragePermission();
+ }
+ }).show();
+ }
+
+ private void onRecordingBeaconUpdated(IBeacon beacon) {
+ IBeaconAdvertisingPacket latestAdvertisingPacket = beacon.getLatestAdvertisingPacket();
+ recordedRssiValues.add(latestAdvertisingPacket.getRssi());
+ }
+
+ private void persistFormValues() {
+ Log.d(TAG, "Persisting form values");
+
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+
+ editor.putString(RssiMeasurements.KEY_DISTANCE, distanceEditText.getText().toString());
+ editor.putString(RssiMeasurements.KEY_NOTES, notesEditText.getText().toString());
+
+ // don't persist dynamic fields
+ //editor.putString(DeviceInfo.KEY_DEVICE_ID, deviceIdEditText.getText().toString());
+ //editor.putString(DeviceInfo.KEY_DEVICE_MODEL, deviceModelEditText.getText().toString());
+ //editor.putString(DeviceInfo.KEY_DEVICE_MANUFACTURER, deviceManufacturerEditText.getText().toString());
+ //editor.putString(DeviceInfo.KEY_DEVICE_OS_VERSION, deviceOsVersionEditText.getText().toString());
+
+ editor.putString(BeaconInfo.KEY_BEACON_NAME, beaconNameEditText.getText().toString());
+ editor.putString(BeaconInfo.KEY_BEACON_MODEL, beaconModelEditText.getText().toString());
+ editor.putString(BeaconInfo.KEY_BEACON_MANUFACTURER, beaconManufacturerEditText.getText().toString());
+ editor.putString(BeaconInfo.KEY_BEACON_TRANSMISSION_POWER, beaconTransmissionPowerEditText.getText().toString());
+ editor.putString(BeaconInfo.KEY_BEACON_ADVERTISING_FREQUENCY, beaconAdvertisingFrequencyEditText.getText().toString());
+
+ editor.apply();
+ }
+
+ private void restoreFormValues() {
+ Log.d(TAG, "Restoring form values");
+
+ distanceEditText.setText(sharedPreferences.getString(RssiMeasurements.KEY_DISTANCE, ""));
+ notesEditText.setText(sharedPreferences.getString(RssiMeasurements.KEY_NOTES, ""));
+
+ deviceIdEditText.setText(sharedPreferences.getString(DeviceInfo.KEY_DEVICE_ID, getDeviceId(this)));
+ deviceModelEditText.setText(sharedPreferences.getString(DeviceInfo.KEY_DEVICE_MODEL, Build.MODEL));
+ deviceManufacturerEditText.setText(sharedPreferences.getString(DeviceInfo.KEY_DEVICE_MANUFACTURER, Build.MANUFACTURER));
+ deviceOsVersionEditText.setText(sharedPreferences.getString(DeviceInfo.KEY_DEVICE_OS_VERSION, getOsVersion()));
+
+ beaconNameEditText.setText(sharedPreferences.getString(BeaconInfo.KEY_BEACON_NAME, ""));
+ beaconModelEditText.setText(sharedPreferences.getString(BeaconInfo.KEY_BEACON_MODEL, ""));
+ beaconManufacturerEditText.setText(sharedPreferences.getString(BeaconInfo.KEY_BEACON_MANUFACTURER, ""));
+ beaconTransmissionPowerEditText.setText(sharedPreferences.getString(BeaconInfo.KEY_BEACON_TRANSMISSION_POWER, ""));
+ beaconAdvertisingFrequencyEditText.setText(sharedPreferences.getString(BeaconInfo.KEY_BEACON_ADVERTISING_FREQUENCY, ""));
+ }
+
+ private static int[] convertIntArray(@Nullable List integerList) {
+ if (integerList == null) {
+ Log.w(TAG, "Can't convert null to int array.");
+ return new int[0];
+ }
+
+ int[] intArray = new int[integerList.size()];
+ Iterator iterator = integerList.iterator();
+ for (int i = 0; i < intArray.length; i++) {
+ intArray[i] = iterator.next();
+ }
+ return intArray;
+ }
+
+ @SuppressLint("HardwareIds")
+ private static String getDeviceId(Context context) {
+ return Settings.Secure.getString(context.getContentResolver(), "android_id");
+ }
+
+ private static String getOsVersion() {
+ return "Android " + Build.VERSION.RELEASE + " (SDK " + Build.VERSION.SDK_INT + ")";
+ }
+
+}
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/bluetooth/BluetoothClient.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/bluetooth/BluetoothClient.java
index 50ac5b0..c5b8c43 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/bluetooth/BluetoothClient.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/bluetooth/BluetoothClient.java
@@ -5,7 +5,7 @@
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import android.util.Log;
import com.nexenio.bleindoorpositioning.ble.advertising.AdvertisingPacket;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/location/AndroidLocationProvider.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/location/AndroidLocationProvider.java
index cfaeb4e..dbe92db 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/location/AndroidLocationProvider.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/location/AndroidLocationProvider.java
@@ -25,8 +25,8 @@
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
import android.text.TextUtils;
import android.util.Log;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/LocationAnimator.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/LocationAnimator.java
index 8c1cfed..85c51c1 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/LocationAnimator.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/LocationAnimator.java
@@ -2,7 +2,7 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import com.nexenio.bleindoorpositioning.location.Location;
import com.nexenio.bleindoorpositioning.location.LocationListener;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconView.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconView.java
index 91f56e0..ded8ec6 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconView.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconView.java
@@ -5,10 +5,10 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
-import android.support.annotation.CallSuper;
-import android.support.annotation.ColorInt;
-import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
+import androidx.annotation.CallSuper;
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconViewFragment.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconViewFragment.java
index 0692f4b..b48fa35 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconViewFragment.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/BeaconViewFragment.java
@@ -2,11 +2,11 @@
import android.content.Context;
import android.os.Bundle;
-import android.support.annotation.CallSuper;
-import android.support.annotation.LayoutRes;
-import android.support.annotation.Nullable;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.v4.app.Fragment;
+import androidx.annotation.CallSuper;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/ColorUtil.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/ColorUtil.java
index 0364089..b1ea7c0 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/ColorUtil.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/ColorUtil.java
@@ -2,10 +2,10 @@
import android.content.Context;
import android.content.res.Resources;
-import android.support.annotation.ColorInt;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.v4.content.res.ResourcesCompat;
+import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.core.content.res.ResourcesCompat;
import com.nexenio.bleindoorpositioningdemo.R;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChart.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChart.java
index 20c9d68..d122036 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChart.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChart.java
@@ -1,8 +1,8 @@
package com.nexenio.bleindoorpositioningdemo.ui.beaconview.chart;
import android.content.Context;
-import android.support.annotation.IntDef;
-import android.support.annotation.Nullable;
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import android.util.AttributeSet;
import com.nexenio.bleindoorpositioningdemo.ui.beaconview.BeaconView;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChartFragment.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChartFragment.java
index ad366ab..4369134 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChartFragment.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconChartFragment.java
@@ -3,7 +3,7 @@
import android.annotation.SuppressLint;
import android.os.Bundle;
-import android.support.annotation.CallSuper;
+import androidx.annotation.CallSuper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconLineChart.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconLineChart.java
index 20cb750..afa96bd 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconLineChart.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/BeaconLineChart.java
@@ -8,8 +8,8 @@
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Shader;
-import android.support.annotation.ColorInt;
-import android.support.annotation.Nullable;
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
import android.util.AttributeSet;
import com.nexenio.bleindoorpositioning.ble.advertising.AdvertisingPacket;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/RssiFilterLineChart.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/RssiFilterLineChart.java
index 1088ed7..373743c 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/RssiFilterLineChart.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/chart/RssiFilterLineChart.java
@@ -3,7 +3,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PointF;
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
import android.util.AttributeSet;
import com.nexenio.bleindoorpositioning.ble.advertising.AdvertisingPacket;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMap.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMap.java
index d242910..fdeb134 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMap.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMap.java
@@ -10,8 +10,8 @@
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
-import android.support.annotation.CallSuper;
-import android.support.annotation.Nullable;
+import androidx.annotation.CallSuper;
+import androidx.annotation.Nullable;
import android.util.AttributeSet;
import com.nexenio.bleindoorpositioning.ble.advertising.AdvertisingPacket;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapBackground.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapBackground.java
index 4035fa8..f0ccd5e 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapBackground.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapBackground.java
@@ -2,7 +2,7 @@
import android.graphics.Bitmap;
import android.graphics.Point;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import com.nexenio.bleindoorpositioning.location.Location;
import com.nexenio.bleindoorpositioning.location.projection.CanvasProjection;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapFragment.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapFragment.java
index 20b80db..63ef77a 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapFragment.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/map/BeaconMapFragment.java
@@ -5,7 +5,7 @@
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.os.Bundle;
-import android.support.annotation.CallSuper;
+import androidx.annotation.CallSuper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadar.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadar.java
index f554264..f4fb24e 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadar.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadar.java
@@ -7,7 +7,7 @@
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
import android.util.AttributeSet;
import com.nexenio.bleindoorpositioning.ble.advertising.AdvertisingPacket;
diff --git a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadarFragment.java b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadarFragment.java
index ccfe7e8..c9eefa2 100644
--- a/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadarFragment.java
+++ b/app/src/main/java/com/nexenio/bleindoorpositioningdemo/ui/beaconview/radar/BeaconRadarFragment.java
@@ -7,7 +7,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
-import android.support.annotation.CallSuper;
+import androidx.annotation.CallSuper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml
index 85773df..2451f42 100644
--- a/app/src/main/res/layout/activity_home.xml
+++ b/app/src/main/res/layout/activity_home.xml
@@ -9,7 +9,7 @@
android:layout_height="match_parent"
tools:context="com.nexenio.bleindoorpositioningdemo.HomeActivity">
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_recording_beacon.xml b/app/src/main/res/layout/content_recording_beacon.xml
new file mode 100644
index 0000000..0ce1e8e
--- /dev/null
+++ b/app/src/main/res/layout/content_recording_beacon.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_recording_device.xml b/app/src/main/res/layout/content_recording_device.xml
new file mode 100644
index 0000000..127aa12
--- /dev/null
+++ b/app/src/main/res/layout/content_recording_device.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_recording_general.xml b/app/src/main/res/layout/content_recording_general.xml
new file mode 100644
index 0000000..09c1006
--- /dev/null
+++ b/app/src/main/res/layout/content_recording_general.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/beacon_view.xml b/app/src/main/res/menu/beacon_view.xml
index f1444f0..0ddc130 100644
--- a/app/src/main/res/menu/beacon_view.xml
+++ b/app/src/main/res/menu/beacon_view.xml
@@ -2,9 +2,14 @@