Compare commits

..

11 Commits

Author SHA1 Message Date
Mariya
95b3a072b4 fixed usb permission error for v1 device 2024-03-01 16:32:43 +05:30
Pritimay Sarkar
be21430d0a Merge remote-tracking branch 'origin/nats_certificate_download' into dev 2024-02-11 14:08:00 +05:30
Pritimay Sarkar
3609d728c1 release 2.1.110 2024-02-10 12:18:52 +05:30
Pritimay Sarkar
7302ad669f release 2.1.108 2024-02-10 11:10:06 +05:30
Pritimay Sarkar
ef98b09eaf release 2.1.107 2024-02-10 10:46:54 +05:30
Pritimay Sarkar
f631a273d7 TLS impleamentation on NATS server 2024-02-09 13:50:33 +05:30
Pritimay Sarkar
de74da3135 add package name dynamically in provider 2024-02-09 13:49:16 +05:30
Mariya
466d0e2afc code added related to nats 2024-02-07 16:33:57 +05:30
Mariya
45fd4861f4 nats code 2024-02-07 12:49:31 +05:30
Mariya
1a3bb67dce nats 2024-02-06 16:23:33 +05:30
Mariya
3dd247b15d Nats Certificate Download API is completed 2024-02-06 13:25:06 +05:30
20 changed files with 329 additions and 300 deletions

View File

@@ -16,11 +16,11 @@ android {
// dev - development, quality - qc, uat - User Acceptance Test, preprod - preproduction, prod - production
defaultConfig {
applicationId "in.sminnovations.hpostesting.dev"
applicationId "in.sminnovations.hpostesting.quality"
minSdk 21
targetSdk 34
versionCode 111
versionName "2.1.111"
versionCode 106
versionName "2.1.106"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -157,7 +157,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.9.3")
implementation "androidx.preference:preference-ktx:1.2.1"
implementation 'io.nats:jnats:2.11.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
implementation("androidx.work:work-runtime-ktx:2.9.0")
@@ -165,4 +165,6 @@ dependencies {
implementation 'com.google.android.play:core:1.10.3'
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation 'io.nats:jnats:2.11.4'
}

View File

@@ -1,205 +1,45 @@
{
"project_info": {
"project_number": "650071678820",
"project_id": "hpos-af3cc",
"storage_bucket": "hpos-af3cc.appspot.com"
"project_number": "1004619739289",
"project_id": "hpos-qa",
"storage_bucket": "hpos-qa.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:650071678820:android:f1435a1c07f710036c6471",
"mobilesdk_app_id": "1:1004619739289:android:f669b47552748433e5c808",
"android_client_info": {
"package_name": "com.example.hposconsentform"
"package_name": "in.sminnovations.hposregistration.quality"
}
},
"oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
],
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"
"current_key": "AIzaSyC4d4GhWHr_NMJAeBvnztQ_yQ3Qe9MAnLs"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
]
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:650071678820:android:7865bef608cdee6f6c6471",
"mobilesdk_app_id": "1:1004619739289:android:8397cd1f0357bd89e5c808",
"android_client_info": {
"package_name": "com.smi.counselling"
"package_name": "in.sminnovations.hpostesting.quality"
}
},
"oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
],
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"
"current_key": "AIzaSyC4d4GhWHr_NMJAeBvnztQ_yQ3Qe9MAnLs"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:650071678820:android:2925e9ce3417d2386c6471",
"android_client_info": {
"package_name": "in.sminnovations.hemocube"
}
},
"oauth_client": [
{
"client_id": "650071678820-srhm9spm9hjn4frcd3r6o02gdhdbtd15.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "in.sminnovations.hemocube",
"certificate_hash": "7714b9268a81d0cf0fb178b0af8dbb630f8fc70a"
}
},
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:650071678820:android:7569c1cad4fc99916c6471",
"android_client_info": {
"package_name": "in.sminnovations.hposregistration"
}
},
"oauth_client": [
{
"client_id": "650071678820-70kp5jvjda4r5diqch2kn4lc40p4f42g.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "in.sminnovations.hposregistration",
"certificate_hash": "7714b9268a81d0cf0fb178b0af8dbb630f8fc70a"
}
},
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:650071678820:android:f96be19e5d43102b6c6471",
"android_client_info": {
"package_name": "in.sminnovations.hpostesting"
}
},
"oauth_client": [
{
"client_id": "650071678820-l87dnr0bdj95get0khgnvfv2an1k6ogq.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "in.sminnovations.hpostesting",
"certificate_hash": "7714b9268a81d0cf0fb178b0af8dbb630f8fc70a"
}
},
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:650071678820:android:a53292637abb7c0d6c6471",
"android_client_info": {
"package_name": "in.sminnovations.hpostesting.dev"
}
},
"oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAVdNGCev_AFX0qmuZJF6kxzRzXUTeAW5I"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "650071678820-to41afi9f0gi5r3k6v7pe1e5p96nupcs.apps.googleusercontent.com",
"client_type": 3
}
]
"other_platform_oauth_client": []
}
}
}

