Merge remote-tracking branch 'origin/tms' into dev

This commit is contained in:
kizzy 2026-06-10 16:47:01 +07:00
commit 5d51f53440
28 changed files with 1406 additions and 80 deletions

View File

@ -14,8 +14,9 @@ android {
}
defaultConfig {
applicationId = "com.mob.ustmm"
applicationId = "com.mob.utsmyanmar"
minSdk = 24
//noinspection OldTargetApi
targetSdk = 36
versionCode = 1
versionName = "1.0"
@ -38,6 +39,7 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
}

View File

@ -0,0 +1,14 @@
package com.mob.utsmyanmar.model.sirius
import com.utsmyanmar.baselib.network.model.sirius.SiriusHost
import com.utsmyanmar.baselib.network.model.sirius.SiriusMerchant
import com.utsmyanmar.baselib.network.model.sirius.SiriusProperty
data class SiriusResponse (
var serial: String,
var ecrKey: String,
var address: String,
var merchant : SiriusMerchant,
var hosts : List<SiriusHost>,
var properties: List<SiriusProperty>
)

View File

@ -0,0 +1,6 @@
package com.mob.utsmyanmar.model.sirius
enum class TMSUpdate {
UPDATE,
CHECK
}

View File

@ -0,0 +1,6 @@
package com.mob.utsmyanmar.model.sirius
data class TMSValidity(
var status: ValidityStatus? = null,
var message: String? = null
)

View File

@ -0,0 +1,6 @@
package com.mob.utsmyanmar.model.sirius
enum class ValidityStatus {
SUCCESS,
FAILURE
}

View File

@ -47,6 +47,7 @@ fun DashboardScreen2(
onNavigateSeeMore: () -> Unit = {},
onNavigateSettlement: () -> Unit = {},
onNavigateVersion: () -> Unit = {},
onNavigateFunctions: () -> Unit = {},
deviceInfoViewModel: DeviceInfoViewModel = viewModel()
) {
val deviceInfo by deviceInfoViewModel.uiState.collectAsState()
@ -250,6 +251,7 @@ fun DashboardScreen2(
)
DrawerItem("Function", Icons.Default.Dashboard) {
scope.launch { drawerState.close() }
onNavigateFunctions()
}
DrawerItem("Version", Icons.Default.Dashboard) {
scope.launch { drawerState.close() }

View File

@ -0,0 +1,235 @@
package com.mob.utsmyanmar.ui.functions
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.OnDeviceTraining
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.preview.P2Preview
import com.mob.utsmyanmar.ui.preview.P3Preview
import com.mob.utsmyanmar.ui.theme.Color
import com.mob.utsmyanmar.R
@Composable
fun FunctionsScreen(
onBack: () -> Unit = {}
) {
Scaffold(
containerColor = Color.IvoryBeige,
topBar = {
AppBar(
title = "Settings",
icon = Icons.AutoMirrored.Filled.ArrowBack,
onIconClick = onBack
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp).verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(text = "General Settings")
FunctionButton(
onClick = {},
title = "App Version",
subTitle = "1.0-uat",
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_device_info),
contentDescription = "icon",
tint = Color.LegacyRed
)
}
)
FunctionButton(
onClick = {},
title = "Host Config",
subTitle = "Detail for bound hosts",
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_database_config),
contentDescription = "icon",
tint = Color.LegacyRed
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = "icon",
tint = Color.LegacyRed
)
}
)
Text(text = "System Configuration")
FunctionButton(
onClick = {},
title = "Clear Batch",
subTitle = "Detail for bound hosts",
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_refresh),
contentDescription = "icon",
tint = Color.LegacyRed
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = "icon",
tint = Color.LegacyRed
)
}
)
FunctionButton(
onClick = {},
title = "Clear Reversal",
subTitle = "Detail for bound hosts",
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_clear),
contentDescription = "icon",
tint = Color.LegacyRed
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = "icon",
tint = Color.LegacyRed
)
}
)
FunctionButton(
onClick = {},
title = "TMS Server Url",
subTitle = "Detail for bound hosts",
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_address_global),
contentDescription = "icon",
tint = Color.LegacyRed
)
},
)
FunctionButton(
onClick = {},
title = "Download Config",
subTitle = "Download terminal config from host",
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.ic_circle_download_arrow),
contentDescription = "icon",
tint = Color.LegacyRed
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = "icon",
tint = Color.LegacyRed
)
}
)
}
}
}
@Composable
fun FunctionButton(
onClick: () -> Unit,
title: String,
subTitle: String,
leadingIcon: (@Composable () -> Unit)? = null,
trailingIcon: (@Composable () -> Unit)? = null,
) {
ElevatedButton(
onClick = onClick,
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.Black
),
modifier = Modifier.fillMaxWidth()
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
leadingIcon?.invoke()
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp)) {
Text(text = title)
Text(
text = subTitle,
style = MaterialTheme.typography.bodySmall,
color = Color.Gray
)
}
trailingIcon?.invoke()
}
}
}
@P3Preview
@P2Preview
@Composable
fun PreviewFunctionsScreen() {
FunctionsScreen()
}
@Preview
@Composable
fun PreviewFunctionButton() {
FunctionButton(
onClick = {},
title = "title",
subTitle = "sub-title"
)
}

View File

