SettingsFragment.kt 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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.TAG
  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.service.ModifierService.Companion.instance
  37. import com.example.modifier.utils.genICCID
  38. import com.example.modifier.utils.genIMEI
  39. import com.example.modifier.utils.uniqueId
  40. import com.google.android.material.dialog.MaterialAlertDialogBuilder
  41. import io.ktor.client.call.body
  42. import io.ktor.client.plugins.resources.put
  43. import io.ktor.client.request.setBody
  44. import io.ktor.client.statement.HttpResponse
  45. import io.ktor.client.statement.bodyAsText
  46. import io.ktor.http.ContentType
  47. import io.ktor.http.contentType
  48. import kotlinx.coroutines.CoroutineScope
  49. import kotlinx.coroutines.Dispatchers
  50. import kotlinx.coroutines.delay
  51. import kotlinx.coroutines.flow.StateFlow
  52. import kotlinx.coroutines.launch
  53. import kotlinx.coroutines.withContext
  54. import org.apache.commons.lang3.RandomStringUtils
  55. import org.apache.commons.lang3.StringUtils
  56. import org.apache.commons.validator.routines.UrlValidator
  57. import java.util.Objects
  58. import java.util.Optional
  59. @SuppressLint("SetTextI18n", "MissingPermission", "HardwareIds", "NewApi")
  60. class SettingsFragment : Fragment() {
  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(com.example.modifier.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. }