diff --git a/app/build.gradle b/app/build.gradle
index bda0ea86..1bfdfe72 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -52,12 +52,14 @@ dependencies {
//https://github.com/google/dagger/issues/2123
implementation project(':profile_bps')
implementation project(':profile_csc')
+ implementation project(':profile_gls')
implementation project(':profile_hrs')
implementation project(':profile_hts')
- implementation project(':profile_gls')
+ implementation project(':profile_prx')
implementation project(':profile_rscs')
- implementation project(':profile_permission')
implementation project(':profile_scanner')
+
+ implementation project(':lib_permission')
implementation project(":lib_theme")
implementation project(":lib_utils")
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
index e8e10137..a67c99ca 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/HomeScreen.kt
@@ -33,6 +33,7 @@ import no.nordicsemi.android.hts.view.HTSScreen
import no.nordicsemi.android.permission.view.BluetoothNotAvailableScreen
import no.nordicsemi.android.permission.view.BluetoothNotEnabledScreen
import no.nordicsemi.android.permission.view.RequestPermissionScreen
+import no.nordicsemi.android.prx.view.PRXScreen
import no.nordicsemi.android.rscs.view.RSCSScreen
import no.nordicsemi.android.scanner.view.ScanDeviceScreen
import no.nordicsemi.android.scanner.view.ScanDeviceScreenResult
@@ -56,6 +57,7 @@ internal fun HomeScreen() {
composable(NavDestination.HTS.id) { HTSScreen { viewModel.navigateUp() } }
composable(NavDestination.GLS.id) { GLSScreen { viewModel.navigateUp() } }
composable(NavDestination.BPS.id) { BPSScreen { viewModel.navigateUp() } }
+ composable(NavDestination.PRX.id) { PRXScreen { viewModel.navigateUp() } }
composable(NavDestination.RSCS.id) { RSCSScreen { viewModel.navigateUp() } }
composable(NavDestination.REQUEST_PERMISSION.id) { RequestPermissionScreen(continueAction) }
composable(NavDestination.BLUETOOTH_NOT_AVAILABLE.id) { BluetoothNotAvailableScreen{ viewModel.finish() } }
@@ -99,6 +101,8 @@ fun HomeView(callback: (NavDestination) -> Unit) {
FeatureButton(R.drawable.ic_bps, R.string.bps_module) { callback(NavDestination.BPS) }
Spacer(modifier = Modifier.height(1.dp))
FeatureButton(R.drawable.ic_rscs, R.string.rscs_module) { callback(NavDestination.RSCS) }
+ Spacer(modifier = Modifier.height(1.dp))
+ FeatureButton(R.drawable.ic_proximity, R.string.prx_module) { callback(NavDestination.PRX) }
}
}
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt
index 9b28ebcf..fdcfecd1 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavDestination.kt
@@ -9,6 +9,7 @@ enum class NavDestination(val id: String) {
HTS("hts-screen"),
GLS("gls-screen"),
BPS("bps-screen"),
+ PRX("prx-screen"),
RSCS("rscs-screen"),
REQUEST_PERMISSION("request-permission"),
BLUETOOTH_NOT_AVAILABLE("bluetooth-not-available"),
diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationViewModel.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationViewModel.kt
index 1cbac2ca..a929352d 100644
--- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationViewModel.kt
+++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/NavigationViewModel.kt
@@ -12,6 +12,7 @@ import no.nordicsemi.android.permission.tools.NordicBleScanner
import no.nordicsemi.android.permission.tools.PermissionHelper
import no.nordicsemi.android.permission.tools.ScannerStatus
import no.nordicsemi.android.permission.viewmodel.BluetoothPermissionState
+import no.nordicsemi.android.prx.service.IMMEDIATE_ALERT_SERVICE_UUID
import no.nordicsemi.android.rscs.service.RSCS_SERVICE_UUID
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
import javax.inject.Inject
@@ -77,6 +78,7 @@ class NavigationViewModel @Inject constructor(
NavDestination.GLS -> GLS_SERVICE_UUID.toString()
NavDestination.BPS -> BPS_SERVICE_UUID.toString()
NavDestination.RSCS -> RSCS_SERVICE_UUID.toString()
+ NavDestination.PRX -> IMMEDIATE_ALERT_SERVICE_UUID.toString()
NavDestination.HOME,
NavDestination.REQUEST_PERMISSION,
NavDestination.BLUETOOTH_NOT_AVAILABLE,
diff --git a/app/src/main/res/drawable/ic_proximity.xml b/app/src/main/res/drawable/ic_proximity.xml
new file mode 100644
index 00000000..085a6050
--- /dev/null
+++ b/app/src/main/res/drawable/ic_proximity.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4ed85901..fa104f0b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,4 +5,5 @@
HTS
BPS
RSCS
+ PRX
\ No newline at end of file
diff --git a/profile_permission/build.gradle b/lib_permission/build.gradle
similarity index 100%
rename from profile_permission/build.gradle
rename to lib_permission/build.gradle
diff --git a/profile_permission/src/androidTest/java/no/nordicsemi/android/permission/ExampleInstrumentedTest.kt b/lib_permission/src/androidTest/java/no/nordicsemi/android/permission/ExampleInstrumentedTest.kt
similarity index 100%
rename from profile_permission/src/androidTest/java/no/nordicsemi/android/permission/ExampleInstrumentedTest.kt
rename to lib_permission/src/androidTest/java/no/nordicsemi/android/permission/ExampleInstrumentedTest.kt
diff --git a/profile_permission/src/main/AndroidManifest.xml b/lib_permission/src/main/AndroidManifest.xml
similarity index 100%
rename from profile_permission/src/main/AndroidManifest.xml
rename to lib_permission/src/main/AndroidManifest.xml
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/HiltModule.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/HiltModule.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/HiltModule.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/HiltModule.kt
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/tools/NordicBleScanner.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/tools/NordicBleScanner.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/tools/NordicBleScanner.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/tools/NordicBleScanner.kt
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/tools/PermissionHelper.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/tools/PermissionHelper.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/tools/PermissionHelper.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/tools/PermissionHelper.kt
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/tools/ScannerStatus.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/tools/ScannerStatus.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/tools/ScannerStatus.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/tools/ScannerStatus.kt
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/view/BluetoothNotAvailableScreen.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/view/BluetoothNotAvailableScreen.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/view/BluetoothNotAvailableScreen.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/view/BluetoothNotAvailableScreen.kt
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/view/NotConnectedView.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/view/NotConnectedView.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/view/NotConnectedView.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/view/NotConnectedView.kt
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/view/RequestPermissionScreen.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/view/RequestPermissionScreen.kt
similarity index 93%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/view/RequestPermissionScreen.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/view/RequestPermissionScreen.kt
index df68ea9c..6659d36c 100644
--- a/profile_permission/src/main/java/no/nordicsemi/android/permission/view/RequestPermissionScreen.kt
+++ b/lib_permission/src/main/java/no/nordicsemi/android/permission/view/RequestPermissionScreen.kt
@@ -77,7 +77,10 @@ private fun PermissionNotGranted(onClick: () -> Unit) {
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
- Text(textAlign = TextAlign.Center, text = stringResource(id = R.string.scanner__permission_rationale))
+ Text(
+ textAlign = TextAlign.Center,
+ text = stringResource(id = R.string.scanner__permission_rationale)
+ )
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(modifier = Modifier.width(100.dp), onClick = { onClick() }) {
@@ -102,7 +105,10 @@ private fun PermissionNotAvailable() {
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
- Text(stringResource(id = R.string.scanner__permission_denied))
+ Text(
+ textAlign = TextAlign.Center,
+ text = stringResource(id = R.string.scanner__permission_denied)
+ )
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { openPermissionSettings(context) }) {
Text(stringResource(id = R.string.scanner__open_settings))
diff --git a/profile_permission/src/main/java/no/nordicsemi/android/permission/viewmodel/BluetoothPermissionState.kt b/lib_permission/src/main/java/no/nordicsemi/android/permission/viewmodel/BluetoothPermissionState.kt
similarity index 100%
rename from profile_permission/src/main/java/no/nordicsemi/android/permission/viewmodel/BluetoothPermissionState.kt
rename to lib_permission/src/main/java/no/nordicsemi/android/permission/viewmodel/BluetoothPermissionState.kt
diff --git a/profile_permission/src/main/res/values/strings.xml b/lib_permission/src/main/res/values/strings.xml
similarity index 100%
rename from profile_permission/src/main/res/values/strings.xml
rename to lib_permission/src/main/res/values/strings.xml
diff --git a/profile_permission/src/test/java/no/nordicsemi/android/permission/ExampleUnitTest.kt b/lib_permission/src/test/java/no/nordicsemi/android/permission/ExampleUnitTest.kt
similarity index 100%
rename from profile_permission/src/test/java/no/nordicsemi/android/permission/ExampleUnitTest.kt
rename to lib_permission/src/test/java/no/nordicsemi/android/permission/ExampleUnitTest.kt
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDateTimeParser.kt
similarity index 98%
rename from profile_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
rename to profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDateTimeParser.kt
index 9b1d266f..f6d95040 100644
--- a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/DateTimeParser.kt
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSDateTimeParser.kt
@@ -25,7 +25,7 @@ import no.nordicsemi.android.ble.common.callback.DateTimeDataCallback
import no.nordicsemi.android.ble.data.Data
import java.util.*
-object DateTimeParser {
+internal object HTSDateTimeParser {
/**
* Parses the date and time info.
*
@@ -50,4 +50,4 @@ object DateTimeParser {
val calendar = DateTimeDataCallback.readDateTime(data, offset)
return String.format(Locale.US, "%1\$te %1\$tb %1\$tY, %1\$tH:%1\$tM:%1\$tS", calendar)
}
-}
\ No newline at end of file
+}
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
index 14b7c7f5..4d8198c3 100644
--- a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSManager.kt
@@ -42,7 +42,10 @@ private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-
* enabling indication and reading characteristics. All operations required to connect to device
* with BLE HT Service and reading health thermometer values are performed here.
*/
-class HTSManager internal constructor(context: Context, private val dataHolder: HTSDataHolder) : BatteryManager(context) {
+internal class HTSManager internal constructor(
+ context: Context,
+ private val dataHolder: HTSDataHolder
+) : BatteryManager(context) {
private var htCharacteristic: BluetoothGattCharacteristic? = null
@@ -50,7 +53,7 @@ class HTSManager internal constructor(context: Context, private val dataHolder:
override fun onDataReceived(device: BluetoothDevice, data: Data) {
log(
LogContract.Log.Level.APPLICATION,
- "\"" + TemperatureMeasurementParser.parse(data) + "\" received"
+ "\"" + HTSTemperatureMeasurementParser.parse(data) + "\" received"
)
super.onDataReceived(device, data)
}
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureMeasurementParser.java b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSTemperatureMeasurementParser.kt
similarity index 55%
rename from profile_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureMeasurementParser.java
rename to profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSTemperatureMeasurementParser.kt
index 2d0b5b66..cae099da 100644
--- a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureMeasurementParser.java
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSTemperatureMeasurementParser.kt
@@ -19,67 +19,55 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package no.nordicsemi.android.hts.service;
+package no.nordicsemi.android.hts.service
-import java.util.Locale;
+import no.nordicsemi.android.ble.data.Data
+import java.util.*
-import no.nordicsemi.android.ble.data.Data;
+private const val TEMPERATURE_UNIT_FLAG: Byte = 0x01 // 1 bit
+private const val TIMESTAMP_FLAG: Byte = 0x02 // 1 bits
+private const val TEMPERATURE_TYPE_FLAG: Byte = 0x04 // 1 bit
-@SuppressWarnings("ConstantConditions")
-public class TemperatureMeasurementParser {
- private static final byte TEMPERATURE_UNIT_FLAG = 0x01; // 1 bit
- private static final byte TIMESTAMP_FLAG = 0x02; // 1 bits
- private static final byte TEMPERATURE_TYPE_FLAG = 0x04; // 1 bit
+internal object HTSTemperatureMeasurementParser {
- public static String parse(final Data data) {
- int offset = 0;
- final int flags = data.getIntValue(Data.FORMAT_UINT8, offset++);
+ fun parse(data: Data): String {
+ var offset = 0
+ val flags = data.getIntValue(Data.FORMAT_UINT8, offset++)!!
- /*
+ /*
* false Temperature is in Celsius degrees
* true Temperature is in Fahrenheit degrees
*/
- final boolean fahrenheit = (flags & TEMPERATURE_UNIT_FLAG) > 0;
+ val fahrenheit = flags and TEMPERATURE_UNIT_FLAG.toInt() > 0
- /*
+ /*
* false No Timestamp in the packet
* true There is a timestamp information
*/
- final boolean timestampIncluded = (flags & TIMESTAMP_FLAG) > 0;
+ val timestampIncluded = flags and TIMESTAMP_FLAG.toInt() > 0
- /*
+ /*
* false Temperature type is not included
* true Temperature type included in the packet
*/
- final boolean temperatureTypeIncluded = (flags & TEMPERATURE_TYPE_FLAG) > 0;
-
- final float tempValue = data.getFloatValue(Data.FORMAT_FLOAT, offset);
- offset += 4;
-
- String dateTime = null;
- if (timestampIncluded) {
- dateTime = DateTimeParser.parse(data, offset);
- offset += 7;
- }
-
- String type = null;
- if (temperatureTypeIncluded) {
- type = TemperatureTypeParser.parse(data, offset);
- // offset++;
- }
-
- final StringBuilder builder = new StringBuilder();
- builder.append(String.format(Locale.US, "%.02f", tempValue));
-
- if (fahrenheit)
- builder.append("°F");
- else
- builder.append("°C");
-
- if (timestampIncluded)
- builder.append("\nTime: ").append(dateTime);
- if (temperatureTypeIncluded)
- builder.append("\nType: ").append(type);
- return builder.toString();
- }
+ val temperatureTypeIncluded = flags and TEMPERATURE_TYPE_FLAG.toInt() > 0
+ val tempValue = data.getFloatValue(Data.FORMAT_FLOAT, offset)!!
+ offset += 4
+ var dateTime: String? = null
+ if (timestampIncluded) {
+ dateTime = HTSDateTimeParser.parse(data, offset)
+ offset += 7
+ }
+ var type: String? = null
+ if (temperatureTypeIncluded) {
+ type = HTSTemperatureTypeParser.parse(data, offset)
+ // offset++;
+ }
+ val builder = StringBuilder()
+ builder.append(String.format(Locale.US, "%.02f", tempValue))
+ if (fahrenheit) builder.append("°F") else builder.append("°C")
+ if (timestampIncluded) builder.append("\nTime: ").append(dateTime)
+ if (temperatureTypeIncluded) builder.append("\nType: ").append(type)
+ return builder.toString()
+ }
}
diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureTypeParser.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSTemperatureTypeParser.kt
similarity index 98%
rename from profile_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureTypeParser.kt
rename to profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSTemperatureTypeParser.kt
index dc789973..339bfd52 100644
--- a/profile_hts/src/main/java/no/nordicsemi/android/hts/service/TemperatureTypeParser.kt
+++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/service/HTSTemperatureTypeParser.kt
@@ -23,7 +23,7 @@ package no.nordicsemi.android.hts.service
import no.nordicsemi.android.ble.data.Data
-object TemperatureTypeParser {
+internal object HTSTemperatureTypeParser {
fun parse(data: Data): String {
return parse(data, 0)
}
diff --git a/profile_prx/build.gradle b/profile_prx/build.gradle
new file mode 100644
index 00000000..d397c91b
--- /dev/null
+++ b/profile_prx/build.gradle
@@ -0,0 +1,26 @@
+apply from: rootProject.file("library.gradle")
+apply plugin: 'kotlin-parcelize'
+
+dependencies {
+ implementation project(":lib_service")
+ implementation project(":lib_theme")
+ implementation project(":lib_utils")
+
+ implementation libs.nordic.ble.common
+
+ implementation libs.nordic.log
+
+ implementation libs.bundles.compose
+ implementation libs.androidx.core
+ implementation libs.material
+ implementation libs.lifecycle.activity
+ implementation libs.lifecycle.service
+ implementation libs.compose.lifecycle
+ implementation libs.compose.activity
+
+ testImplementation libs.test.junit
+ androidTestImplementation libs.android.test.junit
+ androidTestImplementation libs.android.test.espresso
+ androidTestImplementation libs.android.test.compose.ui
+ debugImplementation libs.android.test.compose.tooling
+}
diff --git a/profile_prx/src/androidTest/java/no/nordicsemi/android/prx/ExampleInstrumentedTest.kt b/profile_prx/src/androidTest/java/no/nordicsemi/android/prx/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..5e8752f6
--- /dev/null
+++ b/profile_prx/src/androidTest/java/no/nordicsemi/android/prx/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package no.nordicsemi.android.prx
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("no.nordicsemi.android.prx.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/profile_prx/src/main/AndroidManifest.xml b/profile_prx/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..f322bb43
--- /dev/null
+++ b/profile_prx/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt
new file mode 100644
index 00000000..926e6608
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXData.kt
@@ -0,0 +1,20 @@
+package no.nordicsemi.android.prx.data
+
+internal data class PRXData(
+ private val batteryLevel: Int = 0,
+ private val localAlarmLevel: AlarmLevel = AlarmLevel.NONE,
+ private val remoteAlarmLevel: Boolean = false
+)
+
+internal enum class AlarmLevel(val value: Int) {
+ NONE(0x00),
+ MEDIUM(0x01),
+ HIGH(0x02);
+
+ companion object {
+ fun create(value: Int): AlarmLevel {
+ return AlarmLevel.values().firstOrNull { it.value == value }
+ ?: throw IllegalArgumentException("Cannot find AlarmLevel for provided value: $value")
+ }
+ }
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXDataHolder.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXDataHolder.kt
new file mode 100644
index 00000000..4f2fab54
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXDataHolder.kt
@@ -0,0 +1,30 @@
+package no.nordicsemi.android.prx.data
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class PRXDataHolder @Inject constructor() {
+
+ private val _data = MutableStateFlow(PRXData())
+ val data: StateFlow = _data
+
+ fun setBatteryLevel(batteryLevel: Int) {
+ _data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
+ }
+
+ fun setLocalAlarmLevel(value: Int) {
+ val alarmLevel = AlarmLevel.create(value)
+ _data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel))
+ }
+
+ fun setRemoteAlarmLevel(isOn: Boolean) {
+ _data.tryEmit(_data.value.copy(remoteAlarmLevel = isOn))
+ }
+
+ fun clear(){
+ _data.tryEmit(PRXData())
+ }
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXAlertLevelParser.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXAlertLevelParser.kt
new file mode 100644
index 00000000..43084db8
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXAlertLevelParser.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package no.nordicsemi.android.prx.service
+
+import android.bluetooth.BluetoothGattCharacteristic
+import no.nordicsemi.android.ble.data.Data
+
+internal object PRXAlertLevelParser {
+
+ fun parse(characteristic: BluetoothGattCharacteristic?): String {
+ return parse(Data.from(characteristic!!))
+ }
+
+ /**
+ * Parses the alert level.
+ *
+ * @param data
+ * @return alert level in human readable format
+ */
+ fun parse(data: Data): String {
+ val value = data.getIntValue(Data.FORMAT_UINT8, 0)!!
+ return when (value) {
+ 0 -> "No Alert"
+ 1 -> "Mild Alert"
+ 2 -> "High Alert"
+ else -> "Reserved value ($value)"
+ }
+ }
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXManager.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXManager.kt
new file mode 100644
index 00000000..7b39a5f7
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXManager.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015, Nordic Semiconductor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package no.nordicsemi.android.prx.service
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothGatt
+import android.bluetooth.BluetoothGattCharacteristic
+import android.bluetooth.BluetoothGattServer
+import android.content.Context
+import android.util.Log
+import no.nordicsemi.android.ble.callback.FailCallback
+import no.nordicsemi.android.ble.common.callback.alert.AlertLevelDataCallback
+import no.nordicsemi.android.ble.common.data.alert.AlertLevelData
+import no.nordicsemi.android.ble.data.Data
+import no.nordicsemi.android.ble.error.GattError
+import no.nordicsemi.android.log.LogContract
+import no.nordicsemi.android.prx.data.PRXDataHolder
+import no.nordicsemi.android.service.BatteryManager
+import java.util.*
+
+/** Link Loss service UUID. */
+val LINK_LOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb")
+
+/** Immediate Alert service UUID. */
+val IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb")
+
+/** Alert Level characteristic UUID. */
+val ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-00805f9b34fb")
+
+internal class PRXManager(
+ context: Context,
+ private val dataHolder: PRXDataHolder
+) : BatteryManager(context) {
+
+ // Client characteristics.
+ private var alertLevelCharacteristic: BluetoothGattCharacteristic? = null
+ private var linkLossCharacteristic: BluetoothGattCharacteristic? = null
+
+ // Server characteristics.
+ private var localAlertLevelCharacteristic: BluetoothGattCharacteristic? = null
+ /**
+ * Returns true if the alert has been enabled on the proximity tag, false otherwise.
+ */
+ /** A flag indicating whether the alarm on the connected proximity tag has been activated. */
+ var isAlertEnabled = false
+ private set
+
+ /**
+ * BluetoothGatt callbacks for connection/disconnection, service discovery,
+ * receiving indication, etc.
+ */
+ private inner class ProximityManagerGattCallback : BatteryManagerGattCallback() {
+ override fun initialize() {
+ super.initialize()
+ // This callback will be called whenever local Alert Level char is written
+ // by a connected proximity tag.
+ setWriteCallback(localAlertLevelCharacteristic)
+ .with(object : AlertLevelDataCallback() {
+ override fun onAlertLevelChanged(device: BluetoothDevice, level: Int) {
+ dataHolder.setLocalAlarmLevel(level)
+ }
+ })
+ // After connection, set the Link Loss behaviour on the tag.
+ writeCharacteristic(linkLossCharacteristic, AlertLevelData.highAlert())
+ .done { device: BluetoothDevice? ->
+ log(
+ Log.INFO,
+ "Link loss alert level set"
+ )
+ }
+ .fail { device: BluetoothDevice?, status: Int ->
+ log(
+ Log.WARN,
+ "Failed to set link loss level: $status"
+ )
+ }
+ .enqueue()
+ }
+
+ override fun onServerReady(server: BluetoothGattServer) {
+ val immediateAlertService = server.getService(IMMEDIATE_ALERT_SERVICE_UUID)
+ if (immediateAlertService != null) {
+ localAlertLevelCharacteristic = immediateAlertService.getCharacteristic(
+ ALERT_LEVEL_CHARACTERISTIC_UUID
+ )
+ }
+ }
+
+ override fun onServicesInvalidated() { }
+
+ override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
+ val llService = gatt.getService(LINK_LOSS_SERVICE_UUID)
+ if (llService != null) {
+ linkLossCharacteristic =
+ llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
+ }
+ return linkLossCharacteristic != null
+ }
+
+ override fun isOptionalServiceSupported(gatt: BluetoothGatt): Boolean {
+ super.isOptionalServiceSupported(gatt)
+ val iaService = gatt.getService(IMMEDIATE_ALERT_SERVICE_UUID)
+ if (iaService != null) {
+ alertLevelCharacteristic = iaService.getCharacteristic(
+ ALERT_LEVEL_CHARACTERISTIC_UUID
+ )
+ }
+ return alertLevelCharacteristic != null
+ }
+
+ override fun onDeviceDisconnected() {
+ super.onDeviceDisconnected()
+ alertLevelCharacteristic = null
+ linkLossCharacteristic = null
+ localAlertLevelCharacteristic = null
+ // Reset the alert flag
+ isAlertEnabled = false
+ }
+ }
+
+ /**
+ * Toggles the immediate alert on the target device.
+ */
+ fun toggleImmediateAlert() {
+ writeImmediateAlert(!isAlertEnabled)
+ }
+
+ /**
+ * Writes the HIGH ALERT or NO ALERT command to the target device.
+ *
+ * @param on true to enable the alarm on proximity tag, false to disable it.
+ */
+ fun writeImmediateAlert(on: Boolean) {
+ if (!isConnected()) return
+ writeCharacteristic(
+ alertLevelCharacteristic,
+ if (on) AlertLevelData.highAlert() else AlertLevelData.noAlert()
+ )
+ .before { device: BluetoothDevice? ->
+ log(
+ Log.VERBOSE,
+ if (on) "Setting alarm to HIGH..." else "Disabling alarm..."
+ )
+ }
+ .with { _: BluetoothDevice, data: Data ->
+ log(
+ LogContract.Log.Level.APPLICATION,
+ "\"" + PRXAlertLevelParser.parse(data) + "\" sent"
+ )
+ }
+ .done { device: BluetoothDevice? ->
+ isAlertEnabled = on
+ dataHolder.setRemoteAlarmLevel(on)
+ }
+ .fail { device: BluetoothDevice?, status: Int ->
+ log(
+ Log.WARN,
+ if (status == FailCallback.REASON_NULL_ATTRIBUTE) "Alert Level characteristic not found" else GattError.parse(
+ status
+ )
+ )
+ }
+ .enqueue()
+ }
+
+ override fun onBatteryLevelChanged(batteryLevel: Int) {
+ dataHolder.setBatteryLevel(batteryLevel)
+ }
+
+ override fun getGattCallback(): BleManagerGattCallback {
+ return ProximityManagerGattCallback()
+ }
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXService.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXService.kt
new file mode 100644
index 00000000..071c542d
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/service/PRXService.kt
@@ -0,0 +1,15 @@
+package no.nordicsemi.android.prx.service
+
+import dagger.hilt.android.AndroidEntryPoint
+import no.nordicsemi.android.prx.data.PRXDataHolder
+import no.nordicsemi.android.service.ForegroundBleService
+import javax.inject.Inject
+
+@AndroidEntryPoint
+internal class PRXService : ForegroundBleService() {
+
+ @Inject
+ lateinit var dataHolder: PRXDataHolder
+
+ override val manager: PRXManager by lazy { PRXManager(this, dataHolder) }
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXContentView.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXContentView.kt
new file mode 100644
index 00000000..0cc33481
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXContentView.kt
@@ -0,0 +1,11 @@
+package no.nordicsemi.android.prx.view
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import no.nordicsemi.android.prx.data.PRXData
+
+@Composable
+internal fun ContentView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
+
+ Text(text = "aa")
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt
new file mode 100644
index 00000000..dbf2981f
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt
@@ -0,0 +1,61 @@
+package no.nordicsemi.android.prx.view
+
+import android.content.Intent
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.hilt.navigation.compose.hiltViewModel
+import no.nordicsemi.android.prx.R
+import no.nordicsemi.android.prx.data.PRXData
+import no.nordicsemi.android.prx.service.PRXService
+import no.nordicsemi.android.prx.viewmodel.PRXViewModel
+import no.nordicsemi.android.theme.view.BackIconAppBar
+import no.nordicsemi.android.utils.isServiceRunning
+
+@Composable
+fun PRXScreen(finishAction: () -> Unit) {
+ val viewModel: PRXViewModel = hiltViewModel()
+ val state = viewModel.state.collectAsState().value
+ val isActive = viewModel.isActive.collectAsState().value
+
+ val context = LocalContext.current
+ LaunchedEffect(isActive) {
+ if (!isActive) {
+ finishAction()
+ }
+ if (context.isServiceRunning(PRXService::class.java.name)) {
+ val intent = Intent(context, PRXService::class.java)
+ context.stopService(intent)
+ }
+ }
+
+ LaunchedEffect("start-service") {
+ if (!context.isServiceRunning(PRXService::class.java.name)) {
+ val intent = Intent(context, PRXService::class.java)
+ context.startService(intent)
+ }
+ }
+
+ PRXView(state) { viewModel.onEvent(it) }
+}
+
+@Composable
+private fun PRXView(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
+ Column {
+ BackIconAppBar(stringResource(id = R.string.prx_title)) {
+ onEvent(DisconnectEvent)
+ }
+
+ ContentView(state) { onEvent(it) }
+ }
+}
+
+@Preview
+@Composable
+private fun PRXViewPreview(state: PRXData, onEvent: (PRXScreenViewEvent) -> Unit) {
+ PRXView(state) { }
+}
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt
new file mode 100644
index 00000000..e88ed02e
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreenViewEvent.kt
@@ -0,0 +1,5 @@
+package no.nordicsemi.android.prx.view
+
+internal sealed class PRXScreenViewEvent
+
+internal object DisconnectEvent : PRXScreenViewEvent()
diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt
new file mode 100644
index 00000000..167a521a
--- /dev/null
+++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt
@@ -0,0 +1,28 @@
+package no.nordicsemi.android.prx.viewmodel
+
+import dagger.hilt.android.lifecycle.HiltViewModel
+import no.nordicsemi.android.prx.data.PRXDataHolder
+import no.nordicsemi.android.prx.view.DisconnectEvent
+import no.nordicsemi.android.prx.view.PRXScreenViewEvent
+import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
+import no.nordicsemi.android.utils.exhaustive
+import javax.inject.Inject
+
+@HiltViewModel
+internal class PRXViewModel @Inject constructor(
+ private val dataHolder: PRXDataHolder
+) : CloseableViewModel() {
+
+ val state = dataHolder.data
+
+ fun onEvent(event: PRXScreenViewEvent) {
+ when (event) {
+ DisconnectEvent -> onDisconnectButtonClick()
+ }.exhaustive
+ }
+
+ private fun onDisconnectButtonClick() {
+ finish()
+ dataHolder.clear()
+ }
+}
diff --git a/profile_prx/src/main/res/values/strings.xml b/profile_prx/src/main/res/values/strings.xml
new file mode 100644
index 00000000..6d3258a2
--- /dev/null
+++ b/profile_prx/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Proximity
+
diff --git a/profile_prx/src/test/java/no/nordicsemi/android/prx/ExampleUnitTest.kt b/profile_prx/src/test/java/no/nordicsemi/android/prx/ExampleUnitTest.kt
new file mode 100644
index 00000000..774842bf
--- /dev/null
+++ b/profile_prx/src/test/java/no/nordicsemi/android/prx/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package no.nordicsemi.android.prx
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index b361e1f2..a75b3116 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -67,9 +67,11 @@ include ':profile_csc'
include ':profile_gls'
include ':profile_hrs'
include ':profile_hts'
+include ':profile_prx'
include ':profile_rscs'
-include ':profile_permission'
+include ':profile_scanner'
+include ':lib_permission'
include ':lib_service'
include ':lib_theme'
include ':lib_utils'
@@ -81,4 +83,3 @@ if (file('../Android-BLE-Library').exists()) {
if (file('../Android-Scanner-Compat-Library').exists()) {
includeBuild('../Android-Scanner-Compat-Library')
}
-include ':profile_scanner'