package com.example.modifier.ui.utils import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.example.modifier.Frida import com.example.modifier.R import com.example.modifier.Utils import com.example.modifier.baseTag import com.example.modifier.constants.PACKAGE_GMS import com.example.modifier.constants.PACKAGE_GSF import com.example.modifier.constants.PACKAGE_MESSAGING import com.example.modifier.data.AppPreferences import com.example.modifier.databinding.DialogUpdateBinding import com.example.modifier.databinding.FragmentUtilsBinding import com.example.modifier.extension.kill import com.example.modifier.http.ktorClient import com.example.modifier.http.api.SysConfigApi import com.example.modifier.http.response.SysConfigResponse import com.example.modifier.utils.clear import com.example.modifier.utils.clearConv import com.example.modifier.utils.killPhoneProcess import com.example.modifier.utils.resetAll import com.example.modifier.utils.resumePackage import com.example.modifier.utils.shellRun import com.example.modifier.utils.injectOTP import com.example.modifier.utils.suspendPackage import com.example.modifier.utils.syncTime import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.ktor.client.call.body import io.ktor.client.plugins.resources.get import io.ktor.client.request.prepareGet import io.ktor.http.contentLength import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.core.isEmpty import io.ktor.utils.io.core.readBytes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File class UtilsFragment : Fragment() { private val tag = "$baseTag/UtilsFragment" private lateinit var binding: FragmentUtilsBinding private lateinit var appPreferences: StateFlow override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { if (this::binding.isInitialized) { return binding.root } binding = FragmentUtilsBinding.inflate(inflater, container, false) binding.btnClear.setOnClickListener { onClear() } binding.btnStop.setOnClickListener { onStopClick() } binding.btnSend.setOnClickListener { Utils.makeLoadingButton(context, binding.btnSend) lifecycleScope.launch { delay(1000L) val otp = binding.etOtp.text.toString() withContext(Dispatchers.IO) { injectOTP(otp) } binding.btnSend.setIconResource(R.drawable.ic_done) binding.btnSend.text = "OK" delay(1500L) binding.btnSend.isEnabled = true binding.btnSend.icon = null binding.btnSend.text = "Send" } } binding.btnClearConv.setOnClickListener { binding.btnClearConv.isEnabled = false Utils.makeLoadingButton(context, binding.btnClearConv) lifecycleScope.launch { clearConv() withContext(Dispatchers.Main) { binding.btnClearConv.setIconResource(R.drawable.ic_done) binding.btnClearConv.text = "OK" delay(1500L) binding.btnClearConv.isEnabled = true binding.btnClearConv.icon = null binding.btnClearConv.text = "Clear Msg" } } } binding.btnSuspend.setOnClickListener { binding.btnSuspend.isEnabled = false Utils.makeLoadingButton(context, binding.btnSuspend) lifecycleScope.launch { val packages = mutableListOf() if (binding.cbGsf.isChecked) { packages.add(PACKAGE_GSF) } if (binding.cbGms.isChecked) { packages.add(PACKAGE_GMS) } if (binding.cbSms.isChecked) { packages.add(PACKAGE_MESSAGING) } withContext(Dispatchers.IO) { suspendPackage(*packages.toTypedArray()) } binding.btnSuspend.setIconResource(R.drawable.ic_done) binding.btnSuspend.text = "OK" delay(1500) binding.btnSuspend.isEnabled = true binding.btnSuspend.icon = null binding.btnSuspend.text = "Suspend" } } binding.btnUnsuspend.setOnClickListener { binding.btnUnsuspend.isEnabled = false Utils.makeLoadingButton(context, binding.btnUnsuspend) lifecycleScope.launch { val packages = mutableListOf() if (binding.cbGsf.isChecked) { packages.add(PACKAGE_GSF) } if (binding.cbGms.isChecked) { packages.add(PACKAGE_GMS) } if (binding.cbSms.isChecked) { packages.add(PACKAGE_MESSAGING) } withContext(Dispatchers.IO) { resumePackage(*packages.toTypedArray()) } binding.btnUnsuspend.setIconResource(R.drawable.ic_done) binding.btnUnsuspend.text = "OK" delay(1500) binding.btnUnsuspend.isEnabled = true binding.btnUnsuspend.icon = null binding.btnUnsuspend.text = "Unsuspend" } } binding.btnStartFrida.setOnClickListener { lifecycleScope.launch { withContext(Dispatchers.IO) { Frida.start() } } Toast.makeText(context, "Frida started", Toast.LENGTH_SHORT).show() } binding.btnStopFrida.setOnClickListener { lifecycleScope.launch { withContext(Dispatchers.IO) { Frida.stop() } } Toast.makeText(context, "Frida stopped", Toast.LENGTH_SHORT).show() } binding.btnKillPhone.setOnClickListener { binding.btnKillPhone.isEnabled = false Utils.makeLoadingButton(context, binding.btnKillPhone) lifecycleScope.launch { val success = withContext(Dispatchers.IO) { killPhoneProcess(force = true) } if (success) { binding.btnKillPhone.setIconResource(R.drawable.ic_done) binding.btnKillPhone.text = "OK" } else { binding.btnKillPhone.setIconResource(R.drawable.ic_error) binding.btnKillPhone.text = "Fail" } delay(1500L) binding.btnKillPhone.isEnabled = true binding.btnKillPhone.icon = null binding.btnKillPhone.text = "Kill Phone" } } binding.btnSyncTime.setOnClickListener { binding.btnSyncTime.isEnabled = false Utils.makeLoadingButton(context, binding.btnSyncTime) lifecycleScope.launch { withContext(Dispatchers.IO) { syncTime() } binding.btnSyncTime.setIconResource(R.drawable.ic_done) binding.btnSyncTime.text = "OK" delay(1500L) binding.btnSyncTime.isEnabled = true binding.btnSyncTime.icon = null binding.btnSyncTime.text = "Sync Time" } } binding.btnUpdateModifier.setOnClickListener { lifecycleScope.launch { try { val config = ktorClient.get( SysConfigApi.Id( SysConfigApi(), "modifier_apk" ) ) .body() installApk(config.value) } catch (e: Exception) { Log.e(tag, "Failed to get message apk", e) } } } binding.btnUpdateMessage.setOnClickListener { lifecycleScope.launch { try { val config = ktorClient.get( SysConfigApi.Id( SysConfigApi(), "message_apk" ) ) .body() installApk(config.value) } catch (e: Exception) { Log.e(tag, "Failed to get message apk", e) } } } binding.btnUpdateGms.setOnClickListener { lifecycleScope.launch { try { val config = ktorClient.get( SysConfigApi.Id( SysConfigApi(), "gms_apk" ) ) .body() installApk(config.value) } catch (e: Exception) { Log.e(tag, "Failed to get gms apk", e) } } } return binding.root } private fun onClear() { binding.btnClear.isEnabled = false Utils.makeLoadingButton(context, binding.btnClear) val all = binding.cbAll.isChecked val gsf = binding.cbGsf.isChecked val gms = binding.cbGms.isChecked val sms = binding.cbSms.isChecked lifecycleScope.launch { withContext(Dispatchers.IO) { if (all) { resetAll() } else { clear(gsf, gms, sms) } } binding.btnClear.setIconResource(R.drawable.ic_done) binding.btnClear.text = "OK" delay(1500) binding.btnClear.isEnabled = true binding.btnClear.icon = null binding.btnClear.text = "Clear" } } private fun onStopClick() { binding.btnStop.isEnabled = false Utils.makeLoadingButton(context, binding.btnStop) lifecycleScope.launch { withContext(Dispatchers.IO) { val packages = mutableListOf() if (binding.cbGsf.isChecked) { packages.add(PACKAGE_GSF) } if (binding.cbGms.isChecked) { packages.add(PACKAGE_GMS) } if (binding.cbSms.isChecked) { packages.add(PACKAGE_MESSAGING) } packages.forEach { shellRun(it.kill()) } } binding.btnStop.setIconResource(R.drawable.ic_done) binding.btnStop.text = "OK" delay(1500) binding.btnStop.isEnabled = true binding.btnStop.icon = null binding.btnStop.text = "Stop" } } private suspend fun installApk(url: String) { if (url.isEmpty()) { Toast.makeText(context, "URL is empty", Toast.LENGTH_SHORT).show() return } if (!url.endsWith(".apk")) { Toast.makeText(context, "URL is not an APK", Toast.LENGTH_SHORT).show() return } if (!url.startsWith("http")) { Toast.makeText(context, "URL is not valid", Toast.LENGTH_SHORT).show() return } var job: Job? = null lateinit var dialogBinding: DialogUpdateBinding lateinit var dialog: AlertDialog withContext(Dispatchers.Main) { dialogBinding = DialogUpdateBinding.inflate(layoutInflater) dialog = MaterialAlertDialogBuilder(requireContext()) .setTitle("Installing...") .setView(dialogBinding.root) .setCancelable(false) .setNegativeButton("Cancel") { _, _ -> job?.cancel() } .create() dialog.show() } job = CoroutineScope(Dispatchers.IO).launch { try { val file = File.createTempFile("files", ".apk") ktorClient.prepareGet(url) .execute { httpResponse -> val channel: ByteReadChannel = httpResponse.body() while (!channel.isClosedForRead) { val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) while (!packet.isEmpty) { val bytes = packet.readBytes() file.appendBytes(bytes) withContext( Dispatchers.Main ) { dialogBinding.progressBar.isIndeterminate = false dialogBinding.progressBar.progress = (file.length() * 100 / httpResponse.contentLength()!!).toInt() } } } } Log.i(tag, "A file saved to ${file.path}") shellRun("pm install -d -r ${file.path}") } catch (e: Exception) { Log.e(tag, "Failed to download apk", e) } withContext(Dispatchers.Main) { dialog.dismiss() } } } }