@ -37,6 +37,9 @@ import com.mob.utsmyanmar.ui.pinpad.PinPadViewModel
import com.mob.utsmyanmar.ui.settlement.SettlementViewModel
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultEvent
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultViewModel
import com.mob.utsmyanmar.ui.functions.FunctionsScreen
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupRoute
import com.mob.utsmyanmar.ui.tms_setup.TmsSetupViewModel
import com.mob.utsmyanmar.ui.version.VersionScreen
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.mob.utsmyanmar.viewmodel.TransProcessViewModel
@ -52,8 +55,23 @@ fun AppNavGraph(
NavHost(
navController = navController,
startDestination = Routes.Dashboard.route
startDestination = Routes.TmsSetup.route
) {
composable(Routes.TmsSetup.route) {
val tmsSetupViewModel: TmsSetupViewModel = hiltViewModel()
TmsSetupRoute(
viewModel = tmsSetupViewModel,
onNavigateDashboard = {
navController.navigate(Routes.Dashboard.route) {
popUpTo(Routes.TmsSetup.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
composable(Routes.Dashboard.route) {
val sharedViewModel: SharedViewModel = hiltViewModel(activity);
DashboardScreen2(
@ -62,7 +80,7 @@ fun AppNavGraph(
sharedViewModel.transactionsType.value = TransactionsType.SALE;
sharedViewModel.processCode.value = ProcessCode.SALE_PURCHASE + ProcessCode.SMART + ProcessCode.TO_ACCOUNT;
}
navController.navigate(Routes.Amount.createRoute(action)) {
launchSingleTop = true
}
@ -83,7 +101,12 @@ fun AppNavGraph(
}
},
onNavigateVersion = {
navController.navigate(Routes.Version.route);
navController.navigate(Routes.Version.route)
},
onNavigateFunctions = {
navController.navigate(Routes.Functions.route) {
launchSingleTop = true
}
}
)
}
@ -113,6 +136,12 @@ fun AppNavGraph(
)
}
composable(Routes.Functions.route) {
FunctionsScreen(
onBack = { navController.popBackStack() }
)
}
composable(Routes.VoidTrace.route) {
val voidViewModel: VoidViewModel = hiltViewModel()

View File

@ -3,6 +3,7 @@ package com.mob.utsmyanmar.ui.navigation
import android.net.Uri
sealed class Routes(val route: String) {
data object TmsSetup : Routes("tms_setup")
data object Dashboard : Routes("dashboard")
data object Amount : Routes("amount/{action}") {
fun createRoute(action: String): String = "amount/${Uri.encode(action)}"
@ -27,4 +28,5 @@ sealed class Routes(val route: String) {
data object TransactionResult : Routes("transaction_result")
data object PrintReceipt : Routes("print_receipt")
data object Version : Routes("version")
data object Functions : Routes("functions")
}

View File

@ -0,0 +1,173 @@
package com.mob.utsmyanmar.ui.tms_setup
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CloudDownload
import androidx.compose.material.icons.filled.ErrorOutline
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.mob.utsmyanmar.ui.theme.Color as AppColor
@Composable
fun TmsSetupRoute(
viewModel: TmsSetupViewModel,
onNavigateDashboard: () -> Unit
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel) {
viewModel.navigateToDashboard.collect { onNavigateDashboard() }
}
TmsSetupScreen(
state = state,
onRetry = viewModel::downloadConfigs,
onSkip = viewModel::skipDownload
)
}
@Composable
fun TmsSetupScreen(
state: TmsSetupUiState,
onRetry: () -> Unit,
onSkip: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColor.IvoryBeige),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Icon(
imageVector = if (state.isError) Icons.Default.ErrorOutline else Icons.Default.CloudDownload,
contentDescription = null,
tint = if (state.isError) AppColor.Error else AppColor.LegacyRed,
modifier = Modifier.size(72.dp)
)
Text(
text = if (state.isError) "Configuration Error" else "Setting Up Terminal",
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = AppColor.LegacyRed,
textAlign = TextAlign.Center
)
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = AppColor.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (state.isLoading) {
CircularProgressIndicator(
color = AppColor.CrimsonRed,
modifier = Modifier.size(40.dp)
)
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
color = AppColor.CrimsonRed,
trackColor = AppColor.GoldenGlow.copy(alpha = 0.3f),
strokeCap = StrokeCap.Round
)
}
Text(
text = state.statusText,
fontSize = 14.sp,
color = AppColor.Black,
textAlign = TextAlign.Center
)
if (state.isError) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
AppColor.Error.copy(alpha = 0.08f),
RoundedCornerShape(12.dp)
)
.padding(12.dp)
) {
Text(
text = state.errorMessage,
fontSize = 13.sp,
color = AppColor.Error,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
if (state.isError) {
Button(
onClick = onRetry,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = AppColor.CrimsonRed)
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = null,
modifier = Modifier
.padding(end = 8.dp)
.size(18.dp)
)
Text(text = "Retry", fontSize = 16.sp)
}
OutlinedButton(
onClick = onSkip,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = AppColor.LegacyRed)
) {
Text(text = "Skip", fontSize = 16.sp)
}
}
}
}
}

View File

@ -0,0 +1,165 @@
package com.mob.utsmyanmar.ui.tms_setup
import android.annotation.SuppressLint
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.sirius.SiriusResponse
import com.mob.utsmyanmar.model.sirius.TMSUpdate
import com.mob.utsmyanmar.model.sirius.ValidityStatus
import com.mob.utsmyanmar.utils.tms.TMSSetupsImpl
import com.mob.utsmyanmar.utils.tms.TMSUtil
import com.utsmyanmar.baselib.BaseApplication
import com.utsmyanmar.baselib.emv.EmvParamOperation
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
import com.utsmyanmar.baselib.repo.Repository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import sunmi.sunmiui.utils.LogUtil
import javax.inject.Inject
data class TmsSetupUiState(
val isLoading: Boolean = false,
val statusText: String = "Initializing...",
val isError: Boolean = false,
val errorMessage: String = ""
)
@HiltViewModel
class TmsSetupViewModel @Inject constructor(
private val repository: Repository,
private val emvParamOperation: EmvParamOperation
) : ViewModel() {
private val _uiState = MutableStateFlow(TmsSetupUiState())
val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow()
private val _navigateToDashboard = MutableSharedFlow<Unit>()
val navigateToDashboard: SharedFlow<Unit> = _navigateToDashboard.asSharedFlow()
private val disposables = CompositeDisposable()
private val tmsSetups = TMSSetupsImpl()
init {
viewModelScope.launch {
waitForHardware()
downloadConfigs()
}
}
fun downloadConfigs() {
_uiState.update {
it.copy(isLoading = true, isError = false, statusText = "Connecting to TMS server...")
}
val disposable = repository.getParams(buildRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ response ->
_uiState.update { it.copy(statusText = "Applying configuration...") }
val appResponse = SiriusResponse(
serial = response.serial.orEmpty(),
ecrKey = response.ecrKey.orEmpty(),
address = response.address.orEmpty(),
merchant = response.merchant,
hosts = response.hosts,
properties = response.properties
)
tmsSetups.initParams(appResponse, TMSUpdate.UPDATE, emvParamOperation)
onConfigApplied()
},
{ error ->
_uiState.update {
it.copy(
isLoading = false,
isError = true,
statusText = "Download failed",
errorMessage = formatNetworkError(error)
)
}
}
)
disposables.add(disposable)
}
private suspend fun waitForHardware() {
_uiState.update { it.copy(isLoading = true, statusText = "Starting hardware...") }
var elapsed = 0
while (BaseApplication.basicOptV2 == null && elapsed < 10_000) {
delay(500)
elapsed += 500
}
}
private fun onConfigApplied() {
val validity = TMSUtil.getInstance().checkParams()
if (validity.status == ValidityStatus.SUCCESS) {
_uiState.update { it.copy(isLoading = false, statusText = "Ready.") }
viewModelScope.launch { _navigateToDashboard.emit(Unit) }
} else {
_uiState.update {
it.copy(
isLoading = false,
isError = true,
statusText = "Configuration incomplete",
errorMessage = validity.message ?: "Invalid TMS configuration"
)
}
}
}
fun skipDownload() {
viewModelScope.launch { _navigateToDashboard.emit(Unit) }
}
private fun formatNetworkError(error: Throwable): String {
return when (error) {
is javax.net.ssl.SSLHandshakeException ->
"SSL handshake failed: ${error.message ?: "Certificate or protocol mismatch"}"
is javax.net.ssl.SSLException ->
"SSL/TLS error: ${error.message ?: "Secure connection could not be established"}"
is java.security.cert.CertificateException ->
"Server certificate error: ${error.message ?: "Certificate is invalid or untrusted"}"
is java.net.UnknownHostException ->
"Host not found: ${error.message ?: "Check server URL and network connection"}"
is java.net.ConnectException ->
"Connection refused: ${error.message ?: "Server is unreachable"}"
is java.net.SocketTimeoutException ->
"Connection timed out: ${error.message ?: "Server did not respond in time"}"
is retrofit2.HttpException ->
"HTTP ${error.code()} ${error.message()}"
else ->
error.message ?: "Unknown network error"
}
}
@SuppressLint("MissingPermission")
private fun buildRequest(): SiriusRequest {
return try {
val tranTime: Long = System.currentTimeMillis()
TMSUtil.getInstance().generateRequestParams("...", tranTime)
} catch (e: Exception) {
LogUtil.e("TmsSetupViewModel", e.message)
SiriusRequest()
}
}
override fun onCleared() {
super.onCleared()
disposables.clear()
}
}