Binary file not shown.

View File

@@ -4,15 +4,15 @@
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.example.hpos",
"applicationId": "in.sminnovations.hpostesting.quality",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"versionCode": 101,
"versionName": "2.1.101",
"outputFile": "app-release.apk"
}
],

View File

@@ -119,8 +119,13 @@
android:noHistory="true"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
</activity>
<activity
@@ -160,7 +165,7 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.hpostesting.qa.fileprovider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

View File

@@ -7,8 +7,11 @@ import com.example.hpostesting.data.model.updates.CheckUpdateRequest
import com.example.hpostesting.data.model.updates.CheckUpdateResponse
import com.example.hpostesting.data.model.updates.DeviceUpdateRequest
import okhttp3.MultipartBody
import okhttp3.Response
import okhttp3.ResponseBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
@@ -31,6 +34,10 @@ interface MolbioResultApi {
@Body deviceUpdateRequest: DeviceUpdateRequest
): ResponseBody
@GET("deviceService/device/getClientCertificate")
suspend fun downloadClientCertificate(
): ResponseBody
@Multipart
@POST("deviceService/device/uploadLogs")
suspend fun uploadLogs(

View File

@@ -4,7 +4,7 @@ import com.example.hpostesting.data.model.patient.HemoCubeTestData
data class MolbioV2Result(
val age: Int? = 31,
val analysisDate: String? = "2024-02-08 16:33:56",
val analysisDate: String? = "",
val analysisId: String? = "",
val analysisStatus: String? = "",
val analysisType: String? = "HPOS",
@@ -12,11 +12,11 @@ data class MolbioV2Result(
val bloodGroup: String? = "",
val coefficients: List<Int>? = listOf(22, 22),
val collectionLocation: List<Any>? = listOf(),
val collectionTime: String? = "2024-02-08 16:33:56",
val collectionTime: String? = "",
val collector: String? = "",
val curveFitting: String? = "Linear",
val deviceName: String? = "HPOS",
val expiryTime: String? = "2024-02-08 16:33:56",
val expiryTime: String? = "",
val gender: String? = "",
val interpretation: String? = "",
val `operator`: String? = "",
@@ -30,7 +30,7 @@ data class MolbioV2Result(
val testId: String? = "",
val testResult: String? = "",
val testStatus: String? = "",
val testTime: String? = "2024-02-08 16:33:56",
val testTime: String? = "",
val testType: String? = "",
val thresholds: String? = "",
val underMedication: Boolean? = false,

View File

@@ -77,6 +77,9 @@ class DatabaseRepository @Inject constructor(
return safeApiCall { molbioResultApi.deviceUpdate(deviceUpdateRequest) }
}
override suspend fun downloadClientCertificate(): Result<ResponseBody> {
return safeApiCall { molbioResultApi.downloadClientCertificate() }
}
override suspend fun uploadLogs(logFile: MultipartBody.Part): Result<UploadLogsResponse> {
return safeApiCall { molbioResultApi.uploadLogs(logFile) }
}

View File

@@ -45,5 +45,6 @@ interface Repository {
suspend fun deviceUpdate(deviceUpdateRequest: DeviceUpdateRequest): Result<ResponseBody>
suspend fun downloadClientCertificate(): Result<ResponseBody>
suspend fun uploadLogs(logFile: MultipartBody.Part): Result<UploadLogsResponse>
}

View File

@@ -149,15 +149,6 @@ class MainActivity : AppCompatActivity() {
Constants.DEVICE_TYPE_HEMOCUBE
}
device.productId == 4614 && device.vendorId == 7111 -> {
binding.cvItem1.visibility = View.VISIBLE
binding.cvItem3.visibility = View.VISIBLE
binding.cvItem2.visibility = View.GONE
binding.cvItem4.visibility = View.GONE
DataHolder.deviceType.postValue(Constants.DEVICE_TYPE_HEMOCUBE)
Constants.DEVICE_TYPE_HEMOCUBE
}
device.productId == Constants.DEVICE_PRODUCT_ID && device.vendorId == Constants.DEVICE_VENDOR_ID -> {
Log.d(
TAG,

View File

@@ -1,6 +1,9 @@
package com.example.hpostesting.presentation
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.example.hpostesting.presentation.assurance.AssuranceControlsActivity
import com.example.hpostesting.presentation.dashboard.DashboardActivity
import io.nats.client.AuthHandler
import io.nats.client.Connection
@@ -8,9 +11,22 @@ import io.nats.client.Message
import io.nats.client.NKey
import io.nats.client.Nats
import io.nats.client.Options
import io.nats.client.support.SSLUtils
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.nio.file.Paths
import java.security.GeneralSecurityException
import java.security.KeyStore
import java.security.SecureRandom
import java.security.cert.CertificateFactory
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
class NatsManager(datacollector: DashboardActivity) {
@@ -20,17 +36,57 @@ class NatsManager(datacollector: DashboardActivity) {
val datacollector = datacollector
var connect = false
private fun createSSLContext(): SSLContext {
val keyStorePassword = "prime24".toCharArray() // Change as necessary
val clientCertPath = "/storage/sdcard0/Download/client.p12"
// Load client certificate and key
val keyStore = KeyStore.getInstance("PKCS12")
FileInputStream(clientCertPath).use { keyStoreInputStream ->
keyStore.load(keyStoreInputStream, keyStorePassword)
}
val caCertPath = "/storage/sdcard0/Android/data/in.sminnovations.hpostesting.quality/files/NATS/clientCertificate/client-cert.pem"
val caCert = FileInputStream(caCertPath).use { inputStream ->
val certificateFactory = CertificateFactory.getInstance("X.509")
certificateFactory.generateCertificate(inputStream)
}
val trustStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null) // Initialize the keystore
setCertificateEntry("caCert", caCert) // Add the CA certificate
}
// Initialize key manager factory
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(keyStore, keyStorePassword)
// Initialize trust manager factory
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(trustStore)
// Initialize SSLContext
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(kmf.keyManagers, tmf.trustManagers, SecureRandom())
return sslContext
}
@RequiresApi(Build.VERSION_CODES.O)
fun connect() {
Log.d(TAG, "TRY TO CONNECT")
Thread {
val seedString = "SUAEB5PUWNS6C2HUV3MFI6HUDEAPGPFPBHMI73NHIQATCDD2BWALVEZXZ4"
val seedString = "SUAJH5VMO6GDRQA6QTXCLIJMS74IWIUTU3NJVYIOTZF2LBWUPD77DA2ZEA"
Log.e("seedString",seedString)
val seedBytes = seedString.toCharArray()
val theNKey = NKey.fromSeed(seedBytes) // really should load from somewhere
val options = Options.Builder()
.server("nats://192.168.10.117:4222")
.server("nats://nanodgx.in:4222")
.sslContext(SSLUtils.createOpenTLSContext())
.authHandler(object : AuthHandler {
override fun getID(): CharArray? {
return try {
@@ -68,6 +124,11 @@ class NatsManager(datacollector: DashboardActivity) {
connect = true
datacollector.setConnect(true)
if (nc?.status == Connection.Status.CONNECTED) {
Log.e("NATSCONNECTION", "NATS is successfully connected.")
} else {
Log.e("NATSCONNECTION", "NATS is not connected. Current status: ${nc?.status}")
}
nc?.publish(
"server.hpos.HCV-000-3001.ping",
@@ -82,9 +143,10 @@ class NatsManager(datacollector: DashboardActivity) {
println("PRITIMOI SARKAR $msg")
}
d?.subscribe("device.hpos.HCV-000-3001.update") { msg ->
val response = String(msg.data, StandardCharsets.UTF_8)
datacollector. setResponse(response)
datacollector.setResponse(response)
println("Message received (up to 100 times): $response")
}
@@ -115,7 +177,7 @@ class NatsManager(datacollector: DashboardActivity) {
} catch (exp: Exception) {
println(exp.printStackTrace())
connect = false
datacollector.setConnect(true)
datacollector.setConnect(false)
}
}.start()

View File

@@ -3,14 +3,19 @@ package com.example.hpostesting.presentation.assurance
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.example.hpostesting.presentation.NatsManager
import com.example.hpostesting.presentation.dashboard.IDataCollector
import dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.hpostesting.databinding.ActivityAssuranceControlsBinding
@AndroidEntryPoint
class AssuranceControlsActivity: AppCompatActivity() {
class AssuranceControlsActivity: AppCompatActivity(), IDataCollector {
lateinit var binding: ActivityAssuranceControlsBinding
lateinit var sharedPreference: SharedPreferences
lateinit var nats: NatsManager
var responses: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -24,5 +29,21 @@ class AssuranceControlsActivity: AppCompatActivity() {
.replace(binding.fgAssuranceControls.id, AssuranceControlsFragment())
.commit()
}
// nats = NatsManager(this)
// nats.connect()
// nats.pub("server.hpos.HCV-000-3001.ping", "THIS IS A TEST MSG")
}
override fun setConnect(connect: Boolean) {
if(connect){
Log.i("NATS Connection", connect.toString())
}
}
override fun setResponse(response: String) {
responses = responses+response+"\n"
println(responses)
}
}

View File

@@ -156,9 +156,10 @@ class DashboardActivity : AppCompatActivity(), IDataCollector {
val file = File(getExternalFilesDir("Updates"), "update.apk")
file.setReadable(true, false) // Ensure the file is readable
val pInfo = baseContext.packageManager.getPackageInfo(baseContext.packageName, 0)
val uri: Uri = FileProvider.getUriForFile(
this,
"com.example.hpostesting.qa.fileprovider",
"${pInfo}.fileprovider",
file
)

View File

@@ -43,11 +43,19 @@ import com.google.firebase.perf.ktx.performance
import dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.hpostesting.R
import `in`.sminnovations.hpostesting.databinding.FragmentHomeBinding
import okhttp3.ResponseBody
import org.json.JSONObject
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URL
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@AndroidEntryPoint
class HomeFragment : Fragment() {
@@ -62,7 +70,6 @@ class HomeFragment : Fragment() {
private val homeViewModel: HemoCubeViewModel by activityViewModels()
private var isTokenAvailable = false
private lateinit var sharedPreference: SharedPreferences
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?,
@@ -119,40 +126,6 @@ class HomeFragment : Fragment() {
setSearch()
checkForLocalDBData()
checkForTokenAndUpdate()
hemoCubeViewModel.allUserData.observe(viewLifecycleOwner) { userDataList ->
val resultList = MolbioV2ResultRequest(mutableListOf(MolbioV2Result()))
userDataList.forEach { userData ->
if (!userData.molbioFlag && isTokenAvailable) {
resultList.results?.add(
MolbioV2Result(
rawData = userData,
analysisId = userData._id,
analysisDate = "2024-02-08 16:33:56",
analysisStatus = userData.classificationResult,
thresholds = Constants.BUFFER_INTENSITY_THRESHOLDS[userData.deviceId].toString(),
interpretation = userData.classificationResult,
testId = userData._id,
testTime = userData.testTime,
collectionTime = "2024-02-08 16:33:56",//userData.testTime,
expiryTime = "2024-02-08 16:33:56"//userData.testTime,
)
)
}
if (!userData.molbioFlag && isTokenAvailable) {
userData.molbioFlag = true
hemoCubeViewModel.uploadResult(resultList)
}
if (!userData.localFlag) {
userData.localFlag = true
hemoCubeViewModel.bulkAddResultTestToDb(userData)
}
}
}
} else {
binding.internetAvailableCL.visibility = View.GONE
binding.internetNotAvailableCL.visibility = View.VISIBLE
@@ -183,9 +156,9 @@ class HomeFragment : Fragment() {
val btnSaveLocalVisibility =
if (userData.any { it.testStatus == true }) View.VISIBLE else View.GONE
binding.downloadCSV.visibility = btnSaveLocalVisibility
binding.btnSaveLocal.visibility = btnSaveLocalVisibility
binding.downloadCSV.setOnClickListener {
binding.btnSaveLocal.setOnClickListener {
if (btnSaveLocalVisibility == View.VISIBLE) {
// Execute the action when the button is visible (testStatus is true for at least one user)
showDownloadDialog(requireContext())
@@ -199,8 +172,6 @@ class HomeFragment : Fragment() {
}
}
}
binding.btnNewKit.setOnClickListener {
with(sharedPreference.edit()) {
putString(Constants.KIT_NUMBER, "")
@@ -228,20 +199,18 @@ class HomeFragment : Fragment() {
val accessToken = sharedPreference.getString(Constants.ACCESS_TOKEN, "").toString()
val password = sharedPreference.getString(Constants.DEVICE_PASSWORD_API, "").toString()
val userID = sharedPreference.getString(Constants.DEVICE_ID_API, "").toString()
if (userID.isNotEmpty() && password.isNotEmpty()) {
if (!isTokenAvailable) {
if (accessToken.isEmpty()) {
hemoCubeViewModel.login(createLoginRequestData(userID, password))
} else {
if (isTokenExpired(accessToken)) {
hemoCubeViewModel.login(createLoginRequestData(userID, password))
} else {
isTokenAvailable = true
hemoCubeViewModel.deviceUpdate(createDeviceUpdateRequestData())
hemoCubeViewModel.uploadLogs()
hemoCubeViewModel.startPeriodicCheckUpdate()
hemoCubeViewModel.downloadClientCertificate()
}
}
} else {
@@ -252,6 +221,10 @@ class HomeFragment : Fragment() {
).show()
}
hemoCubeViewModel.loginResponse.observe(viewLifecycleOwner) { response ->
when (response) {
is Result.Success -> {
@@ -306,6 +279,41 @@ class HomeFragment : Fragment() {
}
}
hemoCubeViewModel.downloadcertificate.observe(viewLifecycleOwner) { response ->
when (response) {
is Result.Success -> {
val url = response.data
val fileName = "nats_certificate.zip"
val downloadDirectory = "NATS"
val file = downloadFile(url, requireContext(), fileName, downloadDirectory)
Toast.makeText(requireContext(), "NATS certificate Downloaded", Toast.LENGTH_SHORT).show()
val unzipDirectoryPath = requireContext().getExternalFilesDir(null)?.absolutePath + "/$downloadDirectory"
unzip(file.absolutePath, unzipDirectoryPath)
Toast.makeText(requireContext(), "NATS certificate Extracted", Toast.LENGTH_SHORT).show()
}
is Result.Error -> {
response.exception.let { message ->
Toast.makeText(
activity,
"An error occurred in nats download: $message",
Toast.LENGTH_LONG
)
.show()
}
}
is Result.Loading -> {
}
else -> {}
}
}
hemoCubeViewModel.resultUpload.observe(viewLifecycleOwner) {
when (it) {
is Result.Success -> {
@@ -330,8 +338,11 @@ class HomeFragment : Fragment() {
else -> {}
}
}
}
private fun isTokenExpired(token: String): Boolean {
val parts = token.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val decodedPayload = String(Base64.decode(parts[1], Base64.DEFAULT))
@@ -381,6 +392,60 @@ class HomeFragment : Fragment() {
)
}
private fun downloadFile(responseBody: ResponseBody, context: Context, fileName: String, downloadDirectory: String): File {
// Ensure the download directory exists
val fileDir = File(context.getExternalFilesDir(null), downloadDirectory)
if (!fileDir.exists()) {
fileDir.mkdirs()
}
val file = File(fileDir, fileName)
Log.d("Download", "Starting download to $file")
responseBody.byteStream().use { inputStream ->
FileOutputStream(file).use { outputStream ->
inputStream.copyTo(outputStream)
}
}
// After download
Log.d("Download", "Download completed to ${file.absolutePath}")
return file
}
private fun unzip(zipFilePath: String, destDirectory: String) {
val destDir = File(destDirectory)
if (!destDir.exists()) {
destDir.mkdir()
}
ZipInputStream(FileInputStream(zipFilePath)).use { zipIn ->
var entry: ZipEntry? = zipIn.nextEntry
while (entry != null) {
val filePath = destDirectory + File.separator + entry.name
if (!entry.isDirectory) {
extractFile(zipIn, filePath)
} else {
val dir = File(filePath)
dir.mkdir()
}
zipIn.closeEntry()
entry = zipIn.nextEntry
}
}
}
private fun extractFile(zipIn: ZipInputStream, filePath: String) {
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
val bytesIn = ByteArray(4096)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
}
}
private fun setUserId() {
binding.btnSubmit.setOnClickListener {
val userId = binding.userId.text.toString()
@@ -633,33 +698,30 @@ class HomeFragment : Fragment() {
hemoCubeViewModel.allUserData.observe(viewLifecycleOwner) { userDataList ->
val resultList = MolbioV2ResultRequest(mutableListOf(MolbioV2Result()))
userDataList.forEach { userData ->
if (!userData.localFlag) {
userData.localFlag = true
hemoCubeViewModel.bulkAddResultTestToDb(userData)
if (!userData.molbioFlag && isTokenAvailable) {
resultList.results?.add(
MolbioV2Result(
rawData = userData,
analysisId = userData._id,
analysisDate = "2024-02-08 16:33:56", //userData.reportUploadTime,
analysisDate = userData.testTime,
analysisStatus = userData.classificationResult,
thresholds = Constants.BUFFER_INTENSITY_THRESHOLDS[userData.deviceId].toString(),
interpretation = userData.classificationResult,
testId = userData._id,
testTime = userData.testTime,
collectionTime = "2024-02-08 16:33:56",//userData.testTime,
expiryTime = "2024-02-08 16:33:56",//userData.testTime,
collectionTime = userData.testTime,
expiryTime = userData.testTime,
)
)
}
if(!userData.localFlag){
userData.localFlag = true
hemoCubeViewModel.bulkAddResultTestToDb(userData)
}
if (!userData.molbioFlag && isTokenAvailable) {
userData.molbioFlag = true
hemoCubeViewModel.uploadResult(resultList)
}
}
}
if (isTokenAvailable) {
hemoCubeViewModel.uploadResult(resultList)
}
dialog.dismiss()
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -74,6 +75,11 @@ class DeviceProvisionFragment : Fragment() {
Constants.NATS_TOKEN,
response.data.data?.device?.deviceUser?.natsToken
)
response.data.data?.device?.deviceUser?.natsToken?.let {
Log.e("natstoken",
it
)
}
putString(
Constants.NATS_TOKEN_EXPIRE_DATE,
response.data.data?.device?.deviceUser?.natsTokenExpiry

View File

@@ -643,10 +643,10 @@ class HemoCubeFragment : Fragment() {
if (!Constants.DEVICE_CONFIGURATION.containsKey(deviceHardwareId)) {
assignDefaultDevice(resultData)
testState.allErrorMessages += "Calibration configuration for this device id is not found\n"
// testState.allErrorMessages += "Calibration configuration for this device id is not found\n"
}
if (!Constants.BUFFER_INTENSITY_THRESHOLDS.containsKey(deviceHardwareId)) {
testState.allErrorMessages += "ADC thresholds for this device id are not found\n"
// testState.allErrorMessages += "ADC thresholds for this device id are not found\n"
}
}
@@ -743,9 +743,9 @@ class HemoCubeFragment : Fragment() {
)?.get(0)!!
) {
// validationError = true
testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (low)"
// testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (low)"
activity?.runOnUiThread {
binding.errorMessage.text = getString(R.string.error_improper_buffer_low)
// binding.errorMessage.text = getString(R.string.error_improper_buffer_low)
// binding.errorMessage.visibility = View.VISIBLE
}
}
@@ -763,11 +763,11 @@ class HemoCubeFragment : Fragment() {
)?.get(1)!!
) {
// validationError = true
testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (high)" + "\n"
// testState.allErrorMessages += "Error: Invalid Test. Improper buffer reading (high)" + "\n"
activity?.runOnUiThread {
binding.errorMessage.text =
// binding.errorMessage.text =
getString(R.string.error_improper_buffer_high)
binding.errorMessage.visibility = View.VISIBLE
// binding.errorMessage.visibility = View.VISIBLE
}
}
@@ -811,9 +811,9 @@ class HemoCubeFragment : Fragment() {
if (fittedAbs1 <= fittedAbs2) {
// validationError = true
testState.allErrorMessages += "Error: Invalid Test. Problem with de-oxygenation" + "\n"
// testState.allErrorMessages += "Error: Invalid Test. Problem with de-oxygenation" + "\n"
activity?.runOnUiThread {
binding.errorMessage.text = getString(R.string.error_invalid_test)
// binding.errorMessage.text = getString(R.string.error_invalid_test)
// binding.errorMessage.visibility = View.VISIBLE
}
}
@@ -837,9 +837,9 @@ class HemoCubeFragment : Fragment() {
if (fittedAbs3 < 0.1) {
// validationError = true
testState.allErrorMessages += "Error: Low Hb. Repeat test" + "\n"
// testState.allErrorMessages += "Error: Low Hb. Repeat test" + "\n"
activity?.runOnUiThread {
binding.errorMessage.text = "Error: Low Hb. Repeat test"
// binding.errorMessage.text = "Error: Low Hb. Repeat test"
// binding.errorMessage.visibility = View.VISIBLE
}
}
@@ -874,8 +874,8 @@ class HemoCubeFragment : Fragment() {
this.prdClassification = absorbanceBasedClassification(predictedDenovixRatio)
this.deviceRatioClass = deviceRatioClassification(deviceRatio)
this.slopeRatioClass = slopeClass
this.classificationResult = findResultWithAdditionalMethods(deviceRatio, deviceRatioClass, slopeRatio)
hemoCubeViewModel.messages.postValue("${this.classificationResult} \n Device Ratio: ${"%.3f".format(this.deviceRatio)} \n Slope Ratio: ${"%.3f".format(this.slopeRatio)}")
this.classificationResult = deviceRatioClass
hemoCubeViewModel.messages.postValue("${this.classificationResult} \n Device Ratio: ${"%.3f".format(this.deviceRatio)}")
if (DataHolder.hemoCubeTestData?.testType == "HB")
hemoCubeViewModel.messages.postValue("Hb: $calculatedHb4")
this.errorMessages = testState.allErrorMessages
@@ -928,17 +928,17 @@ class HemoCubeFragment : Fragment() {
fun deviceRatioClassification(ratio: Double?): String {
try {
if (ratio != null) {
if (ratio in 0.001..0.23) {
if (ratio in 0.016..0.22) {
// setSubtitleTextColor(R.color.green_2)
return "Normal"
}
if (ratio in 0.23..0.24)
return "Negative Borderline, Repeat Test"
if (ratio in 0.24..0.29)
if (ratio in 0.22..0.24)
return "Negative Borderline"
if (ratio in 0.24..0.32)
return "Sickle Cell Trait"
if (ratio in 0.29..0.32)
if (ratio in 0.32..0.37)
return "Positive for Sickle Cell. HPLC for Confirmation"
if (ratio in 0.32..Double.POSITIVE_INFINITY)
if (ratio in 0.37..0.56)
return "Sickle Cell Disease"
} else {
return "Invalid"

View File

@@ -79,6 +79,7 @@ class HemoCubeViewModel @Inject constructor(
val checkUpdate = MutableLiveData<Result<CheckUpdateResponse>>()
val deviceUpdate = MutableLiveData<Result<ResponseBody>>()
val downloadcertificate = MutableLiveData<Result<ResponseBody>>()
val uploadLogs = MutableLiveData<Result<UploadLogsResponse>?>()
@@ -141,6 +142,9 @@ class HemoCubeViewModel @Inject constructor(
}
}
fun checkUpdate(checkUpdateRequest: CheckUpdateRequest) = viewModelScope.launch {
checkUpdate.postValue(Result.Loading())
repository.checkUpdate(checkUpdateRequest).let {
@@ -155,6 +159,17 @@ class HemoCubeViewModel @Inject constructor(
}
}
fun downloadClientCertificate() = viewModelScope.launch {
downloadcertificate.postValue(Result.Loading())
repository.downloadClientCertificate().let {
downloadcertificate.postValue(it)
}
}
fun startPeriodicCheckUpdate() {
val periodicRequest = PeriodicWorkRequestBuilder<CheckUpdateWorker>(
repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.MINUTES

View File

@@ -11,12 +11,14 @@ import android.content.ServiceConnection
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbDeviceConnection
import android.hardware.usb.UsbManager
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.Menu
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.get
@@ -43,6 +45,7 @@ open class HemocubeActivity : AppCompatActivity() {
private val TAG = "HemoCube"
private val broadcastReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context, intent: Intent) {
synchronized(this) {
@@ -81,6 +84,7 @@ open class HemocubeActivity : AppCompatActivity() {
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHemocubeBinding.inflate(layoutInflater)
@@ -105,6 +109,7 @@ open class HemocubeActivity : AppCompatActivity() {
}
}
@RequiresApi(Build.VERSION_CODES.O)
open fun connectUsb(permissionGranted: Boolean) {
Log.d(TAG, "connectUsb() called, permission variable = $permissionGranted")
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
@@ -125,32 +130,40 @@ open class HemocubeActivity : AppCompatActivity() {
}
@SuppressLint("MutableImplicitPendingIntent")
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("UnspecifiedImmutableFlag")
private fun requestUserPermission(manager: UsbManager, device: UsbDevice) {
val mPendingIntent: PendingIntent
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
mPendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(Constants.HEMOCUBE_USB_PERMISSION), PendingIntent.FLAG_MUTABLE
)
} else {
mPendingIntent = PendingIntent.getBroadcast(
this,
0,
Intent(Constants.HEMOCUBE_USB_PERMISSION),
val pendingIntentFlags = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
PendingIntent.FLAG_IMMUTABLE
}
else -> {
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
}
}
mPendingIntent = PendingIntent.getBroadcast(
this,
0,
Intent(Constants.HEMOCUBE_USB_PERMISSION),
pendingIntentFlags
)
val filter = IntentFilter(Constants.HEMOCUBE_USB_PERMISSION)
registerReceiver(broadcastReceiver, filter)
registerReceiver(broadcastReceiver, filter, RECEIVER_EXPORTED)
// Request permission
manager.requestPermission(device, mPendingIntent)
}
fun setupService() {
val intent = Intent(this, UsbService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
@RequiresApi(Build.VERSION_CODES.O)
open fun reconnectDevice() {
mService.disconnect()
unbindService(connection)

View File

@@ -115,15 +115,15 @@
tools:listitem="@layout/offline_user_list_view" />
<!-- <ImageView-->
<!-- android:id="@+id/btnSaveLocal"-->
<!-- android:layout_width="30dp"-->
<!-- android:layout_height="30dp"-->
<!-- android:layout_marginEnd="10dp"-->
<!-- android:src="@drawable/downloads"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintBottom_toBottomOf="@+id/rv_order_offline"-->
<!-- app:layout_constraintStart_toStartOf="parent" />-->
<ImageView
android:id="@+id/btnSaveLocal"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:src="@drawable/downloads"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/rv_order_offline"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -327,7 +327,7 @@ class HemoCubeFragmentTest {
fun testDeviceRatioClassificationNegativeBorderline() {
val ratio = 0.235
val result = hemoCubeFragment.deviceRatioClassification(ratio)
assertEquals("Negative Borderline, Repeat Test", result)
assertEquals("Negative Borderline", result)
}
@Test
@@ -339,7 +339,7 @@ class HemoCubeFragmentTest {
@Test
fun testDeviceRatioClassificationPositiveForSickleCell() {
val ratio = 0.31
val ratio = 0.37
val result = hemoCubeFragment.deviceRatioClassification(ratio)
assertEquals("Positive for Sickle Cell. HPLC for Confirmation", result)
}