package com.example.modifier.ui.settings import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.content.DialogInterface import android.content.pm.PackageManager import android.os.Bundle import android.os.Handler import android.os.Looper import android.telephony.SmsManager import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.text.Editable import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.example.modifier.Global import com.example.modifier.Global.save import com.example.modifier.Global.saveServer import com.example.modifier.Global.servers import com.example.modifier.R import com.example.modifier.Utils import com.example.modifier.databinding.FragmentSettingsBinding import com.example.modifier.http.KtorClient import com.example.modifier.http.api.RcsNumberApi import com.example.modifier.http.request.RcsNumberRequest import com.example.modifier.http.response.RcsNumberResponse import com.example.modifier.model.TelephonyConfig import com.example.modifier.service.ModifierService import com.example.modifier.service.ModifierService.Companion import com.example.modifier.service.ModifierService.Companion.instance import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.ktor.client.call.body import io.ktor.client.plugins.resources.put import io.ktor.client.plugins.retry import io.ktor.client.request.setBody import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.contentType import io.ktor.http.isSuccess import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeoutOrNull import org.apache.commons.lang3.RandomStringUtils import org.apache.commons.lang3.StringUtils import org.apache.commons.validator.routines.UrlValidator import java.util.Objects import java.util.Optional @SuppressLint("SetTextI18n", "MissingPermission", "HardwareIds", "NewApi") class SettingsFragment : Fragment() { private val TAG = "SettingsFragment" private lateinit var binding: FragmentSettingsBinding var handler: Handler = Handler(Looper.getMainLooper()) lateinit var requestPermissionLauncher: ActivityResultLauncher init { Log.i("SettingsFragment", "SettingsFragment") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { } else { // Explain to the user that the feature is unavailable because the // feature requires a permission that the user has denied. At the // same time, respect the user's decision. Don't link to system // settings in an effort to convince the user to change their // decision. } } } override fun onResume() { super.onResume() lifecycleScope.launch { loadConfigs() } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { if (this::binding.isInitialized) { return binding.root } binding = FragmentSettingsBinding.inflate(inflater, container, false) binding.tlIccid.setEndIconOnClickListener { if (binding.etMcc.text.isNullOrEmpty() || binding.etMnc.text.isNullOrEmpty() || binding.etAreaCode.text.isNullOrEmpty()) { Toast.makeText(context, "MNC and Code required", Toast.LENGTH_SHORT).show() return@setEndIconOnClickListener } binding.etIccid.setText( Global.genICCID( binding.etMnc.text.toString(), binding.etAreaCode.text.toString() ) ) } binding.tlImsi.setEndIconOnClickListener { val mcc = Optional.ofNullable( binding.etMcc.text ).map { o: Editable? -> Objects.toString(o) }.orElse("") val mnc = Optional.ofNullable(binding.etMnc.text) .map { o: Editable? -> Objects.toString(o) } .orElse("") if (StringUtils.isEmpty(mcc) || StringUtils.isEmpty(mnc)) { Toast.makeText(context, "MCC and MNC are required", Toast.LENGTH_SHORT).show() return@setEndIconOnClickListener } binding.etImsi.setText(mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length)) } binding.tlImei.setEndIconOnClickListener { binding.etImei.setText(Utils.generateIMEI()) } binding.btnSave.setOnClickListener { onSave() } binding.etServer.threshold = 1000 binding.btnServer.setOnClickListener { v: View? -> val server = binding.etServer.text.toString() if (StringUtils.isEmpty(server)) { Toast.makeText(context, "Server is required", Toast.LENGTH_SHORT).show() return@setOnClickListener } if (!UrlValidator(arrayOf("http", "https")).isValid(server)) { Toast.makeText(context, "Invalid server URL", Toast.LENGTH_SHORT).show() return@setOnClickListener } saveServer(server, binding.etDeviceLabel.text.toString()) binding.etServer.setSimpleItems(servers.toTypedArray()) val modifierService = instance modifierService?.connect() lifecycleScope.launch { Utils.makeLoadingButton(context, binding.btnServer) binding.btnServer.isEnabled = false delay(500) binding.btnServer.setIconResource(R.drawable.ic_done) binding.btnServer.text = "OK" delay(500) binding.btnServer.isEnabled = true binding.btnServer.icon = null binding.btnServer.text = "Save" } } binding.btnRequest.setOnClickListener { v: View? -> lifecycleScope.launch { Utils.makeLoadingButton(context, binding.btnRequest) binding.btnRequest.isEnabled = false withContext(Dispatchers.IO) req@{ val response: HttpResponse try { response = KtorClient.put( RcsNumberApi() ) { contentType(ContentType.Application.Json) setBody( RcsNumberRequest( deviceId = Utils.getUniqueID() ) ) } } catch (e: Exception) { withContext(Dispatchers.Main) { MaterialAlertDialogBuilder(requireContext()) .setTitle("Error") .setMessage(e.message) .setPositiveButton("OK") { dialog: DialogInterface, which: Int -> dialog.dismiss() } .show() binding.btnRequest.isEnabled = true binding.btnRequest.text = "Request" binding.btnRequest.icon = null } return@req } Log.i(TAG, "response: ${response.bodyAsText()}") val res = response.body() val number = res.number val mcc = res.mcc val mnc = res.mnc val country = res.country val areaCode = res.areaCode val iccid = Global.genICCID(mnc, areaCode) val imsi = mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length) val imei = Utils.generateIMEI() save( TelephonyConfig( number, mcc, mnc, iccid, imsi, imei, country, areaCode, false, res.carrierId, res.carrierName ) ) loadConfigs() } binding.btnRequest.icon = null binding.btnRequest.isEnabled = true binding.btnRequest.text = "Request" } } val prefs = requireContext().getSharedPreferences("settings", Context.MODE_PRIVATE) binding.switchClean.isChecked = prefs.getBoolean("do_not_clean", false) binding.switchClean.setOnCheckedChangeListener { buttonView, isChecked -> prefs.edit().putBoolean("do_not_clean", isChecked).apply() } binding.switchRequest.isChecked = prefs.getBoolean("do_not_request", false) binding.switchRequest.setOnCheckedChangeListener { buttonView, isChecked -> prefs.edit().putBoolean("do_not_request", isChecked).apply() } binding.swReset.isChecked = prefs.getBoolean("do_not_reset", false) binding.swReset.setOnCheckedChangeListener { buttonView, isChecked -> prefs.edit().putBoolean("do_not_reset", isChecked).apply() } binding.btnCheck.setOnClickListener { when { ContextCompat.checkSelfPermission( requireContext(), Manifest.permission.READ_PHONE_STATE ) == PackageManager.PERMISSION_GRANTED -> { val telephonyManager = requireContext().getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager var res = """IMEI: ${telephonyManager.imei} |IMSI: ${telephonyManager.subscriberId} |ICCID: ${telephonyManager.simSerialNumber} |Phone Number: ${telephonyManager.line1Number} |Network Operator: ${telephonyManager.networkOperator}, ${telephonyManager.networkCountryIso}, ${telephonyManager.networkOperatorName} |Sim Operator: ${telephonyManager.simOperator}, ${telephonyManager.simCountryIso}, ${telephonyManager.simOperatorName} |Sim state: ${telephonyManager.simState} |simCarrierId: ${telephonyManager.simCarrierId} |carrierIdFromSimMccMnc: ${telephonyManager.carrierIdFromSimMccMnc} |simSpecificCarrierId: ${telephonyManager.simSpecificCarrierId} |DefaultSmsSubscriptionId: ${SmsManager.getDefaultSmsSubscriptionId()} | """.trimMargin() val subscriptionManager: SubscriptionManager = requireContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager val simCount = subscriptionManager.activeSubscriptionInfoCountMax for (i in 0 until simCount) { val info = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i) ?: continue res += """ |--- Slot:$i Subscription ID: ${info.subscriptionId} --- |ICCID: ${info.iccId} |Number: ${subscriptionManager.getPhoneNumber(info.subscriptionId)} |Country: ${info.countryIso} |MCC: ${info.mccString} |MNC: ${info.mncString} |carrierId: ${info.carrierId} |roaming: ${info.dataRoaming} """.trimMargin() } MaterialAlertDialogBuilder(requireContext()) .setTitle("Info") .setMessage(res) .setPositiveButton("OK") { dialog: DialogInterface, which: Int -> dialog.dismiss() } .show() } ActivityCompat.shouldShowRequestPermissionRationale( requireActivity(), Manifest.permission.READ_PHONE_STATE ) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user continue // using your app without granting the permission. } else -> { // You can directly ask for the permission. // The registered ActivityResultCallback gets the result of this request. requestPermissionLauncher.launch( Manifest.permission.READ_PHONE_STATE ) } } } lifecycleScope.launch { loadConfigs() try { delay(5000) val req = RcsNumberRequest( deviceId = Utils.getUniqueID() ) val response = KtorClient.put( RcsNumberApi() ) { contentType(ContentType.Application.Json) setBody(req) } var rcsNumber = response.body() Log.i("xxx", "requestNumber response: $rcsNumber") } catch (e: Exception) { Log.e("xxx", "requestNumber error: $e") } } return binding.root } private suspend fun loadConfigs() { withContext(Dispatchers.IO) { Global.load() withContext(Dispatchers.Main) { binding.etServer.setText(Global.serverUrl) binding.etServer.setSimpleItems(servers.toTypedArray()) binding.etDeviceLabel.setText(Global.name) val telephonyConfig = Global.telephonyConfig binding.etNumber.setText(telephonyConfig.number) binding.etMcc.setText(telephonyConfig.mcc) binding.etMnc.setText(telephonyConfig.mnc) binding.etIccid.setText(telephonyConfig.iccid) binding.etImsi.setText(telephonyConfig.imsi) binding.etImei.setText(telephonyConfig.imei) binding.etCountry.setText(telephonyConfig.country) binding.etAreaCode.setText(telephonyConfig.areaCode) binding.etCarrierId.setText(telephonyConfig.carrierId) binding.etCarrierName.setText(telephonyConfig.carrierName) } } } private fun onSave() { Utils.makeLoadingButton(context, binding.btnSave) binding.btnSave.isEnabled = false lifecycleScope.launch { withContext(Dispatchers.IO) { save( TelephonyConfig( binding.etNumber.text.toString(), binding.etMcc.text.toString(), binding.etMnc.text.toString(), binding.etIccid.text.toString(), binding.etImsi.text.toString(), binding.etImei.text.toString(), binding.etCountry.text.toString(), binding.etAreaCode.text.toString(), false, binding.etCarrierId.text.toString(), binding.etCarrierName.text.toString() ) ) } binding.btnSave.setIconResource(R.drawable.ic_done) binding.btnSave.text = "OK" delay(1500) binding.btnSave.isEnabled = true binding.btnSave.icon = null binding.btnSave.text = "Save" } } }