added permission support for USB & established a connection using service
This commit is contained in:
@@ -17,34 +17,39 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.RefactoredApp"
|
android:theme="@style/Theme.RefactoredApp"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".UsbService"
|
android:name=".presentation.testRight.UsbService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"></service>
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".presentation.SplashActivity"
|
android:name=".presentation.SplashActivity"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:noHistory="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".presentation.TestRight.TestRightActivity"
|
android:name=".presentation.testRight.TestRightActivity"
|
||||||
android:exported="false" />
|
android:exported="false">
|
||||||
<activity
|
|
||||||
android:name=".presentation.MainActivity"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||||
android:resource="@xml/device_filter" />
|
android:resource="@xml/device_filter" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".presentation.MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.example.refactoredapp
|
package com.example.refactoredapp
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
import com.example.refactoredapp.data.model.SampleDetails
|
import com.example.refactoredapp.data.model.SampleDetails
|
||||||
|
|
||||||
class MyApplication : Application() {
|
class MyApplication : Application() {
|
||||||
@@ -8,4 +12,5 @@ class MyApplication : Application() {
|
|||||||
private val sampleDetails = SampleDetails("Change it later");
|
private val sampleDetails = SampleDetails("Change it later");
|
||||||
fun getSampleDetails() : SampleDetails = sampleDetails
|
fun getSampleDetails() : SampleDetails = sampleDetails
|
||||||
// fun setSampleDetails(sample: SampleDetails) {sampleDetails = sample}
|
// fun setSampleDetails(sample: SampleDetails) {sampleDetails = sample}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,4 +2,8 @@ package com.example.refactoredapp.data
|
|||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
const val ACTION_USB_PERMISSION = "shanmukha.in.sickle_cell.USB_PERMISSION"
|
const val ACTION_USB_PERMISSION = "shanmukha.in.sickle_cell.USB_PERMISSION"
|
||||||
|
|
||||||
|
const val WRITE_TIMEOUT_MILLIS = 60000 // 1 min
|
||||||
|
const val READ_TIMEOUT_MILLIS = 60000 // 1 min
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,4 +3,6 @@ package com.example.refactoredapp.data
|
|||||||
object DataHolder {
|
object DataHolder {
|
||||||
var isStoragePermissionGranted = false
|
var isStoragePermissionGranted = false
|
||||||
var isAppFolderCreated = false
|
var isAppFolderCreated = false
|
||||||
|
|
||||||
|
var isReferenceTaken = false
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.example.refactoredapp.data.model
|
|
||||||
|
|
||||||
data class DenovixData(
|
|
||||||
val serialNumber: Int,
|
|
||||||
val nameSample: String,
|
|
||||||
val ratio: Double,
|
|
||||||
val result: String
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.example.refactoredapp.data.model
|
||||||
|
|
||||||
|
//class TestRightCmdConstants {
|
||||||
|
//
|
||||||
|
// companion object {
|
||||||
|
// const val readCommand = "read\r"
|
||||||
|
// const val autosetCommand = "autoset\r"
|
||||||
|
// const val printCommand = "print\r"
|
||||||
|
// const val runCommand = "run\r"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
enum class TestRightCommands(val command: String) {
|
||||||
|
read("read\r"),
|
||||||
|
autoset("autoset\r"),
|
||||||
|
print("print 239 2719\r"),
|
||||||
|
print_all("print\r"),
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.example.refactoredapp.data.model
|
||||||
|
|
||||||
|
data class TestRightDeviceConstants(
|
||||||
|
val a: String,
|
||||||
|
val b: String,
|
||||||
|
val c: String,
|
||||||
|
val d: String
|
||||||
|
)
|
||||||
@@ -1,40 +1,13 @@
|
|||||||
package com.example.refactoredapp.presentation
|
package com.example.refactoredapp.presentation
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.hardware.usb.UsbDevice
|
|
||||||
import android.hardware.usb.UsbManager
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.AdapterView
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.example.refactoredapp.R
|
|
||||||
import com.example.refactoredapp.data.Constants
|
|
||||||
import com.example.refactoredapp.databinding.ActivityMainBinding
|
import com.example.refactoredapp.databinding.ActivityMainBinding
|
||||||
import com.example.refactoredapp.presentation.TestRight.TestRightActivity
|
import com.example.refactoredapp.presentation.testRight.TestRightActivity
|
||||||
import com.example.refactoredapp.util.MyUtils
|
|
||||||
import com.example.refactoredapp.util.MyViewModelFactory
|
import com.example.refactoredapp.util.MyViewModelFactory
|
||||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
|
||||||
import com.hoho.android.usbserial.driver.UsbSerialPort
|
|
||||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
|
||||||
import com.hoho.android.usbserial.util.SerialInputOutputManager
|
|
||||||
import java.io.File
|
|
||||||
import java.lang.Exception
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity()
|
class MainActivity : AppCompatActivity()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.example.refactoredapp.presentation
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.example.refactoredapp.data.model.DenovixData
|
|
||||||
import com.example.refactoredapp.data.model.DeviceType
|
import com.example.refactoredapp.data.model.DeviceType
|
||||||
import com.example.refactoredapp.domain.ParseCSV
|
import com.example.refactoredapp.domain.ParseCSV
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -15,7 +14,7 @@ class MainViewModel(private val parseDenovix: ParseCSV, private val parseTestRig
|
|||||||
var device = DeviceType.TestRight
|
var device = DeviceType.TestRight
|
||||||
var isStoragePermission = MutableLiveData<Boolean>(false)
|
var isStoragePermission = MutableLiveData<Boolean>(false)
|
||||||
|
|
||||||
val resultsLiveData = MutableLiveData<ArrayList<DenovixData>>()
|
// val resultsLiveData = MutableLiveData<ArrayList<DenovixData>>()
|
||||||
|
|
||||||
fun parseCSV(file: File){
|
fun parseCSV(file: File){
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package com.example.refactoredapp.presentation
|
package com.example.refactoredapp.presentation
|
||||||
|
|
||||||
import com.example.refactoredapp.data.model.DenovixData
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.example.refactoredapp.R
|
import com.example.refactoredapp.R
|
||||||
|
import com.example.refactoredapp.data.model.SampleDetails
|
||||||
|
|
||||||
class RVAdapter : RecyclerView.Adapter<RVAdapter.ViewHolder>() {
|
class RVAdapter : RecyclerView.Adapter<RVAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val dataset = ArrayList<DenovixData>()
|
private val dataset = ArrayList<SampleDetails>()
|
||||||
|
|
||||||
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ class RVAdapter : RecyclerView.Adapter<RVAdapter.ViewHolder>() {
|
|||||||
return dataset.size
|
return dataset.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDataSet(newDataSet: ArrayList<DenovixData>) {
|
fun updateDataSet(newDataSet: ArrayList<SampleDetails>) {
|
||||||
dataset.clear()
|
dataset.clear()
|
||||||
dataset.addAll(newDataSet)
|
dataset.addAll(newDataSet)
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.example.refactoredapp.presentation.TestRight
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.example.refactoredapp.R
|
|
||||||
import com.example.refactoredapp.databinding.ActivityMainBinding
|
|
||||||
import com.example.refactoredapp.databinding.ActivityTestRightBinding
|
|
||||||
import com.example.refactoredapp.util.MyViewModelFactory
|
|
||||||
|
|
||||||
class TestRightActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityTestRightBinding
|
|
||||||
private lateinit var viewModel: TestRightViewModel
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
binding = ActivityTestRightBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
viewModel = ViewModelProvider(this, MyViewModelFactory(this.applicationContext))[TestRightViewModel::class.java]
|
|
||||||
|
|
||||||
if (!viewModel.isReferenceDone)
|
|
||||||
supportFragmentManager.beginTransaction().replace(binding.flMain.id, TestRightExpReference()).commit()
|
|
||||||
else
|
|
||||||
supportFragmentManager.beginTransaction().replace(binding.flMain.id, TestRightExpSample()).commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.example.refactoredapp.presentation.TestRight
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import com.example.refactoredapp.R
|
|
||||||
import com.example.refactoredapp.databinding.FragmentTestRightExpReferenceBinding
|
|
||||||
import com.example.refactoredapp.databinding.FragmentTestRightResultsBinding
|
|
||||||
|
|
||||||
class TestRightExpReference : Fragment() {
|
|
||||||
|
|
||||||
private lateinit var binding: FragmentTestRightExpReferenceBinding
|
|
||||||
private val viewModel: TestRightViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
binding = FragmentTestRightExpReferenceBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
setupListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupListeners() {
|
|
||||||
binding.btnSetReference.setOnClickListener {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.example.refactoredapp.presentation.TestRight
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class TestRightViewModel : ViewModel() {
|
|
||||||
|
|
||||||
var isReferenceDone = false
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.example.refactoredapp.presentation.TestRight
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.IBinder
|
|
||||||
|
|
||||||
class UsbService : Service() {
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder {
|
|
||||||
TODO("Return the communication channel to the service.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.example.refactoredapp.presentation
|
||||||
|
|
||||||
|
interface UsbServiceListener {
|
||||||
|
fun onUsbConnect()
|
||||||
|
fun onUsbConnectError(e: Exception?)
|
||||||
|
fun onUsbRead(data: ByteArray?)
|
||||||
|
fun onUsbRead()
|
||||||
|
fun onUsbIoError(e: Exception?)
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package com.example.refactoredapp.presentation.testRight
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.*
|
||||||
|
import android.hardware.usb.UsbDevice
|
||||||
|
import android.hardware.usb.UsbDeviceConnection
|
||||||
|
import android.hardware.usb.UsbManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.example.refactoredapp.data.Constants
|
||||||
|
import com.example.refactoredapp.databinding.ActivityTestRightBinding
|
||||||
|
import com.example.refactoredapp.util.MyViewModelFactory
|
||||||
|
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||||
|
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||||
|
|
||||||
|
class TestRightActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityTestRightBinding
|
||||||
|
private lateinit var viewModel: TestRightViewModel
|
||||||
|
|
||||||
|
private lateinit var mDriver: UsbSerialDriver
|
||||||
|
private var mConnection: UsbDeviceConnection? = null
|
||||||
|
lateinit var mService: UsbService
|
||||||
|
|
||||||
|
private val TAG = "TestRightActivity"
|
||||||
|
|
||||||
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
|
||||||
|
|
||||||
|
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||||
|
device?.apply {
|
||||||
|
connectUsb(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onUsbError("permission denied for device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val connection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
|
val binder = service as UsbService.UsbServiceBinder
|
||||||
|
mService = binder.getService()
|
||||||
|
viewModel.isServiceConnected = true
|
||||||
|
mConnection.let { mService.connect(mDriver, mConnection!!) }
|
||||||
|
moveToNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||||
|
viewModel.isServiceConnected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityTestRightBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
viewModel = ViewModelProvider(this, MyViewModelFactory(this.applicationContext))[TestRightViewModel::class.java]
|
||||||
|
|
||||||
|
connectUsb(false);
|
||||||
|
|
||||||
|
// if(connectUsb(false)) {
|
||||||
|
// moveToNext()
|
||||||
|
// } else {
|
||||||
|
// onBackPressed()
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectUsb(permissionGranted: Boolean) {
|
||||||
|
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
|
||||||
|
val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
|
||||||
|
|
||||||
|
if (availableDrivers.isEmpty()) {
|
||||||
|
onUsbError("No Device is Connected")
|
||||||
|
} else {
|
||||||
|
mDriver = availableDrivers[0]
|
||||||
|
mConnection = manager.openDevice(mDriver.device)
|
||||||
|
|
||||||
|
if (mConnection == null){
|
||||||
|
requestUserPermission(manager, mDriver.device)
|
||||||
|
} else {
|
||||||
|
setupService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Request user permission. The response will be received in the BroadcastReceiver
|
||||||
|
*/
|
||||||
|
private fun requestUserPermission(manager: UsbManager, device: UsbDevice) {
|
||||||
|
Log.d(TAG, String.format("requestUserPermissions", device.vendorId, device.productId))
|
||||||
|
|
||||||
|
val mPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
Intent(Constants.ACTION_USB_PERMISSION),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
val filter = IntentFilter(Constants.ACTION_USB_PERMISSION)
|
||||||
|
registerReceiver(broadcastReceiver, filter)
|
||||||
|
manager.requestPermission(device, mPendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupService() {
|
||||||
|
val intent = Intent(this, UsbService::class.java)
|
||||||
|
// startService(intent)
|
||||||
|
bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun moveToNext(){
|
||||||
|
// setupService()
|
||||||
|
if (supportFragmentManager.isDestroyed)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (!viewModel.isReferenceDone)
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(binding.flMain.id, TestRightExpReference()).commit()
|
||||||
|
else
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(binding.flMain.id, TestRightExpSample()).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if (viewModel.isServiceConnected) {
|
||||||
|
mService.disconnect()
|
||||||
|
unbindService(connection)
|
||||||
|
viewModel.isServiceConnected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUsbError(msg: String) {
|
||||||
|
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||||
|
if (!isFinishing)
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package com.example.refactoredapp.presentation.testRight
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.example.refactoredapp.R
|
||||||
|
import com.example.refactoredapp.data.DataHolder
|
||||||
|
import com.example.refactoredapp.data.model.TestRightCommands
|
||||||
|
import com.example.refactoredapp.databinding.FragmentTestRightExpReferenceBinding
|
||||||
|
import com.example.refactoredapp.presentation.UsbServiceListener
|
||||||
|
import com.example.refactoredapp.presentation.utils.MyDialogListener
|
||||||
|
import com.example.refactoredapp.presentation.utils.UIUtils
|
||||||
|
|
||||||
|
|
||||||
|
class TestRightExpReference : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentTestRightExpReferenceBinding
|
||||||
|
private val viewModel: TestRightViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private val TAG = "TestRightExpReference"
|
||||||
|
|
||||||
|
var fullString: String = ""
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
binding = FragmentTestRightExpReferenceBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupListeners()
|
||||||
|
|
||||||
|
if (DataHolder.isReferenceTaken)
|
||||||
|
parentFragmentManager.beginTransaction().replace(R.id.fl_main, TestRightExpSample())
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
binding.btnSetReference.setOnClickListener {
|
||||||
|
UIUtils().createAlertDialog(
|
||||||
|
requireContext(),
|
||||||
|
getString(R.string.have_you_placed),
|
||||||
|
getString(R.string.please_place),
|
||||||
|
getString(R.string.yes),
|
||||||
|
getString(R.string.no),
|
||||||
|
object : MyDialogListener {
|
||||||
|
override fun onClickBtnOne() {
|
||||||
|
startTakingReference()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickBtnTwo() {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
(activity as TestRightActivity).mService.addListener(
|
||||||
|
object : UsbServiceListener {
|
||||||
|
override fun onUsbConnect() {
|
||||||
|
Log.d(TAG, "onUsbConnect() called")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUsbConnectError(e: Exception?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUsbRead(data: ByteArray?) {
|
||||||
|
// Log.d(TAG, "onUsbRead(data) call back called")
|
||||||
|
data?.let {
|
||||||
|
val stringData = String(it)
|
||||||
|
// Log.d(TAG, stringData)
|
||||||
|
fullString += stringData
|
||||||
|
|
||||||
|
if(stringData.contains("OK")){
|
||||||
|
viewModel.mapValues(fullString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// val stringData = String(data!!)
|
||||||
|
// Log.d(TAG, stringData)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUsbRead() {
|
||||||
|
Log.d(TAG, "onSerialRead() call back called")
|
||||||
|
}
|
||||||
|
override fun onUsbIoError(e: Exception?) {
|
||||||
|
Log.e(TAG, "onUsbIoError() called -> $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.btnRead.setOnClickListener {
|
||||||
|
// Log.d(TAG, "clicked on text")
|
||||||
|
// val mService = (activity as TestRightActivity).mService
|
||||||
|
// mService.read()
|
||||||
|
Log.d(TAG, "FULL STRING IS -> $fullString")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startTakingReference() {
|
||||||
|
val mService = (activity as TestRightActivity).mService
|
||||||
|
mService.eventDrivenWrite(TestRightCommands.print_all)
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun startTakingReference() {
|
||||||
|
// val mService = (activity as TestRightActivity).mService
|
||||||
|
// mService.write(TestRightCommands.print)
|
||||||
|
//
|
||||||
|
// val handler = Handler()
|
||||||
|
// handler.postDelayed(Runnable { // Do something after 5s = 5000ms
|
||||||
|
// mService.read()
|
||||||
|
// }, 10000)
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
(activity as TestRightActivity).mService.removeListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun Button.disable() {
|
||||||
|
// background.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
|
||||||
|
// isClickable = false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun Button.enable() {
|
||||||
|
// background.setColorFilter(Color.BLUE, PorterDuff.Mode.MULTIPLY)
|
||||||
|
// isClickable = true
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.example.refactoredapp.presentation.TestRight
|
package com.example.refactoredapp.presentation.testRight
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -6,7 +6,6 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import com.example.refactoredapp.R
|
|
||||||
import com.example.refactoredapp.databinding.FragmentTestRightExpReferenceBinding
|
import com.example.refactoredapp.databinding.FragmentTestRightExpReferenceBinding
|
||||||
|
|
||||||
class TestRightExpSample : Fragment() {
|
class TestRightExpSample : Fragment() {
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
package com.example.refactoredapp.presentation.TestRight
|
package com.example.refactoredapp.presentation.testRight
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import com.example.refactoredapp.R
|
import com.example.refactoredapp.R
|
||||||
import com.example.refactoredapp.databinding.FragmentTestRightResultsBinding
|
import com.example.refactoredapp.databinding.FragmentTestRightResultsBinding
|
||||||
import com.example.refactoredapp.util.MyUtils
|
|
||||||
|
|
||||||
class TestRightResults : Fragment() {
|
class TestRightResults : Fragment() {
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.example.refactoredapp.presentation.testRight
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class TestRightViewModel : ViewModel() {
|
||||||
|
|
||||||
|
var isReferenceDone = false
|
||||||
|
var isServiceConnected = false
|
||||||
|
val isUsbConnected = MutableLiveData(false)
|
||||||
|
|
||||||
|
fun mapValues(fullString: String) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.example.refactoredapp.presentation.testRight
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.hardware.usb.UsbDeviceConnection
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import com.example.refactoredapp.data.Constants
|
||||||
|
import com.example.refactoredapp.data.model.TestRightCommands
|
||||||
|
import com.example.refactoredapp.presentation.UsbServiceListener
|
||||||
|
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||||
|
import com.hoho.android.usbserial.driver.UsbSerialPort
|
||||||
|
import com.hoho.android.usbserial.util.SerialInputOutputManager
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
|
class UsbService : Service() {
|
||||||
|
|
||||||
|
// Binder to be given to clients
|
||||||
|
private val binder = UsbServiceBinder()
|
||||||
|
|
||||||
|
private lateinit var mPort: UsbSerialPort
|
||||||
|
|
||||||
|
private var listener: UsbServiceListener? = null
|
||||||
|
|
||||||
|
private var isUsbConnected = false
|
||||||
|
|
||||||
|
private val TAG = "UsbService"
|
||||||
|
|
||||||
|
inner class UsbServiceBinder : Binder() {
|
||||||
|
// Return this instance of UsbService so clients can call public methods
|
||||||
|
fun getService(): UsbService = this@UsbService
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
Log.d("surya", "onBind() called")
|
||||||
|
return binder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect(driver: UsbSerialDriver, connection: UsbDeviceConnection) {
|
||||||
|
|
||||||
|
mPort = driver.ports[0] // Most devices have just one port (port 0)
|
||||||
|
mPort.open(connection)
|
||||||
|
mPort.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
|
||||||
|
|
||||||
|
isUsbConnected = true
|
||||||
|
Log.d(TAG, "My Usb Connected ${mPort.driver}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
if(isUsbConnected){
|
||||||
|
mPort.close()
|
||||||
|
isUsbConnected = false;
|
||||||
|
Log.d(TAG, "My Usb disconnected:: ${mPort.driver}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addListener(listener: UsbServiceListener){
|
||||||
|
this.listener = listener
|
||||||
|
if(isUsbConnected)
|
||||||
|
listener.onUsbConnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeListener(){
|
||||||
|
this.listener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun eventDrivenWrite(command: TestRightCommands) {
|
||||||
|
val usbIoManager = SerialInputOutputManager(mPort,
|
||||||
|
object : SerialInputOutputManager.Listener{
|
||||||
|
override fun onNewData(data: ByteArray?) {
|
||||||
|
listener?.onUsbRead(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRunError(e: Exception?) {
|
||||||
|
Log.e(TAG, "onRunError() called inside eventDrivenWrite()")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
usbIoManager.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
mPort.write(command.command.toByteArray(), Constants.WRITE_TIMEOUT_MILLIS)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
listener?.onUsbIoError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(command: TestRightCommands){
|
||||||
|
mPort.write(command.command.toByteArray(), Constants.WRITE_TIMEOUT_MILLIS)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(){
|
||||||
|
val byteArray = ByteArray(3700)
|
||||||
|
val len = mPort.read(byteArray, Constants.READ_TIMEOUT_MILLIS)
|
||||||
|
|
||||||
|
Log.d(TAG, "length is $len")
|
||||||
|
val byteArrayCropped = ByteArray(len)
|
||||||
|
System.arraycopy(byteArray, 0, byteArrayCropped, 0, len)
|
||||||
|
|
||||||
|
listener?.onUsbRead(byteArrayCropped)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example.refactoredapp.presentation.utils
|
||||||
|
|
||||||
|
interface MyDialogListener {
|
||||||
|
fun onClickBtnOne()
|
||||||
|
fun onClickBtnTwo()
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.example.refactoredapp.presentation.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
||||||
|
class UIUtils {
|
||||||
|
|
||||||
|
fun createAlertDialog(context: Context, title: String, subtitle: String, btnOneText: String, btnTwoText: String, listener: MyDialogListener){
|
||||||
|
MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(subtitle)
|
||||||
|
.setNegativeButton(btnOneText) { _, _ ->
|
||||||
|
listener.onClickBtnOne()
|
||||||
|
}
|
||||||
|
.setPositiveButton(btnTwoText) { _, _ ->
|
||||||
|
listener.onClickBtnTwo()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,13 +3,10 @@ package com.example.refactoredapp.util
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.example.refactoredapp.data.RepositoryImpl
|
|
||||||
import com.example.refactoredapp.domain.ParseDenovixUseCase
|
import com.example.refactoredapp.domain.ParseDenovixUseCase
|
||||||
import com.example.refactoredapp.domain.ParseTestRightCSVUseCase
|
import com.example.refactoredapp.domain.ParseTestRightCSVUseCase
|
||||||
import com.example.refactoredapp.presentation.MainActivity
|
|
||||||
import com.example.refactoredapp.presentation.MainViewModel
|
import com.example.refactoredapp.presentation.MainViewModel
|
||||||
import com.example.refactoredapp.presentation.TestRight.TestRightActivity
|
import com.example.refactoredapp.presentation.testRight.TestRightViewModel
|
||||||
import com.example.refactoredapp.presentation.TestRight.TestRightViewModel
|
|
||||||
|
|
||||||
class MyViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
|
class MyViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
android:id="@+id/fl_main"
|
android:id="@+id/fl_main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".presentation.TestRight.TestRightActivity">
|
tools:context=".presentation.testRight.TestRightActivity">
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:context=".presentation.TestRight.TestRightExpReference">
|
tools:context=".presentation.testRight.TestRightExpReference">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -23,9 +23,21 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/set_reference"
|
android:text="@string/set_reference"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
|
android:clickable="false"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tv_title"
|
app:layout_constraintTop_toBottomOf="@id/tv_title"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_read"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/read"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:clickable="false"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/btn_set_reference"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
tools:context=".presentation.testRight.TestRightExpSample">
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".presentation.TestRight.TestRightExpSample">
|
|
||||||
|
|
||||||
<!-- TODO: Update blank fragment layout -->
|
<!-- TODO: Update blank fragment layout -->
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
tools:context=".presentation.testRight.TestRightResults">
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".presentation.TestRight.TestRightResults">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.RefactoredApp" parent="Theme.MaterialComponents.Light.DarkActionBar">
|
<style name="Theme.RefactoredApp" parent="Theme.MaterialComponents.Light.DarkActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
|
||||||
|
<!-- <item name="colorPrimary">@color/purple_200</item>-->
|
||||||
|
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
<item name="colorOnPrimary">@color/black</item>
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
<!-- Secondary brand color. -->
|
<!-- Secondary brand color. -->
|
||||||
|
|||||||
@@ -5,8 +5,17 @@
|
|||||||
<item>TestRight</item>
|
<item>TestRight</item>
|
||||||
<item>Denovix</item>
|
<item>Denovix</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<!-- TODO: Remove or change this placeholder text -->
|
|
||||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
<string name="select_the_test">Select the Test</string>
|
<string name="select_the_test">Select the Test</string>
|
||||||
<string name="set_reference">Set Reference</string>
|
<string name="set_reference">Set Reference</string>
|
||||||
|
|
||||||
|
<!-- Alert Dialog -->
|
||||||
|
<string name="have_you_placed">Have you placed the cuvette containing reference sample?</string>
|
||||||
|
<string name="please_place">Please place the cuvette containing sample for reference before moving forward</string>
|
||||||
|
<string name="yes">Yes</string>
|
||||||
|
<string name="no">No</string>
|
||||||
|
<string name="connect">Connect</string>
|
||||||
|
<string name="connected">Connected</string>
|
||||||
|
<string name="read">Read</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:windowFullscreen">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="title1">
|
<style name="title1">
|
||||||
<item name="android:textSize">20.5sp</item>
|
<item name="android:textSize">20.5sp</item>
|
||||||
<item name="android:letterSpacing">-0.01</item>
|
<item name="android:letterSpacing">-0.01</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user