SettingsFragment.kt 17 KB

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