SettingsFragment.kt 17 KB

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