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.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.R import com.example.modifier.TAG import com.example.modifier.Utils import com.example.modifier.constants.servers import com.example.modifier.data.AppPreferences import com.example.modifier.repo.AppPreferencesRepository 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.SpoofedSimInfo import com.example.modifier.repo.SpoofedSimInfoRepository import com.example.modifier.service.ModifierService.Companion.instance import com.example.modifier.utils.genICCID import com.example.modifier.utils.genIMEI import com.example.modifier.utils.uniqueId import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.ktor.client.call.body import io.ktor.client.plugins.resources.put 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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext 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 lateinit var binding: FragmentSettingsBinding private lateinit var requestPermissionLauncher: ActivityResultLauncher private val appPreferencesRepository: AppPreferencesRepository by lazy { AppPreferencesRepository(requireContext()) } private lateinit var appPreferences: StateFlow private val spoofedSimInfoRepository by lazy { SpoofedSimInfoRepository(requireContext()) } private lateinit var spoofedSimInfo: StateFlow 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. } } Log.i(TAG, "SettingsFragment.onCreate") } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { Log.i(TAG, "SettingsFragment.onCreateView") if (!this::binding.isInitialized) { 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( 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(genIMEI()) } binding.btnSave.setOnClickListener { onSave() } binding.etServer.threshold = 1000 binding.etServer.setSimpleItems(servers) 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 } lifecycleScope.launch { appPreferencesRepository.updateServer(server) appPreferencesRepository.updateName(binding.etDeviceLabel.text.toString()) 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(appPreferences.value.server).put( RcsNumberApi() ) { contentType(ContentType.Application.Json) setBody(RcsNumberRequest(deviceId = uniqueId)) } } 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(com.example.modifier.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 = genICCID(mnc, areaCode) val imsi = mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length) val imei = genIMEI() spoofedSimInfoRepository.updateSpoofedSimInfo( SpoofedSimInfo( number, mcc, mnc, iccid, imsi, imei, country, areaCode, false, res.carrierId, res.carrierName ) ) } binding.btnRequest.icon = null binding.btnRequest.isEnabled = true binding.btnRequest.text = "Request" } } binding.switchClean.setOnCheckedChangeListener { buttonView, isChecked -> CoroutineScope(Dispatchers.IO).launch { appPreferencesRepository.updatePreventClean(isChecked) } } binding.switchRequest.setOnCheckedChangeListener { buttonView, isChecked -> CoroutineScope(Dispatchers.IO).launch { appPreferencesRepository.updatePreventRequest(isChecked) } } binding.swReset.setOnCheckedChangeListener { buttonView, isChecked -> CoroutineScope(Dispatchers.IO).launch { appPreferencesRepository.updatePreventReset(isChecked) } } 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 ) } } } } viewLifecycleOwner.lifecycleScope.launch { appPreferences = appPreferencesRepository.stateFlow() spoofedSimInfo = spoofedSimInfoRepository.stateFlow() launch { appPreferencesRepository.stateFlow().collect { Log.i(TAG, "appPreferencesRepository.collect") binding.etServer.setText(it.server) binding.etDeviceLabel.setText(it.name) binding.switchClean.isChecked = it.preventClean binding.switchRequest.isChecked = it.preventRequest binding.swReset.isChecked = it.preventReset } } launch { spoofedSimInfo.collect { Log.i(TAG, "spoofedSimInfo.collect") binding.etNumber.setText(it.number) binding.etMcc.setText(it.mcc) binding.etMnc.setText(it.mnc) binding.etIccid.setText(it.iccid) binding.etImsi.setText(it.imsi) binding.etImei.setText(it.imei) binding.etCountry.setText(it.country) binding.etAreaCode.setText(it.areaCode) binding.etCarrierId.setText(it.carrierId) binding.etCarrierName.setText(it.carrierName) } } } return binding.root } private fun onSave() { Utils.makeLoadingButton(context, binding.btnSave) binding.btnSave.isEnabled = false lifecycleScope.launch { withContext(Dispatchers.IO) { spoofedSimInfoRepository.updateSpoofedSimInfo( SpoofedSimInfo( 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" } } }