diff --git a/app/build.gradle b/app/build.gradle index 1db8587..b36fef6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' + } \ No newline at end of file diff --git a/app/release/hpos-app.apk b/app/release/hpos-app.apk deleted file mode 100644 index b9d6528..0000000 Binary files a/app/release/hpos-app.apk and /dev/null differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index acfe21e..14fde68 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -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" } ], diff --git a/app/src/main/java/com/example/hpostesting/data/api/MolbioResultApi.kt b/app/src/main/java/com/example/hpostesting/data/api/MolbioResultApi.kt index 0b653bb..92d3091 100644 --- a/app/src/main/java/com/example/hpostesting/data/api/MolbioResultApi.kt +++ b/app/src/main/java/com/example/hpostesting/data/api/MolbioResultApi.kt @@ -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( diff --git a/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt b/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt index bffe71e..d982014 100644 --- a/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt +++ b/app/src/main/java/com/example/hpostesting/data/repository/DatabaseRepository.kt @@ -77,6 +77,9 @@ class DatabaseRepository @Inject constructor( return safeApiCall { molbioResultApi.deviceUpdate(deviceUpdateRequest) } } + override suspend fun downloadClientCertificate(): Result { + return safeApiCall { molbioResultApi.downloadClientCertificate() } + } override suspend fun uploadLogs(logFile: MultipartBody.Part): Result { return safeApiCall { molbioResultApi.uploadLogs(logFile) } } diff --git a/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt b/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt index cd3e9b5..f595aeb 100644 --- a/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt +++ b/app/src/main/java/com/example/hpostesting/data/repository/Repository.kt @@ -45,5 +45,6 @@ interface Repository { suspend fun deviceUpdate(deviceUpdateRequest: DeviceUpdateRequest): Result + suspend fun downloadClientCertificate(): Result suspend fun uploadLogs(logFile: MultipartBody.Part): Result } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt b/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt index 56898c7..433075e 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/NatsManager.kt @@ -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 @@ -9,9 +12,21 @@ 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) { @@ -21,11 +36,50 @@ 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 @@ -70,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", @@ -84,6 +143,7 @@ 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) @@ -117,7 +177,7 @@ class NatsManager(datacollector: DashboardActivity) { } catch (exp: Exception) { println(exp.printStackTrace()) connect = false - datacollector.setConnect(true) + datacollector.setConnect(false) } }.start() diff --git a/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsActivity.kt b/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsActivity.kt index 307ac08..027fe09 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsActivity.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/assurance/AssuranceControlsActivity.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt index fac8a0f..cd62ff2 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/dashboard/HomeFragment.kt @@ -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?, @@ -203,7 +210,7 @@ class HomeFragment : Fragment() { hemoCubeViewModel.deviceUpdate(createDeviceUpdateRequestData()) hemoCubeViewModel.uploadLogs() hemoCubeViewModel.startPeriodicCheckUpdate() - + hemoCubeViewModel.downloadClientCertificate() } } } else { @@ -213,6 +220,11 @@ class HomeFragment : Fragment() { Toast.LENGTH_SHORT ).show() } + + + + + hemoCubeViewModel.loginResponse.observe(viewLifecycleOwner) { response -> when (response) { is Result.Success -> { @@ -267,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 -> { @@ -291,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)) @@ -342,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() diff --git a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt index 4bff0e9..8a0b230 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/deviceprovision/DeviceProvisionFragment.kt @@ -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 diff --git a/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt b/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt index 79ad9e0..a56407d 100644 --- a/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt +++ b/app/src/main/java/com/example/hpostesting/presentation/hemocube/HemoCubeViewModel.kt @@ -79,6 +79,7 @@ class HemoCubeViewModel @Inject constructor( val checkUpdate = MutableLiveData>() val deviceUpdate = MutableLiveData>() + val downloadcertificate = MutableLiveData>() val uploadLogs = MutableLiveData?>() @@ -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( repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.MINUTES