View File

@ -0,0 +1,89 @@
package com.mob.utsmyanmar.utils.tms
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.telephony.TelephonyManager
import androidx.annotation.RequiresPermission
object Connectivity {
fun isConnected(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = cm.activeNetwork ?: return false
val capabilities = cm.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
fun isConnectedWifi(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false
return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
}
fun isConnectedMobile(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false
return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
}
fun isConnectedFast(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return false) ?: return false
return when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
// Downstream bandwidth in Kbps; 2000 Kbps = ~2 Mbps threshold for "fast"
capabilities.linkDownstreamBandwidthKbps >= 2000
}
else -> false
}
}
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
fun getNetworkType(context: Context): String {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork ?: return "None") ?: return "None"
if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "WiFi"
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "Ethernet"
else -> "Unknown"
}
}
val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val networkType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
tm.dataNetworkType
} else {
@Suppress("DEPRECATION")
tm.networkType
}
return when (networkType) {
TelephonyManager.NETWORK_TYPE_GPRS,
TelephonyManager.NETWORK_TYPE_EDGE,
TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_IDEN -> "2G"
TelephonyManager.NETWORK_TYPE_UMTS,
TelephonyManager.NETWORK_TYPE_EVDO_0,
TelephonyManager.NETWORK_TYPE_EVDO_A,
TelephonyManager.NETWORK_TYPE_EVDO_B,
TelephonyManager.NETWORK_TYPE_HSDPA,
TelephonyManager.NETWORK_TYPE_HSUPA,
TelephonyManager.NETWORK_TYPE_HSPA,
TelephonyManager.NETWORK_TYPE_EHRPD,
TelephonyManager.NETWORK_TYPE_HSPAP -> "3G"
TelephonyManager.NETWORK_TYPE_LTE -> "4G"
TelephonyManager.NETWORK_TYPE_NR -> "5G"
else -> "Unknown"
}
}
}

View File

@ -0,0 +1,25 @@
package com.mob.utsmyanmar.utils.tms
import com.mob.utsmyanmar.model.sirius.SiriusResponse
import com.mob.utsmyanmar.model.sirius.TMSUpdate
import com.utsmyanmar.baselib.emv.EmvParamOperation
interface TMSSetups {
fun initParams(
siriusResponse: SiriusResponse,
tmsUpdate: TMSUpdate,
emvParamOperation: EmvParamOperation
)
fun initParams(json: String)
fun convertToArray(string: String): ArrayList<String>
fun getPayHardwareVersion(): String
fun getRomVersion(): String
fun generateFinalVersion(): String
}

View File

