mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-20 07:54:20 +01:00
Make test with service working
This commit is contained in:
@@ -29,37 +29,32 @@
|
||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package no.nordicsemi.android.uart
|
||||
package no.nordicsemi.android.nrftoolbox
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import no.nordicsemi.android.uart.db.ConfigurationsDao
|
||||
import no.nordicsemi.android.uart.db.ConfigurationsDatabase
|
||||
import no.nordicsemi.android.uart.db.MIGRATION_1_2
|
||||
import javax.inject.Singleton
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import no.nordicsemi.android.analytics.AppAnalytics
|
||||
import no.nordicsemi.android.analytics.AppOpenEvent
|
||||
import no.nordicsemi.android.gls.UartServer
|
||||
import javax.inject.Inject
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class HiltModule {
|
||||
@HiltAndroidApp
|
||||
class NrfToolboxApplication : Application() {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideDB(@ApplicationContext context: Context): ConfigurationsDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
ConfigurationsDatabase::class.java, "toolbox_uart.db"
|
||||
).addMigrations(MIGRATION_1_2).build()
|
||||
@Inject
|
||||
lateinit var analytics: AppAnalytics
|
||||
|
||||
@Inject
|
||||
lateinit var glsServer: UartServer
|
||||
|
||||
@Inject
|
||||
lateinit var uartServer: UartServer
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
analytics.logEvent(AppOpenEvent)
|
||||
|
||||
uartServer.start(this)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideDao(db: ConfigurationsDatabase): ConfigurationsDao {
|
||||
return db.dao()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import no.nordicsemi.android.analytics.AppAnalytics
|
||||
import no.nordicsemi.android.analytics.AppOpenEvent
|
||||
import no.nordicsemi.android.gls.GlsServer
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
@@ -44,14 +43,9 @@ class NrfToolboxApplication : Application() {
|
||||
@Inject
|
||||
lateinit var analytics: AppAnalytics
|
||||
|
||||
@Inject
|
||||
lateinit var glsServer: GlsServer
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
analytics.logEvent(AppOpenEvent)
|
||||
|
||||
glsServer.start(this)
|
||||
}
|
||||
}
|
||||
@@ -31,41 +31,11 @@
|
||||
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
const val DEVICE_DATA = "device-data"
|
||||
|
||||
class ServiceManager @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context
|
||||
) {
|
||||
interface ServiceManager {
|
||||
|
||||
fun <T> startService(service: Class<T>, device: ServerDevice) {
|
||||
val intent = Intent(context, service).apply {
|
||||
putExtra(DEVICE_DATA, device)
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun <T> startService(service: Class<T>, device: BluetoothDevice) {
|
||||
val intent = Intent(context, service).apply {
|
||||
putExtra(DEVICE_DATA, device)
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun <T> startService(service: Class<T>) {
|
||||
val intent = Intent(context, service)
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun <T> stopService(service: Class<T>) {
|
||||
val intent = Intent(context, service)
|
||||
context.stopService(intent)
|
||||
}
|
||||
fun <T> startService(service: Class<T>, device: ServerDevice)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class ServiceManagerHiltModule {
|
||||
|
||||
@Provides
|
||||
fun createServiceManager(
|
||||
@ApplicationContext
|
||||
context: Context,
|
||||
): ServiceManager {
|
||||
return ServiceManagerImpl(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
class ServiceManagerImpl @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context
|
||||
): ServiceManager {
|
||||
|
||||
override fun <T> startService(service: Class<T>, device: ServerDevice) {
|
||||
val intent = Intent(context, service).apply {
|
||||
putExtra(DEVICE_DATA, device)
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,10 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "no.nordicsemi.android.ui"
|
||||
|
||||
testOptions {
|
||||
unitTests.isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -6,12 +6,11 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import no.nordicsemi.android.common.logger.NordicBlekLogger
|
||||
import no.nordicsemi.android.common.logger.BlekLogger
|
||||
import no.nordicsemi.android.common.logger.BlekLoggerAndLauncher
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class HiltModule {
|
||||
class NordicLoggerFactoryHiltModule {
|
||||
|
||||
@Provides
|
||||
fun createLogger(): NordicLoggerFactory {
|
||||
@@ -119,8 +119,6 @@ internal class GLSViewModel @Inject constructor(
|
||||
private val highestSequenceNumber
|
||||
get() = state.value.glsServiceData.records.keys.maxByOrNull { it.sequenceNumber }?.sequenceNumber ?: -1
|
||||
|
||||
fun test() = 2
|
||||
|
||||
init {
|
||||
navigationManager.navigateTo(ScannerDestinationId, ParcelUuid(GLS_SERVICE_UUID))
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import no.nordicsemi.android.ui.view.NordicLoggerFactory
|
||||
import no.nordicsemi.android.ui.view.StringConst
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -121,11 +120,6 @@ internal class GLSViewModelTest {
|
||||
every { NordicBlekLogger.create(any(), any(), any(), any()) } returns mockk()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(2, viewModel.test())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when connection fails return disconnected`() = runTest {
|
||||
val disconnectedState = GattConnectionStateWithStatus(
|
||||
|
||||
@@ -38,6 +38,10 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "no.nordicsemi.android.uart"
|
||||
|
||||
testOptions {
|
||||
unitTests.isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
wire {
|
||||
@@ -54,6 +58,8 @@ dependencies {
|
||||
implementation(libs.nordic.blek.client)
|
||||
implementation(libs.nordic.blek.profile)
|
||||
implementation(libs.nordic.blek.core)
|
||||
implementation(libs.nordic.blek.server)
|
||||
implementation(libs.nordic.blek.advertiser)
|
||||
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.room.ktx)
|
||||
@@ -81,6 +87,21 @@ dependencies {
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.lifecycle.service)
|
||||
|
||||
// For Robolectric tests.
|
||||
testImplementation("com.google.dagger:hilt-android-testing:2.44")
|
||||
// ...with Kotlin.
|
||||
kaptTest("com.google.dagger:hilt-android-compiler:2.46.1")
|
||||
|
||||
testImplementation("androidx.test:rules:1.5.0")
|
||||
|
||||
testImplementation(libs.junit4)
|
||||
testImplementation(libs.test.mockk)
|
||||
testImplementation(libs.androidx.test.ext)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
testImplementation(libs.test.slf4j.simple)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(libs.kotlin.junit)
|
||||
|
||||
implementation("org.simpleframework:simple-xml:2.7.1") {
|
||||
exclude(group = "stax", module = "stax-api")
|
||||
exclude(group = "xpp3", module = "xpp3")
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.kotlin.ble.advertiser.BleAdvertiser
|
||||
import no.nordicsemi.android.kotlin.ble.core.MockServerDevice
|
||||
import no.nordicsemi.android.kotlin.ble.core.advertiser.BleAdvertiseConfig
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.BleGattPermission
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.BleGattProperty
|
||||
import no.nordicsemi.android.kotlin.ble.profile.gls.RecordAccessControlPointInputParser
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.BleGattServer
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleGattServerServiceType
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleServerGattCharacteristic
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleServerGattCharacteristicConfig
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleServerGattServiceConfig
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BluetoothGattServerConnection
|
||||
import no.nordicsemi.android.uart.repository.BATTERY_LEVEL_CHARACTERISTIC_UUID
|
||||
import no.nordicsemi.android.uart.repository.BATTERY_SERVICE_UUID
|
||||
import no.nordicsemi.android.uart.repository.UART_RX_CHARACTERISTIC_UUID
|
||||
import no.nordicsemi.android.uart.repository.UART_SERVICE_UUID
|
||||
import no.nordicsemi.android.uart.repository.UART_TX_CHARACTERISTIC_UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val STANDARD_DELAY = 1000L
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Singleton
|
||||
class UartServer @Inject constructor(
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
|
||||
lateinit var server: BleGattServer
|
||||
|
||||
lateinit var glsCharacteristic: BleServerGattCharacteristic
|
||||
lateinit var glsContextCharacteristic: BleServerGattCharacteristic
|
||||
lateinit var racpCharacteristic: BleServerGattCharacteristic
|
||||
lateinit var batteryLevelCharacteristic: BleServerGattCharacteristic
|
||||
|
||||
private var lastRequest = byteArrayOf()
|
||||
|
||||
val YOUNGEST_RECORD = byteArrayOf(0x07, 0x00, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x05, 0x00, 0x00, 0x26, 0xD2.toByte(), 0x11)
|
||||
val OLDEST_RECORD = byteArrayOf(0x07, 0x04, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x11, 0x00, 0x00, 0x82.toByte(), 0xD2.toByte(), 0x11)
|
||||
|
||||
val records = listOf(
|
||||
YOUNGEST_RECORD,
|
||||
byteArrayOf(0x07, 0x01, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x08, 0x00, 0x00, 0x3D, 0xD2.toByte(), 0x11),
|
||||
byteArrayOf(0x07, 0x02, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x0B, 0x00, 0x00, 0x54, 0xD2.toByte(), 0x11),
|
||||
byteArrayOf(0x07, 0x03, 0x00, 0xDC.toByte(), 0x07, 0x01, 0x01, 0x0C, 0x1E, 0x0E, 0x00, 0x00, 0x6B, 0xD2.toByte(), 0x11),
|
||||
OLDEST_RECORD
|
||||
)
|
||||
|
||||
val racp = byteArrayOf(0x06, 0x00, 0x01, 0x01)
|
||||
|
||||
fun start(
|
||||
context: Context,
|
||||
device: MockServerDevice = MockServerDevice(
|
||||
name = "GLS Server",
|
||||
address = "55:44:33:22:11"
|
||||
),
|
||||
) = scope.launch {
|
||||
val rxCharacteristic = BleServerGattCharacteristicConfig(
|
||||
UART_RX_CHARACTERISTIC_UUID,
|
||||
listOf(BleGattProperty.PROPERTY_NOTIFY),
|
||||
listOf()
|
||||
)
|
||||
|
||||
val txCharacteristic = BleServerGattCharacteristicConfig(
|
||||
UART_TX_CHARACTERISTIC_UUID,
|
||||
listOf(BleGattProperty.PROPERTY_INDICATE, BleGattProperty.PROPERTY_WRITE),
|
||||
listOf(BleGattPermission.PERMISSION_WRITE)
|
||||
)
|
||||
|
||||
val uartService = BleServerGattServiceConfig(
|
||||
UART_SERVICE_UUID,
|
||||
BleGattServerServiceType.SERVICE_TYPE_PRIMARY,
|
||||
listOf(rxCharacteristic, txCharacteristic)
|
||||
)
|
||||
|
||||
val batteryLevelCharacteristic = BleServerGattCharacteristicConfig(
|
||||
BATTERY_LEVEL_CHARACTERISTIC_UUID,
|
||||
listOf(BleGattProperty.PROPERTY_READ, BleGattProperty.PROPERTY_NOTIFY),
|
||||
listOf(BleGattPermission.PERMISSION_READ)
|
||||
)
|
||||
|
||||
val batteryService = BleServerGattServiceConfig(
|
||||
BATTERY_SERVICE_UUID,
|
||||
BleGattServerServiceType.SERVICE_TYPE_PRIMARY,
|
||||
listOf(batteryLevelCharacteristic)
|
||||
)
|
||||
|
||||
server = BleGattServer.create(
|
||||
context = context,
|
||||
config = arrayOf(uartService, batteryService),
|
||||
mock = device
|
||||
)
|
||||
|
||||
val advertiser = BleAdvertiser.create(context)
|
||||
advertiser.advertise(config = BleAdvertiseConfig(), mock = device).launchIn(scope)
|
||||
|
||||
launch {
|
||||
server.connections
|
||||
.mapNotNull { it.values.firstOrNull() }
|
||||
.collect { setUpConnection(it) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun stopServer() {
|
||||
server.stopServer()
|
||||
}
|
||||
|
||||
private fun setUpConnection(connection: BluetoothGattServerConnection) {
|
||||
// val glsService = connection.services.findService(GLS_SERVICE_UUID)!!
|
||||
// glsCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!!
|
||||
// glsContextCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC)!!
|
||||
// racpCharacteristic = glsService.findCharacteristic(RACP_CHARACTERISTIC)!!
|
||||
|
||||
val batteryService = connection.services.findService(BATTERY_SERVICE_UUID)!!
|
||||
batteryLevelCharacteristic = batteryService.findCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)!!
|
||||
|
||||
|
||||
// startGlsService(connection)
|
||||
// startBatteryService(connection)
|
||||
}
|
||||
|
||||
private fun startGlsService(connection: BluetoothGattServerConnection) {
|
||||
racpCharacteristic.value
|
||||
.filter { it.isNotEmpty() }
|
||||
.onEach { lastRequest = it }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
internal fun continueWithResponse() {
|
||||
sendResponse(lastRequest)
|
||||
}
|
||||
|
||||
private fun sendResponse(request: ByteArray) {
|
||||
if (request.contentEquals(RecordAccessControlPointInputParser.reportNumberOfAllStoredRecords().value)) {
|
||||
sendAll(glsCharacteristic)
|
||||
racpCharacteristic.setValue(racp)
|
||||
} else if (request.contentEquals(RecordAccessControlPointInputParser.reportLastStoredRecord().value)) {
|
||||
sendLast(glsCharacteristic)
|
||||
racpCharacteristic.setValue(racp)
|
||||
} else if (request.contentEquals(RecordAccessControlPointInputParser.reportFirstStoredRecord().value)) {
|
||||
sendFirst(glsCharacteristic)
|
||||
racpCharacteristic.setValue(racp)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendFirst(characteristics: BleServerGattCharacteristic) {
|
||||
characteristics.setValue(records.first())
|
||||
}
|
||||
|
||||
private fun sendLast(characteristics: BleServerGattCharacteristic) {
|
||||
characteristics.setValue(records.last())
|
||||
}
|
||||
|
||||
private fun sendAll(characteristics: BleServerGattCharacteristic) = scope.launch {
|
||||
records.forEach {
|
||||
characteristics.setValue(it)
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startBatteryService(connection: BluetoothGattServerConnection) {
|
||||
scope.launch {
|
||||
repeat(100) {
|
||||
batteryLevelCharacteristic.setValue(byteArrayOf(0x61))
|
||||
delay(STANDARD_DELAY)
|
||||
batteryLevelCharacteristic.setValue(byteArrayOf(0x60))
|
||||
delay(STANDARD_DELAY)
|
||||
batteryLevelCharacteristic.setValue(byteArrayOf(0x5F))
|
||||
delay(STANDARD_DELAY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package no.nordicsemi.android.uart
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import no.nordicsemi.android.uart.db.ConfigurationsDao
|
||||
import no.nordicsemi.android.uart.db.ConfigurationsDatabase
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class DaoHiltModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideDao(db: ConfigurationsDatabase): ConfigurationsDao {
|
||||
return db.dao()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package no.nordicsemi.android.uart
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import no.nordicsemi.android.uart.db.ConfigurationsDatabase
|
||||
import no.nordicsemi.android.uart.db.MIGRATION_1_2
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class DbHiltModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideDB(@ApplicationContext context: Context): ConfigurationsDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
ConfigurationsDatabase::class.java, "toolbox_uart.db"
|
||||
).addMigrations(MIGRATION_1_2).build()
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import no.nordicsemi.android.common.core.simpleSharedFlow
|
||||
import no.nordicsemi.android.common.logger.BlekLoggerAndLauncher
|
||||
import no.nordicsemi.android.common.logger.NordicBlekLogger
|
||||
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus
|
||||
@@ -52,6 +51,7 @@ import no.nordicsemi.android.uart.data.UARTRecord
|
||||
import no.nordicsemi.android.uart.data.UARTRecordType
|
||||
import no.nordicsemi.android.uart.data.UARTServiceData
|
||||
import no.nordicsemi.android.uart.data.parseWithNewLineChar
|
||||
import no.nordicsemi.android.ui.view.NordicLoggerFactory
|
||||
import no.nordicsemi.android.ui.view.StringConst
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -62,7 +62,8 @@ class UARTRepository @Inject internal constructor(
|
||||
private val context: Context,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val configurationDataSource: ConfigurationDataSource,
|
||||
private val stringConst: StringConst
|
||||
private val stringConst: StringConst,
|
||||
private val loggerFactory: NordicLoggerFactory
|
||||
) {
|
||||
private var logger: BlekLoggerAndLauncher? = null
|
||||
|
||||
@@ -97,7 +98,7 @@ class UARTRepository @Inject internal constructor(
|
||||
private fun shouldClean() = !isOnScreen && !isServiceRunning
|
||||
|
||||
fun launch(device: ServerDevice) {
|
||||
logger = NordicBlekLogger.create(context, stringConst.APP_NAME, "UART", device.address)
|
||||
logger = loggerFactory.createNordicLogger(context, stringConst.APP_NAME, "UART", device.address)
|
||||
_data.value = _data.value.copy(deviceName = device.name)
|
||||
serviceManager.startService(UARTService::class.java, device)
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
val UART_SERVICE_UUID: UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
private val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
internal val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
internal val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
|
||||
private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
internal val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
internal val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@AndroidEntryPoint
|
||||
|
||||
@@ -77,6 +77,7 @@ import no.nordicsemi.android.uart.view.OnRunMacro
|
||||
import no.nordicsemi.android.uart.view.OpenLogger
|
||||
import no.nordicsemi.android.uart.view.UARTViewEvent
|
||||
import no.nordicsemi.android.uart.view.UARTViewState
|
||||
import no.nordicsemi.android.ui.view.NordicLoggerFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -84,7 +85,8 @@ internal class UARTViewModel @Inject constructor(
|
||||
private val repository: UARTRepository,
|
||||
private val navigationManager: Navigator,
|
||||
private val dataSource: UARTPersistentDataSource,
|
||||
private val analytics: AppAnalytics
|
||||
private val analytics: AppAnalytics,
|
||||
private val loggerFactory: NordicLoggerFactory
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(UARTViewState())
|
||||
@@ -126,7 +128,7 @@ internal class UARTViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun handleResult(result: NavigationResult<ServerDevice>) {
|
||||
internal fun handleResult(result: NavigationResult<ServerDevice>) {
|
||||
when (result) {
|
||||
is NavigationResult.Cancelled -> navigationManager.navigateUp()
|
||||
is NavigationResult.Success -> onDeviceSelected(result.value)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.hilt.testing.TestInstallIn
|
||||
import no.nordicsemi.android.common.logger.BlekLoggerAndLauncher
|
||||
import no.nordicsemi.android.ui.view.NordicLoggerFactory
|
||||
import no.nordicsemi.android.ui.view.NordicLoggerFactoryHiltModule
|
||||
|
||||
@Module
|
||||
@TestInstallIn(
|
||||
components = [SingletonComponent::class],
|
||||
replaces = [NordicLoggerFactoryHiltModule::class]
|
||||
)
|
||||
class NordicLoggerFactoryTestModule {
|
||||
|
||||
@Provides
|
||||
fun createLogger(): NordicLoggerFactory {
|
||||
return object : NordicLoggerFactory {
|
||||
override fun createNordicLogger(
|
||||
context: Context,
|
||||
profile: String?,
|
||||
key: String,
|
||||
name: String?,
|
||||
): BlekLoggerAndLauncher {
|
||||
return object : BlekLoggerAndLauncher {
|
||||
override fun launch() {
|
||||
|
||||
}
|
||||
|
||||
override fun log(priority: Int, log: String) {
|
||||
println(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.hilt.testing.TestInstallIn
|
||||
import no.nordicsemi.android.kotlin.ble.core.MockServerDevice
|
||||
import no.nordicsemi.android.kotlin.ble.core.ServerDevice
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.service.ServiceManagerHiltModule
|
||||
import no.nordicsemi.android.uart.repository.UARTService
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.android.controller.ServiceController
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@TestInstallIn(
|
||||
components = [SingletonComponent::class],
|
||||
replaces = [ServiceManagerHiltModule::class]
|
||||
)
|
||||
class ServiceManagerTestModule {
|
||||
|
||||
private val componentName = ComponentName("org.robolectric", UARTService::class.java.name)
|
||||
|
||||
@Provides
|
||||
internal fun provideDevice(): MockServerDevice {
|
||||
return MockServerDevice(
|
||||
name = "GLS Server",
|
||||
address = "55:44:33:22:11"
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
internal fun provideServiceController(
|
||||
@ApplicationContext context: Context,
|
||||
device: MockServerDevice
|
||||
): ServiceController<UARTService> {
|
||||
return Robolectric.buildService(UARTService::class.java, Intent(context, UARTService::class.java).apply {
|
||||
putExtra(DEVICE_DATA, device)
|
||||
})
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideServiceManager(controller: ServiceController<UARTService>): ServiceManager {
|
||||
return object : ServiceManager {
|
||||
override fun <T> startService(service: Class<T>, device: ServerDevice) {
|
||||
controller.create().startCommand(3, 4).get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.hilt.testing.TestInstallIn
|
||||
import no.nordicsemi.android.uart.DbHiltModule
|
||||
import no.nordicsemi.android.uart.db.ConfigurationsDatabase
|
||||
import no.nordicsemi.android.uart.db.MIGRATION_1_2
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@TestInstallIn(
|
||||
components = [SingletonComponent::class],
|
||||
replaces = [DbHiltModule::class]
|
||||
)
|
||||
class TestDbHiltModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun provideDB(@ApplicationContext context: Context): ConfigurationsDatabase {
|
||||
return Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
ConfigurationsDatabase::class.java
|
||||
).addMigrations(MIGRATION_1_2).build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import dagger.Module
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.hilt.testing.TestInstallIn
|
||||
|
||||
//@Module
|
||||
//@TestInstallIn(
|
||||
// components = [SingletonComponent::class],
|
||||
// replaces = [AnalyticsModule::class]
|
||||
//)
|
||||
//class TestHiltModule {
|
||||
//}
|
||||
@@ -0,0 +1,208 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.rule.ServiceTestRule
|
||||
import dagger.hilt.android.testing.BindValue
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.impl.annotations.RelaxedMockK
|
||||
import io.mockk.junit4.MockKRule
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.spyk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import no.nordicsemi.android.analytics.AppAnalytics
|
||||
import no.nordicsemi.android.common.logger.NordicBlekLogger
|
||||
import no.nordicsemi.android.common.navigation.NavigationResult
|
||||
import no.nordicsemi.android.common.navigation.Navigator
|
||||
import no.nordicsemi.android.common.navigation.di.NavigationModule
|
||||
import no.nordicsemi.android.kotlin.ble.client.main.ClientScope
|
||||
import no.nordicsemi.android.kotlin.ble.core.MockServerDevice
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.BleGattConnectionStatus
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionState
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.GattConnectionStateWithStatus
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.ServerScope
|
||||
import no.nordicsemi.android.uart.data.UARTPersistentDataSource
|
||||
import no.nordicsemi.android.uart.repository.UARTRepository
|
||||
import no.nordicsemi.android.uart.view.DisconnectEvent
|
||||
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
||||
import no.nordicsemi.android.ui.view.NordicLoggerFactory
|
||||
import no.nordicsemi.android.ui.view.StringConst
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@HiltAndroidTest
|
||||
@Config(application = HiltTestApplication::class)
|
||||
@UninstallModules(NavigationModule::class)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
internal class UARTViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mockkRule = MockKRule(this)
|
||||
|
||||
@get:Rule
|
||||
val serviceRule = ServiceTestRule()
|
||||
|
||||
@get:Rule
|
||||
var hiltRule = HiltAndroidRule(this)
|
||||
|
||||
@BindValue
|
||||
@JvmField
|
||||
val analyticsService: Navigator = mockk(relaxed = true)
|
||||
|
||||
@RelaxedMockK
|
||||
lateinit var analytics: AppAnalytics
|
||||
|
||||
@MockK
|
||||
lateinit var stringConst: StringConst
|
||||
|
||||
@RelaxedMockK
|
||||
lateinit var context: Context
|
||||
|
||||
@RelaxedMockK
|
||||
lateinit var logger: NordicBlekLogger
|
||||
|
||||
@Inject
|
||||
lateinit var repository: UARTRepository
|
||||
|
||||
@Inject
|
||||
lateinit var dataSource: UARTPersistentDataSource
|
||||
|
||||
lateinit var viewModel: UARTViewModel
|
||||
|
||||
lateinit var uartServer: UartServer
|
||||
|
||||
@Inject
|
||||
lateinit var device: MockServerDevice
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
hiltRule.inject()
|
||||
Dispatchers.setMain(UnconfinedTestDispatcher())
|
||||
}
|
||||
|
||||
@After
|
||||
fun release() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
viewModel = UARTViewModel(repository, mockk(relaxed = true), dataSource, mockk(relaxed = true), object :
|
||||
NordicLoggerFactory {
|
||||
override fun createNordicLogger(
|
||||
context: Context,
|
||||
profile: String?,
|
||||
key: String,
|
||||
name: String?,
|
||||
): NordicBlekLogger {
|
||||
return logger
|
||||
}
|
||||
|
||||
})
|
||||
runBlocking {
|
||||
mockkStatic("no.nordicsemi.android.kotlin.ble.client.main.ClientScopeKt")
|
||||
every { ClientScope } returns CoroutineScope(UnconfinedTestDispatcher())
|
||||
mockkStatic("no.nordicsemi.android.kotlin.ble.server.main.ServerScopeKt")
|
||||
every { ServerScope } returns CoroutineScope(UnconfinedTestDispatcher())
|
||||
every { stringConst.APP_NAME } returns "Test"
|
||||
|
||||
uartServer = UartServer(CoroutineScope(UnconfinedTestDispatcher()))
|
||||
uartServer.start(spyk(), device)
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun prepareLogger() {
|
||||
mockkObject(NordicBlekLogger.Companion)
|
||||
every { NordicBlekLogger.create(any(), any(), any(), any()) } returns mockk()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when connected should return state connected`() = runTest {
|
||||
val connectedState = GattConnectionStateWithStatus(
|
||||
GattConnectionState.STATE_CONNECTED,
|
||||
BleGattConnectionStatus.SUCCESS
|
||||
)
|
||||
viewModel.handleResult(NavigationResult.Success(device))
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(connectedState, viewModel.state.value.uartManagerState.connectionState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when disconnected should return state connected`() = runTest {
|
||||
val disconnectedState = GattConnectionStateWithStatus(
|
||||
GattConnectionState.STATE_DISCONNECTED,
|
||||
BleGattConnectionStatus.SUCCESS
|
||||
)
|
||||
viewModel.handleResult(NavigationResult.Success(device))
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(disconnectedState, viewModel.state.value.uartManagerState.connectionState)
|
||||
}
|
||||
//
|
||||
// @Test
|
||||
// fun `when request last record then change status and get 1 record`() = runTest {
|
||||
// viewModel.handleResult(NavigationResult.Success(device))
|
||||
// advanceUntilIdle() //Needed because of delay() in waitForBonding()
|
||||
// assertEquals(RequestStatus.IDLE, viewModel.state.value.glsServiceData.requestStatus)
|
||||
//
|
||||
// viewModel.onEvent(OnWorkingModeSelected(WorkingMode.LAST))
|
||||
// assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus)
|
||||
//
|
||||
// glsServer.continueWithResponse() //continue server breakpoint
|
||||
//
|
||||
// assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus)
|
||||
// assertEquals(1, viewModel.state.value.glsServiceData.records.size)
|
||||
//
|
||||
// val parsedResponse = GlucoseMeasurementParser.parse(glsServer.OLDEST_RECORD)
|
||||
// assertEquals(parsedResponse, viewModel.state.value.glsServiceData.records.keys.first())
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun `when request all record then change status and get 5 records`() = runTest {
|
||||
// viewModel.handleResult(NavigationResult.Success(device))
|
||||
// advanceUntilIdle() //Needed because of delay() in waitForBonding()
|
||||
// assertEquals(RequestStatus.IDLE, viewModel.state.value.glsServiceData.requestStatus)
|
||||
//
|
||||
// viewModel.onEvent(OnWorkingModeSelected(WorkingMode.ALL))
|
||||
// assertEquals(RequestStatus.PENDING, viewModel.state.value.glsServiceData.requestStatus)
|
||||
//
|
||||
// glsServer.continueWithResponse() //continue server breakpoint
|
||||
// advanceUntilIdle() //We have to use because of delay() in sendAll()
|
||||
//
|
||||
// assertEquals(RequestStatus.SUCCESS, viewModel.state.value.glsServiceData.requestStatus)
|
||||
// assertEquals(5, viewModel.state.value.glsServiceData.records.size)
|
||||
//
|
||||
// val expectedRecords = glsServer.records.map { GlucoseMeasurementParser.parse(it) }
|
||||
// assertContentEquals(expectedRecords, viewModel.state.value.glsServiceData.records.keys)
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user