SettingsFragment.kt 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package com.example.modifier.ui.settings
  2. import android.Manifest
  3. import android.annotation.SuppressLint
  4. import android.content.Context
  5. import android.content.DialogInterface
  6. import android.content.pm.PackageManager
  7. import android.os.Bundle
  8. import android.telephony.SmsManager
  9. import android.telephony.SubscriptionManager
  10. import android.telephony.TelephonyManager
  11. import android.text.Editable
  12. import android.util.Log
  13. import android.view.LayoutInflater
  14. import android.view.View
  15. import android.view.ViewGroup
  16. import android.widget.Toast
  17. import androidx.activity.result.ActivityResultLauncher
  18. import androidx.activity.result.contract.ActivityResultContracts
  19. import androidx.core.app.ActivityCompat
  20. import androidx.core.content.ContextCompat
  21. import androidx.fragment.app.Fragment
  22. import androidx.lifecycle.lifecycleScope
  23. import com.example.modifier.R
  24. import com.example.modifier.baseTag
  25. import com.example.modifier.Utils
  26. import com.example.modifier.constants.servers
  27. import com.example.modifier.repo.AppPrefsRepo
  28. import com.example.modifier.databinding.FragmentSettingsBinding
  29. import com.example.modifier.http.ktorClient
  30. import com.example.modifier.http.api.RcsNumberApi
  31. import com.example.modifier.http.request.RcsNumberRequest
  32. import com.example.modifier.http.response.RcsNumberResponse
  33. import com.example.modifier.model.SpoofedSimInfo
  34. import com.example.modifier.repo.SpoofedSimInfoRepo
  35. import com.example.modifier.utils.genICCID
  36. import com.example.modifier.utils.genIMEI
  37. import com.google.android.material.dialog.MaterialAlertDialogBuilder
  38. import io.ktor.client.call.body
  39. import io.ktor.client.plugins.resources.put
  40. import io.ktor.client.request.setBody
  41. import io.ktor.client.statement.HttpResponse
  42. import io.ktor.client.statement.bodyAsText
  43. import io.ktor.http.ContentType
  44. import io.ktor.http.contentType
  45. import kotlinx.coroutines.CoroutineScope
  46. import kotlinx.coroutines.Dispatchers
  47. import kotlinx.coroutines.delay
  48. import kotlinx.coroutines.flow.StateFlow
  49. import kotlinx.coroutines.launch
  50. import kotlinx.coroutines.withContext
  51. import org.apache.commons.lang3.RandomStringUtils
  52. import org.apache.commons.lang3.StringUtils
  53. import org.apache.commons.validator.routines.UrlValidator
  54. import java.util.Objects
  55. import java.util.Optional
  56. @SuppressLint("SetTextI18n", "MissingPermission", "HardwareIds", "NewApi")
  57. class SettingsFragment : Fragment() {
  58. private val tag = "$baseTag/SettingsFragment"
  59. private lateinit var binding: FragmentSettingsBinding
  60. private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
  61. private val appPrefsRepo = AppPrefsRepo.instance
  62. private val spoofedSimInfoRepo = SpoofedSimInfoRepo.instance
  63. init {
  64. Log.i("SettingsFragment", "SettingsFragment")
  65. }
  66. override fun onCreate(savedInstanceState: Bundle?) {
  67. super.onCreate(savedInstanceState)
  68. requestPermissionLauncher =
  69. registerForActivityResult(
  70. ActivityResultContracts.RequestPermission()
  71. ) { isGranted: Boolean ->
  72. if (isGranted) {
  73. } else {
  74. // Explain to the user that the feature is unavailable because the
  75. // feature requires a permission that the user has denied. At the
  76. // same time, respect the user's decision. Don't link to system
  77. // settings in an effort to convince the user to change their
  78. // decision.
  79. }
  80. }
  81. Log.i(tag, "SettingsFragment.onCreate")
  82. }
  83. override fun onCreateView(
  84. inflater: LayoutInflater, container: ViewGroup?,
  85. savedInstanceState: Bundle?
  86. ): View {
  87. Log.i(tag, "SettingsFragment.onCreateView")
  88. if (!this::binding.isInitialized) {
  89. binding = FragmentSettingsBinding.inflate(inflater, container, false)
  90. binding.tlIccid.setEndIconOnClickListener {
  91. if (binding.etMcc.text.isNullOrEmpty() || binding.etMnc.text.isNullOrEmpty() || binding.etAreaCode.text.isNullOrEmpty()) {
  92. Toast.makeText(context, "MNC and Code required", Toast.LENGTH_SHORT).show()
  93. return@setEndIconOnClickListener
  94. }
  95. binding.etIccid.setText(
  96. genICCID(
  97. binding.etMnc.text.toString(),
  98. binding.etAreaCode.text.toString()
  99. )
  100. )
  101. }
  102. binding.tlImsi.setEndIconOnClickListener {
  103. val mcc = Optional.ofNullable(
  104. binding.etMcc.text
  105. ).map { o: Editable? -> Objects.toString(o) }.orElse("")
  106. val mnc = Optional.ofNullable(binding.etMnc.text)
  107. .map { o: Editable? -> Objects.toString(o) }
  108. .orElse("")
  109. if (StringUtils.isEmpty(mcc) || StringUtils.isEmpty(mnc)) {
  110. Toast.makeText(context, "MCC and MNC are required", Toast.LENGTH_SHORT).show()
  111. return@setEndIconOnClickListener
  112. }
  113. binding.etImsi.setText(mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length))
  114. }
  115. binding.tlImei.setEndIconOnClickListener {
  116. binding.etImei.setText(genIMEI())
  117. }
  118. binding.btnSave.setOnClickListener {
  119. onSave()
  120. }
  121. binding.etServer.threshold = 1000
  122. binding.etServer.setSimpleItems(servers)
  123. binding.btnServer.setOnClickListener { v: View? ->
  124. val server = binding.etServer.text.toString()
  125. if (StringUtils.isEmpty(server)) {
  126. Toast.makeText(context, "Server is required", Toast.LENGTH_SHORT).show()
  127. return@setOnClickListener
  128. }
  129. if (!UrlValidator(arrayOf("http", "https")).isValid(server)) {
  130. Toast.makeText(context, "Invalid server URL", Toast.LENGTH_SHORT).show()
  131. return@setOnClickListener
  132. }
  133. lifecycleScope.launch {
  134. appPrefsRepo.updateServer(server)
  135. appPrefsRepo.updateName(binding.etDeviceLabel.text.toString())
  136. Utils.makeLoadingButton(context, binding.btnServer)
  137. binding.btnServer.isEnabled = false
  138. delay(500)
  139. binding.btnServer.setIconResource(R.drawable.ic_done)
  140. binding.btnServer.text = "OK"
  141. delay(500)
  142. binding.btnServer.isEnabled = true
  143. binding.btnServer.icon = null
  144. binding.btnServer.text = "Save"
  145. }
  146. }
  147. binding.btnRequest.setOnClickListener { v: View? ->
  148. lifecycleScope.launch {
  149. Utils.makeLoadingButton(context, binding.btnRequest)
  150. binding.btnRequest.isEnabled = false
  151. withContext(Dispatchers.IO) req@{
  152. val response: HttpResponse
  153. try {
  154. response = ktorClient.put(
  155. RcsNumberApi()
  156. ) {
  157. contentType(ContentType.Application.Json)
  158. setBody(RcsNumberRequest(deviceId = appPrefsRepo.appPrefs.value.id))
  159. }
  160. } catch (e: Exception) {
  161. withContext(Dispatchers.Main) {
  162. MaterialAlertDialogBuilder(requireContext())
  163. .setTitle("Error")
  164. .setMessage(e.message)
  165. .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
  166. dialog.dismiss()
  167. }
  168. .show()
  169. binding.btnRequest.isEnabled = true
  170. binding.btnRequest.text = "Request"
  171. binding.btnRequest.icon = null
  172. }
  173. return@req
  174. }
  175. Log.i(tag, "response: ${response.bodyAsText()}")
  176. val res = response.body<RcsNumberResponse>()
  177. val number = res.number
  178. val mcc = res.mcc
  179. val mnc = res.mnc
  180. val country = res.country
  181. val areaCode = res.areaCode
  182. val iccid = genICCID(mnc, areaCode)
  183. val imsi =
  184. mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length)
  185. val imei = genIMEI()
  186. spoofedSimInfoRepo.updateSpoofedSimInfo(
  187. SpoofedSimInfo(
  188. numberId = res.id,
  189. number = number,
  190. mcc = mcc,
  191. mnc = mnc,
  192. iccid = iccid,
  193. imsi = imsi,
  194. imei = imei,
  195. country = country,
  196. areaCode = areaCode,
  197. available = false,
  198. carrierId = res.carrierId,
  199. carrierName = res.carrierName
  200. )
  201. )
  202. }
  203. binding.btnRequest.icon = null
  204. binding.btnRequest.isEnabled = true
  205. binding.btnRequest.text = "Request"
  206. }
  207. }
  208. binding.switchClean.setOnCheckedChangeListener { buttonView, isChecked ->
  209. CoroutineScope(Dispatchers.IO).launch {
  210. appPrefsRepo.updatePreventClean(isChecked)
  211. }
  212. }
  213. binding.switchRequest.setOnCheckedChangeListener { buttonView, isChecked ->
  214. CoroutineScope(Dispatchers.IO).launch {
  215. appPrefsRepo.updatePreventRequest(isChecked)
  216. }
  217. }
  218. binding.swReset.setOnCheckedChangeListener { buttonView, isChecked ->
  219. CoroutineScope(Dispatchers.IO).launch {
  220. appPrefsRepo.updatePreventReset(isChecked)
  221. }
  222. }
  223. binding.btnCheck.setOnClickListener {
  224. when {
  225. ContextCompat.checkSelfPermission(
  226. requireContext(),
  227. Manifest.permission.READ_PHONE_STATE
  228. ) == PackageManager.PERMISSION_GRANTED -> {
  229. val telephonyManager =
  230. requireContext().getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
  231. var res = """IMEI: ${telephonyManager.imei}
  232. |IMSI: ${telephonyManager.subscriberId}
  233. |ICCID: ${telephonyManager.simSerialNumber}
  234. |Phone Number: ${telephonyManager.line1Number}
  235. |Network Operator: ${telephonyManager.networkOperator}, ${telephonyManager.networkCountryIso}, ${telephonyManager.networkOperatorName}
  236. |Sim Operator: ${telephonyManager.simOperator}, ${telephonyManager.simCountryIso}, ${telephonyManager.simOperatorName}
  237. |Sim state: ${telephonyManager.simState}
  238. |simCarrierId: ${telephonyManager.simCarrierId}
  239. |carrierIdFromSimMccMnc: ${telephonyManager.carrierIdFromSimMccMnc}
  240. |simSpecificCarrierId: ${telephonyManager.simSpecificCarrierId}
  241. |DefaultSmsSubscriptionId: ${SmsManager.getDefaultSmsSubscriptionId()}
  242. |
  243. """.trimMargin()
  244. val subscriptionManager: SubscriptionManager =
  245. requireContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
  246. val simCount = subscriptionManager.activeSubscriptionInfoCountMax
  247. for (i in 0 until simCount) {
  248. val info =
  249. subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i)
  250. ?: continue
  251. res += """
  252. |--- Slot:$i Subscription ID: ${info.subscriptionId} ---
  253. |ICCID: ${info.iccId}
  254. |Number: ${subscriptionManager.getPhoneNumber(info.subscriptionId)}
  255. |Country: ${info.countryIso}
  256. |MCC: ${info.mccString}
  257. |MNC: ${info.mncString}
  258. |carrierId: ${info.carrierId}
  259. |roaming: ${info.dataRoaming}
  260. """.trimMargin()
  261. }
  262. MaterialAlertDialogBuilder(requireContext())
  263. .setTitle("Info")
  264. .setMessage(res)
  265. .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
  266. dialog.dismiss()
  267. }
  268. .show()
  269. }
  270. ActivityCompat.shouldShowRequestPermissionRationale(
  271. requireActivity(), Manifest.permission.READ_PHONE_STATE
  272. ) -> {
  273. // In an educational UI, explain to the user why your app requires this
  274. // permission for a specific feature to behave as expected, and what
  275. // features are disabled if it's declined. In this UI, include a
  276. // "cancel" or "no thanks" button that lets the user continue
  277. // using your app without granting the permission.
  278. }
  279. else -> {
  280. // You can directly ask for the permission.
  281. // The registered ActivityResultCallback gets the result of this request.
  282. requestPermissionLauncher.launch(
  283. Manifest.permission.READ_PHONE_STATE
  284. )
  285. }
  286. }
  287. }
  288. }
  289. viewLifecycleOwner.lifecycleScope.launch {
  290. launch {
  291. appPrefsRepo.appPrefs.collect {
  292. Log.i(tag, "appPreferencesRepository.collect")
  293. binding.etServer.setText(it.server)
  294. binding.etDeviceLabel.setText(it.name)
  295. binding.switchClean.isChecked = it.preventClean
  296. binding.switchRequest.isChecked = it.preventRequest
  297. binding.swReset.isChecked = it.preventReset
  298. }
  299. }
  300. launch {
  301. spoofedSimInfoRepo.spoofedSimInfo.collect {
  302. Log.i(tag, "spoofedSimInfo.collect")
  303. binding.etNumber.setText(it.number)
  304. binding.etMcc.setText(it.mcc)
  305. binding.etMnc.setText(it.mnc)
  306. binding.etIccid.setText(it.iccid)
  307. binding.etImsi.setText(it.imsi)
  308. binding.etImei.setText(it.imei)
  309. binding.etCountry.setText(it.country)
  310. binding.etAreaCode.setText(it.areaCode)
  311. binding.etCarrierId.setText(it.carrierId)
  312. binding.etCarrierName.setText(it.carrierName)
  313. }
  314. }
  315. }
  316. return binding.root
  317. }
  318. private fun onSave() {
  319. Utils.makeLoadingButton(context, binding.btnSave)
  320. binding.btnSave.isEnabled = false
  321. lifecycleScope.launch {
  322. withContext(Dispatchers.IO) {
  323. spoofedSimInfoRepo.updateSpoofedSimInfo(
  324. SpoofedSimInfo(
  325. 0,
  326. binding.etNumber.text.toString(),
  327. binding.etMcc.text.toString(),
  328. binding.etMnc.text.toString(),
  329. binding.etIccid.text.toString(),
  330. binding.etImsi.text.toString(),
  331. binding.etImei.text.toString(),
  332. binding.etCountry.text.toString(),
  333. binding.etAreaCode.text.toString(),
  334. false,
  335. binding.etCarrierId.text.toString(),
  336. binding.etCarrierName.text.toString()
  337. )
  338. )
  339. }
  340. binding.btnSave.setIconResource(R.drawable.ic_done)
  341. binding.btnSave.text = "OK"
  342. delay(1500)
  343. binding.btnSave.isEnabled = true
  344. binding.btnSave.icon = null
  345. binding.btnSave.text = "Save"
  346. }
  347. }
  348. }