@ -0,0 +1,274 @@
package com.mob.utsmyanmar.utils.tms
import com.google.gson.Gson
import com.mob.utsmyanmar.model.sirius.SiriusResponse
import com.mob.utsmyanmar.model.sirius.TMSUpdate
import com.utsmyanmar.baselib.BaseApplication
import com.utsmyanmar.baselib.emv.EmvParamOperation
import com.utsmyanmar.baselib.network.model.sirius.SiriusHost
import com.utsmyanmar.baselib.network.model.sirius.SiriusMerchant
import com.utsmyanmar.baselib.network.model.sirius.SiriusProperty
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import com.utsmyanmar.paylibs.utils.enums.CurrencyType
import sunmi.sunmiui.utils.LogUtil
class TMSSetupsImpl : TMSSetups {
companion object {
private const val TAG = "TMSSetupsImpl"
private const val UPI = "UPI"
private const val JCB = "JCB"
private const val VISA = "VISA"
private const val MASTERCARD = "MASTERCARD"
}
private fun currencyTextToCurrencyType(currencyText: String): CurrencyType {
return try {
CurrencyType.valueOf(currencyText)
} catch (e: Exception) {
CurrencyType.MMK
}
}
private fun currencyTextToCode(currencyText: String): String {
return when (currencyText) {
"USD" -> "804"
"CNY" -> "156"
"THB" -> "764"
"RUB" -> "643"
else -> "104"
}
}
private fun currencyCodeToText(currencyCode: String): String {
return when (currencyCode) {
"804" -> "USD"
"156" -> "CNY"
"764" -> "THB"
"643" -> "RUB"
else -> "MMK"
}
}
private fun init() {
SystemParamsOperation.getInstance().apply {
hostName = ""
terminalId = ""
merchantId = ""
ipAddress = ""
secIpAddress = ""
secHostName = ""
secHostTerminalId = ""
secHostMerchantId = ""
secHostIpAddress = ""
secHostSecIpAddress = ""
}
}
override fun initParams(
siriusResponse: SiriusResponse,
tmsUpdate: TMSUpdate,
emvParamOperation: EmvParamOperation
) {
val siriusMerchant: SiriusMerchant = siriusResponse.merchant
val siriusHosts: List<SiriusHost> = siriusResponse.hosts
val siriusProperty: List<SiriusProperty> = siriusResponse.properties
val imgUrls = mutableListOf<String>()
val ops = SystemParamsOperation.getInstance()
if (tmsUpdate == TMSUpdate.UPDATE) ops.isNeedSettlement = false
siriusResponse.address.takeIf { it.isEmpty() }?.let { ops.merchantAddress = "" }
init()
siriusMerchant.let { m ->
ops.merchantName = m.name
ops.merchantAddress = m.address
ops.merchantPhoneNo = m.phone
}
siriusResponse.address.takeIf { it.isNotEmpty() }?.let { ops.merchantAddress = it }
//host
if (siriusHosts.isNotEmpty()) {
for (host in siriusHosts) {
val isMMQR = listOf(host.name, host.description).any {
it.lowercase().run { contains("mmqr") }
}
if (isMMQR) {
ops.secHostName = host.name
ops.secHostTerminalId = host.tid
ops.secHostMerchantId = host.mid
host.secondaryIP.trim().let { ip ->
ops.secHostIpAddress = if (ip.contains(":")) "$ip/" else ""
}
host.currency.takeIf { it.isNotEmpty() }?.let {
ops.secHostCurrency = currencyTextToCode(it)
}
if (host.tid.isEmpty()) ops.secHostTerminalId = ""
if (host.mid.isEmpty()) ops.secHostTerminalId = ""
} else {
ops.hostName = host.name
ops.terminalId = host.tid
ops.merchantId = host.mid
host.primaryIP.trim().let { ip ->
ops.ipAddress = if (ip.contains(":")) ip else ""
}
host.secondaryIP.trim().let { ip ->
ops.secIpAddress = if (ip.contains(":")) ip else ""
}
host.currency.takeIf { it.isNotEmpty() }?.let {
ops.currencyType = currencyTextToCurrencyType(it)
}
if (host.tid.isEmpty()) ops.terminalId = ""
if (host.mid.isEmpty()) ops.merchantId = ""
}
}
}
//Properties
for (prop in siriusProperty) {
val name = prop.name
val data = prop.property
when (name) {
//image
"carousel_img_1",
"carousel_img_2",
"carousel_img_3",
"carousel_img_4",
"carousel_img_5",
"carousel_img_6" -> imgUrls.add(data)
//host
"host_timeout",
"host_connect_timeout" -> ops.hostResponseTimeout = data
"host_read_timeout" -> ops.hostReadTimeout = data
"reversal_delay" -> ops.reversalDelay = data
"key_index" -> ops.tmkIndex = data
"receipt_footer" -> ops.receiptFooter = data
"time_out" -> ops.setTmsTimeout(data)
"manual_update" -> ops.manualUpdate = parseBoolean(data)
"emv_enable" -> ops.setEmvEnable(parseBoolean(data))
"hostport" -> ops.portAddress = data.toInt()
"pre_auth_enable" -> ops.preAuthStatus = parseBoolean(data)
"void_enable" -> ops.voidStatus = parseBoolean(data)
"cash_advance_enable" -> ops.cashAdvanceStatus = parseBoolean(data)
"refund_enable" -> ops.refundStatus = parseBoolean(data)
"settlement_enable" -> ops.settlementStatus = parseBoolean(data)
"system_password" -> ops.systemPassword = data.take(6).ifEmpty { data }
"settlement_password" -> ops.settlementPassword = data.take(6).ifEmpty { data }
"setting_password" -> ops.settingPassword = data.take(6).ifEmpty { data }
"terminal_enable" -> ops.isActive = parseBoolean(data)
"terminal_enable_msg" -> ops.disabledMsg = data
"ssl_enable" -> ops.setSslSwitchStatus(parseBoolean(data))
"wave_pay_inquiry_status_enable" -> ops.wavePayInquiryStatus = parseBoolean(data)
"tips_adjustment_enable" -> ops.tipsAdjustmentStatus = parseBoolean(data)
"wave_enable" -> ops.wavePayStatus = parseBoolean(data)
"print_iso_enable" -> ops.printISOStatus = parseBoolean(data)
"receipt_header" -> ops.receiptHeader = data
"random_pin_pad_enable" -> ops.isRandomPinPad = parseBoolean(data)
"clear_batch_time" -> ops.clearBatchTime = data
"alert_sound_enable" -> ops.isAlertSound = parseBoolean(data)
"auto_print_enable" -> ops.isAutoPrintCustomerCopy = parseBoolean(data)
"ecr_enable" -> ops.ecrStatus = parseBoolean(data)
"manual_entry_enable" -> ops.setManualEntyrStatus(parseBoolean(data))
"mmqr_interval_waiting_time" -> ops.waveIntervalTime = data
"full_void_preauth_enable" -> ops.fullVoidPreauthStatus = parseBoolean(data)
"partial_void_preauth_enable" -> ops.partialVoidPreauthStatus = parseBoolean(data)
"clear_batch_day" -> ops.clearBatchDay = data
"qr_min_amount" -> ops.minAmount = data
"qr_max_amount" -> ops.maxAmount = data
"mmqr_auth_token" -> ops.authToken = data
"mmqr_grant_type" -> ops.grantType = data
"mmqr_token_host_address" -> ops.tokenHostAddress = "${data.trim()}/"
"mmpay_enable" -> ops.isMMPayEnabled = parseBoolean(data)
"fallback_enable" -> ops.fallbackEnabled = parseBoolean(data)
"magstripe_enable" -> ops.isMagStripeEnabled = parseBoolean(data)
"nfc_enable" -> ops.isNfcEnabled = parseBoolean(data)
"cvv_bypass_enable" -> ops.cvvBypassStatus = parseBoolean(data)
"upi_chip_cvm" -> emvParamOperation.updateChipCVM(UPI, data.toLong())
"upi_contactless_cvm" -> {
val limit = data.toLong()
ops.upiCvmLimit = limit
emvParamOperation.updateUpiCVM(limit)
}
"upi_currency_code" -> emvParamOperation.updateUpiCurrencyCode(data)
"jcb_chip_cvm" -> emvParamOperation.updateChipCVM(JCB, data.toLong())
"visa_chip_cvm" -> emvParamOperation.updateChipCVM(VISA, data.toLong())
"visa_contactless_cvm" -> emvParamOperation.updatePayWaveCVM(data.toLong())
"visa_currency_code" -> emvParamOperation.updatePayWaveCurrencyCode(data)
"master_chip_cvm" -> emvParamOperation.updateChipCVM(MASTERCARD, data.toLong())
"master_contactless_cvm" -> emvParamOperation.updatePayPassCVM(data.toLong())
"master_currency_code" -> emvParamOperation.updatePayPassCurrencyCode(data)
"terminal_capability" -> {
if (data.isNotEmpty()) ops.setTerminalCapability(data)
}
"upi_ttq" -> emvParamOperation.updateQuickPassTTQ(data)
"visa_ttq" -> emvParamOperation.updatePayWaveTTQ(data)
"master_ttq" -> emvParamOperation.updatePayPassTTQ(data)
"upi_tc_enabled" -> ops.setUpiTCEnabled(parseBoolean(data))
"debug_feature_enable" -> ops.setDebugFeatureEnabled(parseBoolean(data))
"master_terminal_capability" -> {
if (data.isNotEmpty()) emvParamOperation.updatePayPassTerminalCapability(data)
}
"speedup_contactless_enable" -> ops.setSpeedUpContactless(parseBoolean(data))
"manual_entry_pin_enable" -> ops.isManualEntryPinEnable = parseBoolean(data)
"cmhl_enabled" -> ops.setCMHLEnable(parseBoolean(data))
}
}
ops.carouselUrls = imgUrls.joinToString(",")
}
override fun initParams(json: String) {
val response = Gson().fromJson<SiriusResponse>(json, SiriusResponse::class.java)
response.properties.forEach { prop ->
LogUtil.d(TAG, "name : ${prop.name}")
LogUtil.d(TAG, "value: ${prop.property}")
}
}
override fun convertToArray(string: String): ArrayList<String> {
if (string.isEmpty()) return ArrayList()
return ArrayList(string.split(",").filter { it.isNotEmpty() })
}
override fun getPayHardwareVersion(): String {
return try {
BaseApplication.getInstance().applicationContext.packageManager
.getPackageInfo("com.sunmi.pay.hardware_v3", 0)
.versionName ?: "?"
} catch (e: Exception) {
"?"
}
}
override fun getRomVersion(): String {
return try {
android.os.Build.VERSION.RELEASE
} catch (e: Exception) {
"UNKNOWN"
}
}
override fun generateFinalVersion(): String {
val phv = getPayHardwareVersion()
val rv = getRomVersion()
val sv = try {
BaseApplication.getInstance().applicationContext.packageManager
.getPackageInfo(BaseApplication.getInstance().packageName, 0)
.versionName ?: "?"
} catch (e: Exception) {
"?"
}
return "PHV$phv-RV$rv-SV$sv"
}
private fun parseBoolean(data: String): Boolean =
data.toIntOrNull()?.let { it == 1 } ?: data.toBoolean();
}

