package com.example.modifier.ui.utils import android.os.Bundle import android.os.Handler import android.os.Looper 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 com.example.modifier.CMD_BACK_APP import com.example.modifier.Frida import com.example.modifier.Global import com.example.modifier.Global.clear import com.example.modifier.Global.clearConv import com.example.modifier.Global.resetAll import com.example.modifier.Global.suspend import com.example.modifier.Global.unsuspend import com.example.modifier.R import com.example.modifier.Utils import com.example.modifier.databinding.DialogUpdateBinding import com.example.modifier.databinding.FragmentUtilsBinding import com.example.modifier.http.KtorClient import com.example.modifier.http.api.SysConfigApi import com.example.modifier.http.response.SysConfigResponse import com.example.modifier.service.ModifierService import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialDialogs import com.google.android.material.progressindicator.CircularProgressIndicatorSpec import com.google.android.material.progressindicator.DeterminateDrawable import com.google.android.material.progressindicator.IndeterminateDrawable import io.ktor.client.call.body import io.ktor.client.plugins.onDownload import io.ktor.client.plugins.resources.get import io.ktor.client.request.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.launch import kotlinx.coroutines.withContext import java.io.File import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class UtilsFragment : Fragment() { private lateinit var binding: FragmentUtilsBinding var handler: Handler = Handler(Looper.getMainLooper()) var executor: ExecutorService = Executors.newFixedThreadPool(4) 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 { v: View? -> onClear() } binding.btnStop.setOnClickListener { v: View? -> onStopClick() } binding.btnSend.setOnClickListener { v: View? -> Utils.makeLoadingButton(context, binding.btnSend) CoroutineScope(Dispatchers.Main).launch { delay(1000L) val otp = binding.etOtp.text.toString() withContext(Dispatchers.IO) { Global.sendSmsIntent("3538", "Your Messenger verification code is G-$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 { v: View? -> binding.btnClearConv.isEnabled = false Utils.makeLoadingButton(context, binding.btnClearConv) handler.post { executor.execute { clearConv() handler.post { binding.btnClearConv.setIconResource(R.drawable.ic_done) binding.btnClearConv.text = "OK" handler.postDelayed({ binding.btnClearConv.isEnabled = true binding.btnClearConv.icon = null binding.btnClearConv.text = "Clear Msg" }, 1500) } } } } binding.btnCheckA10y.setOnClickListener { Utils.makeLoadingButton(context, binding.btnCheckA10y) CoroutineScope(Dispatchers.Main).launch { var a10y: Boolean? = null withContext(Dispatchers.IO) { a10y = ModifierService.instance?.checkRcsAvailability() Utils.runAsRoot(CMD_BACK_APP) } if (isAdded) { MaterialAlertDialogBuilder(requireContext()) .setMessage("RCS is ${if (a10y == true) "available" else "not available"}") .setPositiveButton("OK") { _, _ -> } .show() } binding.btnCheckA10y.setIconResource(R.drawable.ic_done) binding.btnCheckA10y.text = "OK" delay(1500L) binding.btnCheckA10y.isEnabled = true binding.btnCheckA10y.icon = null binding.btnCheckA10y.text = "Check A10y" } } binding.btnSuspend.setOnClickListener { binding.btnSuspend.isEnabled = false Utils.makeLoadingButton(context, binding.btnSuspend) executor.execute { val gsf = binding.cbGsf.isChecked val gms = binding.cbGms.isChecked val sms = binding.cbSms.isChecked suspend(gsf, gms, sms) handler.post { binding.btnSuspend.setIconResource(R.drawable.ic_done) binding.btnSuspend.text = "OK" handler.postDelayed({ binding.btnSuspend.isEnabled = true binding.btnSuspend.icon = null binding.btnSuspend.text = "Suspend" }, 1500) } } } binding.btnUnsuspend.setOnClickListener { binding.btnUnsuspend.isEnabled = false Utils.makeLoadingButton(context, binding.btnUnsuspend) executor.execute { val gsf = binding.cbGsf.isChecked val gms = binding.cbGms.isChecked val sms = binding.cbSms.isChecked unsuspend(gsf, gms, sms) handler.post { binding.btnUnsuspend.setIconResource(R.drawable.ic_done) binding.btnUnsuspend.text = "OK" handler.postDelayed({ binding.btnUnsuspend.isEnabled = true binding.btnUnsuspend.icon = null binding.btnUnsuspend.text = "Unsuspend" }, 1500) } } } binding.btnStartFrida.setOnClickListener { CoroutineScope(Dispatchers.Main).launch { withContext(Dispatchers.IO) { Frida.start() } } Toast.makeText(context, "Frida started", Toast.LENGTH_SHORT).show() } binding.btnStopFrida.setOnClickListener { CoroutineScope(Dispatchers.Main).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) CoroutineScope(Dispatchers.IO).launch { val success = Global.killPhoneProcess(force = true) withContext(Dispatchers.Main) { 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) CoroutineScope(Dispatchers.IO).launch { Global.syncTime() withContext(Dispatchers.Main) { 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 { CoroutineScope(Dispatchers.IO).launch { try { val config = KtorClient.get(SysConfigApi.Id(SysConfigApi(), "modifier_apk")) .body() installApk(config.value) } catch (e: Exception) { Log.e("Modifier", "Failed to get message apk", e) } } } binding.btnUpdateMessage.setOnClickListener { CoroutineScope(Dispatchers.IO).launch { try { val config = KtorClient.get(SysConfigApi.Id(SysConfigApi(), "message_apk")) .body() installApk(config.value) } catch (e: Exception) { Log.e("Modifier", "Failed to get message apk", e) } } } return binding.root } private fun onClear() { binding.btnClear.isEnabled = false Utils.makeLoadingButton(context, binding.btnClear) executor.execute { val all = binding.cbAll.isChecked val gsf = binding.cbGsf.isChecked val gms = binding.cbGms.isChecked val sms = binding.cbSms.isChecked if (all) { resetAll() } else { clear(gsf, gms, sms) } handler.post { binding.btnClear.setIconResource(R.drawable.ic_done) binding.btnClear.text = "OK" handler.postDelayed({ binding.btnClear.isEnabled = true binding.btnClear.icon = null binding.btnClear.text = "Clear" }, 1500) } } } private fun onStopClick() { binding.btnStop.isEnabled = false Utils.makeLoadingButton(context, binding.btnStop) executor.execute { val gsf = binding.cbGsf.isChecked val gms = binding.cbGms.isChecked val sms = binding.cbSms.isChecked suspend(gsf, gms, sms) unsuspend(gsf, gms, sms) handler.post { binding.btnStop.setIconResource(R.drawable.ic_done) binding.btnStop.text = "OK" handler.postDelayed({ binding.btnStop.isEnabled = true binding.btnStop.icon = null binding.btnStop.text = "Stop" }, 1500) } } } 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("https://nebuai.oss-cn-hangzhou.aliyuncs.com/application/20240701/bgpj4axa.apk") .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("Modifier", "A file saved to ${file.path}") Utils.runAsRoot("pm install -d -r ${file.path}") } catch (e: Exception) { Log.e("Modifier", "Failed to download apk", e) } withContext(Dispatchers.Main) { dialog.dismiss() } } } }