SettingsFragment.kt 18 KB

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