View File

@ -0,0 +1,169 @@
package com.mob.utsmyanmar.utils.tms
import android.Manifest
import android.content.Context
import android.os.BatteryManager
import android.os.Build
import androidx.annotation.RequiresPermission
import com.mob.utsmyanmar.BuildConfig
import com.mob.utsmyanmar.model.sirius.TMSValidity
import com.mob.utsmyanmar.model.sirius.ValidityStatus
import com.mob.utsmyanmar.viewmodel.SharedViewModel
import com.sunmi.pay.hardware.aidl.AidlConstants
import com.utsmyanmar.baselib.BaseApplication
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import sunmi.sunmiui.utils.LogUtil
class TMSUtil private constructor() {
private val tmsSetups: TMSSetups = TMSSetupsImpl();
companion object {
private val TAG = TMSUtil::class.java.simpleName
@Volatile
private var app: TMSUtil? = null
fun getInstance(): TMSUtil = app ?: synchronized(this) {
app ?: TMSUtil().also { app = it }
}
}
// --- Param Init ---
fun initParams(json: String) {
tmsSetups.initParams(json)
}
fun convertToArray(string: String): ArrayList<String> = tmsSetups.convertToArray(string)
fun getPayHardwareVersion(): String = tmsSetups.getPayHardwareVersion()
fun getRomVersion(): String = tmsSetups.getRomVersion()
fun generateFinalVersion(): String = tmsSetups.generateFinalVersion()
//---shared view model---
fun loadDownloadParameters(sharedViewModel: SharedViewModel) {
sharedViewModel.setManualEntryStatus(SystemParamsOperation.getInstance().manualEntryStatus)
}
//---system params---
fun getSystemParams(name: String): String = runCatching {
BaseApplication.basicOptV2.getSysParam(name)
}.getOrDefault("")
fun getSerialNumber(): String = runCatching {
BaseApplication.basicOptV2.getSysParam("SN")
}.getOrDefault("")
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
fun generateRequestParams(lastTransName: String, lastTransTime: Long): SiriusRequest =
SiriusRequest().apply {
serial = getSerialNumber()
appPackage = BuildConfig.APPLICATION_ID
androidVersion = Build.VERSION.RELEASE
firmwareVersion = getSystemParams(AidlConstants.SysParam.FIRMWARE_VERSION)
applicationVersion = BuildConfig.VERSION_NAME
currentNetwork = getNetworkType()
lastTransaction = lastTransName
lastTranTime = lastTransTime
latitude = 0.000000
longitude = 0.000000
value = "YourValueHere"
}
//---logging--
fun retrieveParameters() {
val ops = SystemParamsOperation.getInstance()
LogUtil.d(TAG, "TID: ${ops.terminalId}")
LogUtil.d(TAG, "MID: ${ops.merchantId}")
LogUtil.d(TAG, "Merchant Name: ${ops.merchantName}")
LogUtil.d(TAG, "Merchant Address: ${ops.merchantAddress}")
LogUtil.d(TAG, "Host Timeout: ${ops.hostResponseTimeout}")
LogUtil.d(TAG, "TMS Timeout: ${ops.tmsTimeOut}")
LogUtil.d(TAG, "Key Index: ${ops.tmkIndex}")
LogUtil.d(TAG, "Receipt Footer: ${ops.receiptFooter}")
LogUtil.d(TAG, "Manual Update: ${ops.manualUpdate}")
LogUtil.d(TAG, "Master Enabled: ${ops.isEmvEnabled}")
}
//---checks---
fun checkParams(): TMSValidity {
val ops = SystemParamsOperation.getInstance()
val tid = ops.terminalId
val mid = ops.merchantId
val hostIp = ops.ipAddress
val secIp = ops.secIpAddress
val keyIndex = ops.tmkIndex
return when {
tid.length == 8 && mid.length == 15 && hostIp.isNotEmpty() && secIp.isNotEmpty() && keyIndex.isNotEmpty() -> TMSValidity(
ValidityStatus.SUCCESS, "Success"
)
tid.length != 8 -> TMSValidity(ValidityStatus.FAILURE, "Tid is invalid")
mid.length != 15 -> TMSValidity(ValidityStatus.FAILURE, "Mid is invalid")
hostIp.isEmpty() -> TMSValidity(ValidityStatus.FAILURE, "host ip is empty")
secIp.isEmpty() -> TMSValidity(ValidityStatus.FAILURE, "sec ip is empty")
else -> TMSValidity(ValidityStatus.FAILURE, "KeyIndex is invalid")
}
}
fun checkSecHostParams(): TMSValidity {
val ops = SystemParamsOperation.getInstance()
val tid = ops.secHostTerminalId
val mid = ops.secHostMerchantId
val hostIp = ops.secHostIpAddress
val secIp = ops.secHostSecIpAddress
val keyIndex = ops.tmkIndex
return when {
tid.length == 8 && mid.length == 15 && hostIp.isNotEmpty() && secIp.isNotEmpty() && keyIndex.isNotEmpty()
-> TMSValidity(ValidityStatus.SUCCESS, "Success")
tid.length != 8 -> TMSValidity(ValidityStatus.FAILURE, "MMQR Tid is invalid!")
mid.length != 15 -> TMSValidity(ValidityStatus.FAILURE, "MMQR Mid is invalid!")
hostIp.isEmpty() -> TMSValidity(ValidityStatus.FAILURE, "MMQR Pri-Ip is invalid!")
else -> TMSValidity(ValidityStatus.FAILURE, "MMQR Sec-Ip is invalid!")
}
}
//---helpers---
private fun getTransactionStatus(code: String): String = when (code) {
"00" -> "Transaction Approved"
else -> "Transaction Failed, reason : $code"
}
private fun getBoolean(data: String): Boolean = data == "1"
private fun getBatteryLevel(context: Context): Int {
val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
private fun getNetworkType(): String {
val ctx = BaseApplication.getInstance().applicationContext
return when {
Connectivity.isConnectedWifi(ctx) -> {
LogUtil.d(TAG, "Connected to Wifi")
"WIFI"
}
Connectivity.isConnectedMobile(ctx) -> {
val type = try {
Connectivity.getNetworkType(ctx)
} catch (e: SecurityException) {
LogUtil.d(TAG, "READ_PHONE_STATE permission not granted: ${e.message}")
"MOBILE"
}
LogUtil.d(TAG, "Connected to Mobile Network: $type")
type
}
else -> "No Internet Connection"
}
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10v-1c-0.6,0.9 -1.2,1.7 -1.7,2.4c-0.4,2.2 -1.6,4.1 -3.3,5.4c-0.1,-0.3 -0.2,-0.7 -0.3,-1c-0.1,-0.4 -0.2,-0.8 -0.4,-1.2c-0.1,-0.4 -0.2,-0.8 -0.4,-1.1c-0.1,-0.1 -0.3,-0.2 -0.5,-0.3c-0.7,-0.2 -1.6,0.1 -2.1,-0.6c-0.2,-0.3 -0.2,-0.6 -0.1,-1c0.2,-0.3 0.3,-0.6 0.5,-0.9c0.2,-0.4 0.5,-0.9 0.6,-1.4c-0.8,-1.2 -1.6,-2.6 -2,-3.9h-0.1c-0.1,0 -0.1,0 -0.2,-0.1c-0.2,-0.2 -0.3,-0.7 -0.2,-1c0,-0.5 0.3,-0.8 0.2,-1.3c0,-0.1 -0.1,-0.9 -0.1,-0.9c-0.3,0 -0.8,0 -0.7,-0.5V3.5H12h0.5c0.2,-0.6 0.6,-1.1 0.9,-1.5H12zM18,2c-2.2,0 -4,1.8 -4,4s4,7 4,7s4,-4.8 4,-7S20.2,2 18,2zM18,4.5c0.8,0 1.5,0.7 1.5,1.5S18.8,7.5 18,7.5S16.5,6.8 16.5,6S17.2,4.5 18,4.5zM8,5.1c0.4,0 0.7,0 1,0.1s0.6,0.3 0.8,0.5s0.5,0.5 0.5,0.8c0,0.1 0,0.2 -0.1,0.2C10.1,6.8 10,6.8 9.9,6.8c-0.3,0 -0.6,0 -0.8,-0.1C9,6.6 8.8,6.4 8.6,6.3C8.1,6.1 7.2,7.4 7.1,7.8C7,8.1 7,8.8 7.5,8.9c0.3,0 1,-0.6 1.2,-0.8C8.9,8 9,7.9 9.2,7.8c0.8,-0.1 1.4,0.6 1.6,1.3C11,9.9 9.6,10.5 9,10.7c-0.2,0.1 -0.3,-0.1 -0.5,0c-0.5,0.2 -1.1,0.9 -1.1,1.4s-0.1,1 -0.2,1.5c-0.1,0 -0.2,-0.1 -0.2,-0.1v-0.2c0,-0.3 -0.1,-0.6 -0.4,-0.8c-0.1,0 -0.1,-0.1 -0.2,-0.1c-0.3,-0.1 -0.6,-0.4 -0.9,-0.1c-0.2,0.2 -0.4,0.5 -0.4,0.8c0,0.1 0,0.2 0.1,0.3c0.2,0.1 0.4,0 0.6,0c0.1,0 0.2,0.2 0.3,0.3c0.2,0.3 0.3,0.8 0.7,0.8h0.7h1.3c0.3,0.1 0.8,0.2 1,0.4c0.1,0.2 0.1,0.4 0.2,0.6c0.4,0.5 1.1,0.5 1.7,0.7c0.2,0.1 0.3,0.2 0.3,0.4c0,0.3 -0.1,0.7 -0.2,1s-0.2,0.7 -0.4,0.9s-0.4,0.3 -0.6,0.4c-0.4,0.2 -0.6,0.6 -0.8,0.9c0,0 -0.1,0.2 -0.2,0.3c-0.8,-0.2 -1.5,-0.5 -2.2,-1v-0.2c-0.1,-0.4 -0.2,-0.7 -0.3,-1c-0.2,-0.5 -0.5,-1.1 -0.6,-1.6c0,-0.5 0.1,-1 -0.2,-1.4c-0.3,-0.5 -1.1,-0.5 -1.6,-0.8c-0.4,-0.4 -0.9,-0.8 -1.3,-1.3V12c0,-2.7 1.3,-5.1 3.3,-6.7C7.3,5.2 7.6,5.1 8,5.1z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512.8,1002.4c-65.6,0 -129.6,-12.8 -189.6,-38.4 -58.4,-24.8 -110.4,-60 -155.2,-104.8S88,761.6 64,703.2c-25.6,-60 -38.4,-124 -38.4,-190.4 0,-65.6 12.8,-130.4 38.4,-190.4 24,-57.6 59.2,-110.4 104,-155.2s96.8,-80 155.2,-104.8C383.2,36.8 447.2,24 512.8,24s129.6,12.8 189.6,38.4c58.4,24.8 110.4,60 155.2,104.8 44.8,44.8 80,96.8 104.8,155.2 25.6,60 38.4,124 38.4,190.4 0,65.6 -12.8,130.4 -38.4,190.4 -24.8,58.4 -60,110.4 -104.8,155.2 -44.8,44.8 -96.8,80 -155.2,104.8 -60,25.6 -123.2,39.2 -189.6,39.2zM512.8,71.2c-242.4,0 -440,198.4 -440,441.6s197.6,441.6 440.8,441.6c243.2,0 440.8,-198.4 440.8,-441.6S756,71.2 512.8,71.2z"
android:fillColor="#000000"/>
<path
android:pathData="M512,811.2c-5.6,0 -12,-2.4 -16,-6.4 -1.6,-0.8 -2.4,-1.6 -4,-3.2L280,589.6c-9.6,-9.6 -9.6,-24 0,-33.6 4.8,-4.8 10.4,-7.2 16.8,-7.2s12,2.4 16.8,7.2L488,731.2V236c0,-12.8 10.4,-24 24,-24 12.8,0 24,10.4 24,24v495.2l175.2,-175.2c4.8,-4.8 10.4,-7.2 16.8,-7.2s12,2.4 16.8,7.2c9.6,9.6 9.6,24 0,33.6l-212,212c-1.6,1.6 -2.4,2.4 -4,3.2 -5.6,4 -11.2,6.4 -16.8,6.4z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:fillColor="#FF000000"
android:pathData="m15.3,13.52c-4.61,2.66 -6.21,8.52 -3.54,13.14L32.21,62.09 15.73,71.6c-4.61,2.66 -6.18,8.51 -3.52,13.12l5.59,9.68 8.03,-4.64c3.18,4.61 7.29,10.53 11,15.48 2.79,3.72 3.86,6.62 7.09,8.89 1.62,1.13 3.79,1.7 5.91,1.63 2.11,-0.07 4.28,-0.6 7.03,-1.47a3.88,3.88 0,0 0,0.44 -0.15L104.38,92.85c2.94,-1.33 5.3,-2.31 7.09,-3.05 1.79,-0.74 2.7,-0.95 4.13,-1.93 0.36,-0.25 0.86,-0.47 1.46,-1.65 0.3,-0.59 0.6,-1.57 0.4,-2.58 -0.19,-1.01 -0.81,-1.8 -1.29,-2.23 -0.95,-0.86 -1.46,-0.9 -1.88,-1.01 -0.42,-0.11 -0.73,-0.15 -1.06,-0.19 -1.32,-0.17 -2.92,-0.2 -5.33,-0.32L95.76,79.25C92.9,79.1 88.82,75.69 86.64,72.48L77.95,59.68 83.07,56.73 77.48,47.04c-2.66,-4.61 -8.53,-6.16 -13.15,-3.5l-15.46,8.92 -20.45,-35.43c-2.66,-4.61 -8.51,-6.18 -13.12,-3.52zM32.55,85.89 L71.21,63.58 80.23,76.84c2.98,4.38 7.96,9.79 15.14,10.16l2.78,0.15 -43.6,19.75c-0.03,0.01 -0.04,-0.01 -0.07,0 -2.38,0.74 -4.04,1.09 -4.91,1.12 -0.89,0.03 -0.87,-0.04 -1.15,-0.24 -0.58,-0.4 -2.22,-2.97 -5.36,-7.16C39.67,96.11 35.71,90.46 32.55,85.89z"/>
</vector>

View File

@ -0,0 +1,52 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M0,0h48v48h-48z"
android:fillColor="#ffffff"
android:fillAlpha="0.01"/>
<path
android:pathData="M34,12V20V21C31.045,21 28.389,22.282 26.559,24.32C24.968,26.091 24,28.432 24,31C24,31.579 24.049,32.146 24.144,32.698C24.658,35.705 26.514,38.253 29.074,39.705C26.412,40.51 22.878,41 19,41C10.716,41 4,38.761 4,36V28V20V12"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M34,12C34,14.761 27.284,17 19,17C10.716,17 4,14.761 4,12C4,9.239 10.716,7 19,7C27.284,7 34,9.239 34,12Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#CD2029"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M4,28C4,30.761 10.716,33 19,33C20.807,33 22.539,32.894 24.144,32.698"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M4,20C4,22.761 10.716,25 19,25C21.756,25 24.339,24.752 26.559,24.32"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M44,31C44,36.523 39.523,41 34,41C32.209,41 30.528,40.529 29.074,39.705C26.514,38.253 24.658,35.705 24.144,32.698C24.049,32.146 24,31.579 24,31C24,28.432 24.968,26.091 26.559,24.32C28.389,22.282 31.045,21 34,21C39.523,21 44,25.477 44,31Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#CD2029"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M34,27V31M37.464,29L34,31M37.464,33L34,31M34,35V31M30.536,33L34,31M30.536,29L34,31"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="192"
android:viewportHeight="192">
<path
android:pathData="M96.01,63.63c-3.32,0 -6.12,2.71 -6.12,6.1a6.12,6.12 0,0 0,6.12 6.12c3.38,0 6.1,-2.8 6.1,-6.12s-2.78,-6.1 -6.1,-6.1z"
android:strokeAlpha="0.98"
android:strokeLineJoin="round"
android:strokeWidth="3.78002"
android:fillColor="#000000"
android:strokeColor="#000000"
android:fillAlpha="0.98"
android:strokeLineCap="round"/>
<path
android:pathData="M83.43,122.37h25.13M87.48,90.96h8.54v31.41"
android:strokeLineJoin="round"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M54,46h84M60,22h72c5.54,0 10,4.46 10,10v128c0,5.54 -4.46,10 -10,10L60,170c-5.54,0 -10,-4.46 -10,-10L50,32c0,-5.54 4.46,-10 10,-10zM54,146h84"
android:strokeLineJoin="round"
android:strokeWidth="12"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M643.8,876.9c77.5,-26.7 141.1,-75.8 186,-137.3a19.7,19.7 0,0 0,5.7 -17.4,19.4 19.4,0 0,0 -0.1,-0.6l-0,-0a19.7,19.7 0,0 0,-10.7 -14.2l-56.5,-40.1a19.8,19.8 0,0 0,-17.3 -5.4c-0.2,0 -0.3,0 -0.5,0.1l-0.1,0a19.8,19.8 0,0 0,-14.1 10.5c-31.4,42.9 -75.6,76.9 -129.8,95.6 -140.8,48.5 -293.6,-25.7 -342,-166.1s26.4,-292.9 167.2,-341.4c132,-45.4 273.6,15 330.8,138.1l-89.4,0.6c-9,-1.2 -17.6,3.9 -21,12.3a19.7,19.7 0,0 0,6.3 23.1l153.7,155.2c3.8,3.8 9,6 14.5,5.8s10.6,-2.5 14.2,-6.5l148.2,-162.7c6.5,-5.3 8.9,-14.2 6,-22 -0.1,-0.1 -0.1,-0.3 -0.2,-0.4a19.4,19.4 0,0 0,-1.2 -2.6l-0,-0.1c-4,-7.1 -12,-11 -20.1,-9.8l-89.5,0.9 -1.2,-3.4c-68.9,-200 -287.9,-306.3 -488.5,-237.3S86.4,439.6 155.2,639.6c68.9,200 287.9,306.3 488.5,237.3l0,0z"
android:fillColor="#000000"/>
</vector>

View File

@ -1,7 +1,5 @@
package com.utsmyanmar.baselib.di;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.utsmyanmar.baselib.BuildConfig;
@ -11,8 +9,6 @@ import com.utsmyanmar.baselib.network.WaveTokenApiService;
import com.utsmyanmar.baselib.network.interceptor.HostSelectionInterceptor;
import com.utsmyanmar.baselib.network.interceptor.QRAuthInterceptor;
import com.utsmyanmar.baselib.network.interceptor.SiriusInterceptor;
import com.utsmyanmar.baselib.network.interceptor.WaveAuthInterceptor;
import com.utsmyanmar.paylibs.Constant;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import java.lang.annotation.Retention;
@ -38,7 +34,6 @@ import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import sunmi.sunmiui.utils.LogUtil;
@Module
@InstallIn(SingletonComponent.class)
@ -355,7 +350,7 @@ public class NetworkModule {
tmsAddress = getTMSUrlFromNative();
}
String baseUrl = tmsAddress.trim() + "/api/v1/";
String baseUrl = tmsAddress.trim() + "/";
final Gson gson =

View File

@ -3,12 +3,10 @@ package com.utsmyanmar.baselib.network.interceptor;
import androidx.annotation.NonNull;
import com.utsmyanmar.baselib.util.TerminalUtil;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
@ -18,13 +16,6 @@ public class SiriusInterceptor implements Interceptor {
private static final String TAG = SiriusInterceptor.class.getSimpleName();
public static native String getHiddenFromNative();
static {
System.loadLibrary("native-lib");
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
@ -35,9 +26,9 @@ public class SiriusInterceptor implements Interceptor {
String hashed = "";
String nonce = TerminalUtil.getInstance().generateRandomNumbers();
try {
hashed = TerminalUtil.getInstance().generateHashedString(nonce);
hashed = TerminalUtil.getInstance().generateHashedString(nonce).toLowerCase();
// LogUtil.d(TAG,"hashed :"+ hashed);
LogUtil.d(TAG,"hashed :"+ hashed);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
@ -48,8 +39,13 @@ public class SiriusInterceptor implements Interceptor {
.addHeader("request-id", hashed)
.addHeader("request-code",nonce)
.build();
LogUtil.d(TAG, "URL: " + newRequest.url());
LogUtil.d(TAG, "Method: " + newRequest.method());
LogUtil.d(TAG, "Headers: " + newRequest.headers());
LogUtil.d(TAG, "request-id: " + hashed);
LogUtil.d(TAG, "request-code: " + nonce);
return chain.proceed(newRequest);
}
}

View File

@ -2,7 +2,7 @@ package com.utsmyanmar.baselib.network.model.sirius;
public class SiriusMerchant {
private int id;
private String id;
private String name;
@ -16,7 +16,7 @@ public class SiriusMerchant {
private String mobile;
public SiriusMerchant(int id, String name, String description, String address,String address2,String phone,String mobile) {
public SiriusMerchant(String id, String name, String description, String address,String address2,String phone,String mobile) {
this.id = id;
this.name = name;
this.description = description;
@ -50,7 +50,7 @@ public class SiriusMerchant {
return mobile;
}
public void setId(int id) {
public void setId(String id) {
this.id = id;
}
@ -66,7 +66,7 @@ public class SiriusMerchant {
this.address = address;
}
public int getId() {
public String getId() {
return id;
}

View File

@ -2,12 +2,12 @@ package com.utsmyanmar.baselib.network.model.sirius;
public class SiriusProperty {
private int id;
private int terId;
private String id;
private String terId;
private int appId;
private String appId;
private int configId;
private String configId;
private String name;
@ -17,7 +17,7 @@ public class SiriusProperty {
private String property;
public SiriusProperty(int id, int terId, int appId, int configId, String name, String description, String type, String property) {
public SiriusProperty(String id, String terId, String appId, String configId, String name, String description, String type, String property) {
this.id = id;
this.terId = terId;
this.appId = appId;
@ -28,19 +28,19 @@ public class SiriusProperty {
this.property = property;
}
public void setId(int id) {
public void setId(String id) {
this.id = id;
}
public void setTerId(int terId) {
public void setTerId(String terId) {
this.terId = terId;
}
public void setAppId(int appId) {
public void setAppId(String appId) {
this.appId = appId;
}
public void setConfigId(int configId) {
public void setConfigId(String configId) {
this.configId = configId;
}
@ -60,19 +60,19 @@ public class SiriusProperty {
this.property = property;
}
public int getId() {
public String getId() {
return id;
}
public int getTerId() {
public String getTerId() {
return terId;
}
public int getAppId() {
public String getAppId() {
return appId;
}
public int getConfigId() {
public String getConfigId() {
return configId;
}

View File

@ -15,10 +15,30 @@ public class SiriusRequest {
private String lastTransaction;
private String lastTranTime;
private Long lastTranTime;
public SiriusRequest() {}
public SiriusRequest(String serial, String appPackage, String androidVersion, String firmwareVersion, String applicationVersion, String currentNetwork, String lastTransaction, String lastTranTime) {
private String value;
private double latitude;
private double longitude;
public SiriusRequest() {
}
public SiriusRequest(
String serial,
String appPackage,
String androidVersion,
String firmwareVersion,
String applicationVersion,
String currentNetwork,
String lastTransaction,
Long lastTranTime,
String value,
double latitude,
double longitude
) {
this.serial = serial;
this.appPackage = appPackage;
this.androidVersion = androidVersion;
@ -27,6 +47,9 @@ public class SiriusRequest {
this.currentNetwork = currentNetwork;
this.lastTransaction = lastTransaction;
this.lastTranTime = lastTranTime;
this.value = value;
this.latitude = latitude;
this.longitude = longitude;
}
public void setSerial(String serial) {
@ -57,7 +80,7 @@ public class SiriusRequest {
this.lastTransaction = lastTransaction;
}
public void setLastTranTime(String lastTranTime) {
public void setLastTranTime(Long lastTranTime) {
this.lastTranTime = lastTranTime;
}
@ -89,7 +112,31 @@ public class SiriusRequest {
return lastTransaction;
}
public String getLastTranTime() {
public Long getLastTranTime() {
return lastTranTime;
}
public String getValue(){
return this.value;
}
public void setValue(String value){
this.value = value;
}
public double getLatitude(){
return this.latitude;
}
public void setLatitude(double latitude){
this.latitude = latitude;
}
public double getLongitude(){
return this.longitude;
}
public void setLongitude(double longitude){
this.longitude = longitude;
}
}

View File

@ -1,9 +1,6 @@
package com.utsmyanmar.baselib.util;
import android.os.RemoteException;
import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2;
import com.utsmyanmar.baselib.BaseApplication;
import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil;
import java.nio.charset.StandardCharsets;
@ -12,53 +9,28 @@ import java.security.NoSuchAlgorithmException;
import java.util.Random;
public class TerminalUtilsImpl implements TerminalUtils{
long number = 2485718;
long sub = 1251151;
public static native String getHiddenFromNative();
public static native String getEncryptedFromNative();
static {
System.loadLibrary("native-lib");
}
@Override
public String generateHashString(String random,BasicOptV2 basicOptV2) throws NoSuchAlgorithmException {
String sn = getSerialNumber(basicOptV2);
String snPN = BaseApplication.getInstance().getPackageName();
String nonce = random;
String text = sn + snPN + nonce;
// LogUtil.d(TAG,"Plain Text: "+text);
String sn = "P30224BSJ0276";
String snPN = "com.mob.utsmyanmar";
String text = sn + snPN + random;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
return ByteUtil.bytes2HexStr(hash);
}
@Override
public String generateRandom() {
Random rnd = new Random();
StringBuilder sb = new StringBuilder((1000000 + rnd.nextInt(9000000)));
StringBuilder sb = new StringBuilder();
sb.append(1000000 + rnd.nextInt(9000000));
return sb.toString();
}
@Override
public String getSerialNumber(BasicOptV2 basicOptV2){
String data = "";
try {
data = basicOptV2.getSysParam(getEncryptedFromNative());
} catch (RemoteException e) {
e.printStackTrace();
}
return data;
return "abcd";
}

View File

@ -38,7 +38,7 @@ public class SystemParamsSettings implements Serializable {
private boolean checkExpSwitch = false;
private String tmsAddress = "https://tms.smile-mm.com";
private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1";
// private String tmsAddress = "http://128.199.170.203";
private String terminalCapability = "E0E8C8";