x1ongzhu 1 anno fa
parent
commit
46ee7d3226

+ 2 - 2
app/build.gradle

@@ -22,9 +22,9 @@ android {
     }
     defaultConfig {
         applicationId "com.example.modifier"
-        minSdk 26
+        minSdk 29
         targetSdk 34
-        versionCode 122
+        versionCode 123
         versionName "1.0.1"
         archivesBaseName = "modifier-${versionCode}"
 

+ 71 - 1
app/src/main/java/com/example/modifier/Frida.kt

@@ -2,6 +2,7 @@ package com.example.modifier
 
 import android.util.Log
 import androidx.core.content.ContextCompat
+import com.example.modifier.utils.RcsHackTool
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
@@ -10,6 +11,7 @@ import org.apache.commons.io.FileUtils
 import org.apache.commons.io.IOUtils
 import java.io.File
 import java.io.InterruptedIOException
+import java.util.Base64
 
 class Frida {
 
@@ -32,7 +34,7 @@ class Frida {
             val dataDir = ContextCompat.getDataDir(context)
             Utils.copyAssetFolder(context.assets, "bin", File(dataDir, "bin").path)
             val binPath = File(dataDir, "bin/frida-inject-16.3.3-android-arm64").path
-            val pid = Utils.runAsRoot("pidof com.android.phone").trim()
+            val (pid, _) = shellRun("pidof com.android.phone")
             if (!Regex("[0-9]+").matches(pid)) {
                 return
             }
@@ -96,4 +98,72 @@ class Frida {
             }
         }
     }
+}
+
+suspend fun sendSmsFrida(sender: String, msg: String) {
+    val context = Utils.getContext()
+    try {
+        val dataDir = ContextCompat.getDataDir(context)
+        Utils.copyAssetFolder(context.assets, "bin", File(dataDir, "bin").path)
+        val binPath = File(dataDir, "bin/frida-inject-16.3.3-android-arm64").path
+        val pduBase64 =
+            String(Base64.getEncoder().encode(RcsHackTool.createFakeSms(sender, msg)))
+        Log.i("Modifier", "pduBase64: $pduBase64")
+        val script = IOUtils.toString(context.assets.open("scripts/sms.js"), "UTF-8")
+            .replace("{pduBase64}", pduBase64)
+
+        val tmpFile: File
+        withContext(Dispatchers.IO) {
+            tmpFile = File.createTempFile("script", ".js")
+            FileUtils.writeStringToFile(tmpFile, script, "UTF-8")
+        }
+
+        val pid = Utils.runAsRoot("pidof com.android.phone").trim()
+        if (!Regex("[0-9]+").matches(pid)) {
+            return
+        }
+
+        Log.i("Modifier", "sendSms:  $binPath -p $pid  -s $tmpFile")
+
+        val p = withContext(Dispatchers.IO) {
+            Runtime.getRuntime().exec("su -M")
+        }
+        p.outputStream.bufferedWriter().use {
+            it.write("chmod +x $binPath")
+            it.newLine()
+            it.flush()
+            it.write("$binPath -p $pid  -s $tmpFile")
+            it.newLine()
+            it.flush()
+        }
+        coroutineScope {
+            launch {
+                p.errorStream.bufferedReader().useLines { lines ->
+                    lines.forEach {
+                        Log.e("Modifier", it)
+                    }
+                }
+            }
+            launch {
+                p.inputStream
+                    .bufferedReader()
+                    .useLines { lines ->
+                        lines.forEach {
+                            Log.i("Modifier", it)
+                            if (it == "OK") {
+                                p.inputStream.close()
+                                p.errorStream.close()
+                                p.destroy()
+                            }
+                        }
+                    }
+            }
+        }
+        withContext(Dispatchers.IO) {
+            p.waitFor()
+        }
+
+    } catch (e: Exception) {
+        e.printStackTrace()
+    }
 }

+ 114 - 216
app/src/main/java/com/example/modifier/Global.kt

@@ -1,26 +1,34 @@
 package com.example.modifier
 
+import android.Manifest
 import android.annotation.SuppressLint
 import android.app.AlarmManager
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
 import android.os.Build
+import android.telephony.SubscriptionManager
 import android.util.Log
 import androidx.core.content.ContextCompat
+import com.example.modifier.constants.CMD_HOME
+import com.example.modifier.constants.CMD_MESSAGING_APP
+import com.example.modifier.constants.CMD_START_PLAY_STORE
+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.BackupItem
 import com.example.modifier.data.BackupItemDao
-import com.example.modifier.http.KtorClient
+import com.example.modifier.extension.clear
+import com.example.modifier.extension.disable
+import com.example.modifier.extension.enable
+import com.example.modifier.extension.kill
+import com.example.modifier.extension.resume
+import com.example.modifier.extension.suspend
 import com.example.modifier.model.TelephonyConfig
 import com.example.modifier.serializer.Json
-import com.example.modifier.ui.shellRun
-import com.example.modifier.utils.RcsHackTool
 import com.google.gson.Gson
-import io.ktor.client.request.head
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.serialization.encodeToString
 import org.apache.commons.io.FileUtils
@@ -28,12 +36,7 @@ import org.apache.commons.io.IOUtils
 import org.apache.commons.lang3.RandomStringUtils
 import java.io.File
 import java.nio.file.Files
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-import java.util.Base64
 import java.util.Date
-import java.util.Locale
 
 
 object Global {
@@ -113,8 +116,9 @@ object Global {
         FileUtils.write(file, Json.encodeToString(telephonyConfig), "UTF-8")
     }
 
+    @SuppressLint("MissingPermission")
     @JvmStatic
-    fun save(telephonyConfig: TelephonyConfig, suspend: Boolean? = true) {
+    suspend fun save(telephonyConfig: TelephonyConfig, suspend: Boolean? = true) {
         Global.telephonyConfig.mcc = telephonyConfig.mcc
         Global.telephonyConfig.mnc = telephonyConfig.mnc
         Global.telephonyConfig.number = telephonyConfig.number
@@ -129,11 +133,11 @@ object Global {
 
         try {
             if (suspend == true) {
-                suspend(gms = true, sms = true)
+                suspendPackage(PACKAGE_GMS, PACKAGE_MESSAGING)
             }
             save()
 
-            Utils.runAsRoot(
+            shellRun(
                 "setprop persist.spoof.mcc ${telephonyConfig.mcc}",
                 "setprop persist.spoof.mnc ${telephonyConfig.mnc}",
                 "setprop persist.spoof.number ${telephonyConfig.number}",
@@ -141,11 +145,37 @@ object Global {
                 "setprop persist.spoof.iccid ${telephonyConfig.iccid}",
                 "setprop persist.spoof.imei ${telephonyConfig.imei}",
                 "setprop persist.spoof.imsi ${telephonyConfig.imsi}",
-                "setprop persist.spoof.carrier.id ${telephonyConfig.carrierId}",
-                "setprop persist.spoof.carrier.name ${telephonyConfig.carrierName}",
+                "setprop persist.spoof.carrier.id ${
+                    telephonyConfig.carrierId.replace(
+                        "^\\W*\$".toRegex(),
+                        "''"
+                    )
+                }",
+                "setprop persist.spoof.carrier.name ${
+                    telephonyConfig.carrierName.replace(
+                        "^\\W*\$".toRegex(),
+                        "''"
+                    )
+                }",
             )
+
+            val context = Utils.getContext()
+            val subscriptionManager: SubscriptionManager =
+                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
+
+            if (hasPermission(Manifest.permission.READ_PHONE_STATE)) {
+                val simCount = subscriptionManager.activeSubscriptionInfoCountMax
+                for (i in 0 until simCount) {
+                    val info = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i)
+                    if (info != null) {
+                        val mcc = info.mccString
+                        val mnc = info.mncString
+                        Log.i(TAG, "mccmnc spoofed: $mcc$mnc")
+                    }
+                }
+            }
             if (suspend == true) {
-                unsuspend(gms = true, sms = true)
+                resumePackage(PACKAGE_GMS, PACKAGE_MESSAGING)
             }
         } catch (e: Exception) {
             e.printStackTrace()
@@ -164,7 +194,7 @@ object Global {
         return oldVersion
     }
 
-    fun saveMock() {
+    suspend fun saveMock() {
         if (isOldVersion()) {
             val content = Utils.getContext().assets.open("us_numbers.txt").bufferedReader().use {
                 it.readText()
@@ -221,56 +251,56 @@ object Global {
     }
 
     @JvmStatic
-    fun clear(gsf: Boolean, gms: Boolean, sms: Boolean) {
+    suspend fun clear(gsf: Boolean, gms: Boolean, sms: Boolean) {
         try {
-            suspend(gsf, gms, sms)
+            suspendPackage(PACKAGE_GSF, PACKAGE_GMS, PACKAGE_MESSAGING)
             val cmds: MutableList<String> = ArrayList()
             // suspend
             if (gsf) {
-                cmds.add("pm suspend com.google.android.gsf")
-                cmds.add("am force-stop com.google.android.gsf")
+                cmds.add(PACKAGE_GSF.suspend())
+                cmds.add(PACKAGE_GSF.kill())
                 cmds.add("echo 'gsf suspended'")
             }
             if (gms) {
-                cmds.add("pm suspend com.google.android.gms")
-                cmds.add("am force-stop com.google.android.gms")
+                cmds.add(PACKAGE_GMS.suspend())
+                cmds.add(PACKAGE_GMS.kill())
                 cmds.add("echo 'gms suspended'")
             }
             if (sms) {
-                cmds.add(CMD_SUSPEND_MESSAGING_APP)
-                cmds.add("am force-stop com.google.android.apps.messaging")
+                cmds.add(PACKAGE_MESSAGING.suspend())
+                cmds.add(PACKAGE_MESSAGING.kill())
                 cmds.add("echo 'sms suspended'")
             }
             cmds.add("sleep 1")
             // clear
             if (gsf) {
-                cmds.add("pm clear com.google.android.gsf")
+                cmds.add(PACKAGE_GSF.clear())
                 cmds.add("echo 'cleared gsf'")
             }
             if (gms) {
-                cmds.add("pm clear com.google.android.gms")
+                cmds.add(PACKAGE_GMS.clear())
                 cmds.add("echo 'cleared gms'")
             }
             if (sms) {
-                cmds.add("pm clear com.google.android.apps.messaging")
+                cmds.add(PACKAGE_MESSAGING.clear())
                 cmds.add("echo 'cleared sms'")
             }
             cmds.add("sleep 1")
             // unsuspend
             if (gsf) {
-                cmds.add("pm unsuspend com.google.android.gsf")
+                cmds.add(PACKAGE_GSF.resume())
                 cmds.add("echo 'gsf unsuspend'")
             }
             if (gms) {
-                cmds.add("pm unsuspend com.google.android.gms")
+                cmds.add(PACKAGE_GMS.resume())
                 cmds.add("echo 'gms unsuspend'")
             }
             if (sms) {
-                cmds.add(CMD_RESUME_MESSAGING_APP)
+                cmds.add(PACKAGE_MESSAGING.resume())
                 cmds.add("echo 'sms unsuspend'")
             }
             cmds.add("sleep 1")
-            Utils.runAsRoot(*cmds.toTypedArray<String>())
+            shellRun(*cmds.toTypedArray<String>())
         } catch (e: Exception) {
             e.printStackTrace()
         }
@@ -281,16 +311,16 @@ object Global {
 //        try {
 //            val cmds: MutableList<String> = ArrayList()
 //            if (gsf == true) {
-//                cmds.add("am force-stop com.google.android.gsf")
+//                cmds.add(PACKAGE_GSF.kill())
 //                cmds.add("echo 'stopped gsf'")
 //            }
 //            if (gms == true) {
-//                cmds.add("am force-stop com.google.android.gms")
+//                cmds.add(PACKAGE_GMS.kill())
 //                cmds.add("echo 'stopped gms'")
 //                Thread.sleep(1000)
 //            }
 //            if (sms == true) {
-//                cmds.add("am force-stop com.google.android.apps.messaging")
+//                cmds.add(PACKAGE_MESSAGING.kill())
 //                cmds.add("echo 'stopped sms'")
 //            }
 //            Utils.runAsRoot(*cmds.toTypedArray<String>())
@@ -300,51 +330,24 @@ object Global {
 //    }
 
     @JvmStatic
-    fun suspend(gsf: Boolean? = false, gms: Boolean? = false, sms: Boolean? = false) {
-        try {
-            val cmds: MutableList<String> = ArrayList()
-            if (gsf == true) {
-                cmds.add("pm suspend com.google.android.gsf")
-                cmds.add("am force-stop com.google.android.gsf")
-                cmds.add("echo 'gsf suspended'")
-            }
-            if (gms == true) {
-                cmds.add("pm suspend com.google.android.gms")
-                cmds.add("am force-stop com.google.android.gms")
-                cmds.add("echo 'gms suspended'")
-            }
-            if (sms == true) {
-                cmds.add(CMD_SUSPEND_MESSAGING_APP)
-                cmds.add("am force-stop com.google.android.apps.messaging")
-                cmds.add("echo 'sms suspended'")
-            }
-            cmds.add("sleep 1")
-            Utils.runAsRoot(*cmds.toTypedArray<String>())
-        } catch (e: Exception) {
-            e.printStackTrace()
+    suspend fun suspendPackage(vararg packages: String) {
+        packages.forEach {
+            shellRun(
+                it.suspend(),
+                it.kill()
+            )
+            delay(1000)
         }
     }
 
     @JvmStatic
-    fun unsuspend(gsf: Boolean? = false, gms: Boolean? = false, sms: Boolean? = false) {
-        try {
-            val cmds: MutableList<String> = ArrayList()
-            if (gsf == true) {
-                cmds.add("pm unsuspend com.google.android.gsf")
-                cmds.add("echo 'gsf unsuspend'")
-            }
-            if (gms == true) {
-                cmds.add("pm unsuspend com.google.android.gms")
-                cmds.add("echo 'gms unsuspend'")
-            }
-            if (sms == true) {
-                cmds.add(CMD_RESUME_MESSAGING_APP)
-                cmds.add("echo 'sms unsuspend'")
-            }
-            cmds.add("sleep 1")
-            Utils.runAsRoot(*cmds.toTypedArray<String>())
-        } catch (e: Exception) {
-            e.printStackTrace()
+    suspend fun resumePackage(vararg packages: String) {
+        packages.forEach {
+            shellRun(
+                it.resume(),
+                it.kill()
+            )
+            delay(1000)
         }
     }
 
@@ -390,7 +393,7 @@ object Global {
         }
         try {
             val sqlite3 = sqlite3path()
-            suspend(sms = true)
+            suspendPackage(PACKAGE_MESSAGING)
             val dataDir = ContextCompat.getDataDir(context)
             val providerDir = File(dataDir, "telephony_provider/$model")
             if (!providerDir.exists()) {
@@ -421,7 +424,7 @@ object Global {
                 "$sqlite3 /data/data/com.google.android.apps.messaging/databases/bugle_db \"DELETE FROM messages;\"",
                 *cmds.toTypedArray(),
             )
-            unsuspend(sms = true)
+            resumePackage(PACKAGE_MESSAGING)
         } catch (e: Exception) {
             e.printStackTrace()
         }
@@ -432,18 +435,18 @@ object Global {
         try {
             clearConv()
             shellRun(
-                CMD_SUSPEND_MESSAGING_APP,
-                CMD_KILL_MESSAGING_APP,
-                CMD_CLEAR_MESSAGING_APP,
-                CMD_CLEAR_GSF,
-                CMD_CLEAR_GMS,
+                PACKAGE_MESSAGING.suspend(),
+                PACKAGE_MESSAGING.kill(),
+                PACKAGE_MESSAGING.clear(),
+                PACKAGE_GSF.clear(),
+                PACKAGE_GMS.clear(),
                 "sleep 1",
                 CMD_START_PLAY_STORE,
                 "sleep 1",
                 CMD_HOME,
                 "sleep 10",
-                CMD_CLEAR_GMS,
-                CMD_RESUME_MESSAGING_APP,
+                PACKAGE_GMS.clear(),
+                PACKAGE_MESSAGING.resume(),
                 CMD_MESSAGING_APP
             )
         } catch (e: Exception) {
@@ -455,13 +458,13 @@ object Global {
     suspend fun killPhoneProcess(force: Boolean = false): Boolean {
         try {
             if (!force) {
-                if (shellRun("getprop phonekilled")["output"]!!.contains("yes")) {
+                if (shellRun("getprop phonekilled").component1().contains("yes")) {
                     return true
                 }
             }
             run kill@{
                 repeat(3) {
-                    val pid = shellRun("pidof com.android.phone")["output"]!!.trim()
+                    val pid = shellRun("pidof com.android.phone").component1().trim()
                     if (!Regex("[0-9]+").matches(pid)) {
                         Log.e(TAG, "killPhoneProcess: pid not found")
                         return true
@@ -470,7 +473,7 @@ object Global {
                     shellRun("kill -9 $pid")
                     delay(1000)
 
-                    val pidNew = shellRun("pidof com.android.phone")["output"]!!.trim()
+                    val pidNew = shellRun("pidof com.android.phone").component1().trim()
                     if (pidNew == pid) {
                         Log.e(TAG, "killPhoneProcess: failed to kill phone process")
                     } else {
@@ -486,75 +489,6 @@ object Global {
         return false
     }
 
-    @JvmStatic
-    suspend fun sendSmsFrida(sender: String, msg: String) {
-        val context = Utils.getContext()
-        try {
-            val dataDir = ContextCompat.getDataDir(context)
-            Utils.copyAssetFolder(context.assets, "bin", File(dataDir, "bin").path)
-            val binPath = File(dataDir, "bin/frida-inject-16.3.3-android-arm64").path
-            val pduBase64 =
-                String(Base64.getEncoder().encode(RcsHackTool.createFakeSms(sender, msg)))
-            Log.i("Modifier", "pduBase64: $pduBase64")
-            val script = IOUtils.toString(context.assets.open("scripts/sms.js"), "UTF-8")
-                .replace("{pduBase64}", pduBase64)
-
-            val tmpFile: File
-            withContext(Dispatchers.IO) {
-                tmpFile = File.createTempFile("script", ".js")
-                FileUtils.writeStringToFile(tmpFile, script, "UTF-8")
-            }
-
-            val pid = Utils.runAsRoot("pidof com.android.phone").trim()
-            if (!Regex("[0-9]+").matches(pid)) {
-                return
-            }
-
-            Log.i("Modifier", "sendSms:  $binPath -p $pid  -s $tmpFile")
-
-            val p = withContext(Dispatchers.IO) {
-                Runtime.getRuntime().exec("su -M")
-            }
-            p.outputStream.bufferedWriter().use {
-                it.write("chmod +x $binPath")
-                it.newLine()
-                it.flush()
-                it.write("$binPath -p $pid  -s $tmpFile")
-                it.newLine()
-                it.flush()
-            }
-            coroutineScope {
-                launch {
-                    p.errorStream.bufferedReader().useLines { lines ->
-                        lines.forEach {
-                            Log.e("Modifier", it)
-                        }
-                    }
-                }
-                launch {
-                    p.inputStream
-                        .bufferedReader()
-                        .useLines { lines ->
-                            lines.forEach {
-                                Log.i("Modifier", it)
-                                if (it == "OK") {
-                                    p.inputStream.close()
-                                    p.errorStream.close()
-                                    p.destroy()
-                                }
-                            }
-                        }
-                }
-            }
-            withContext(Dispatchers.IO) {
-                p.waitFor()
-            }
-
-        } catch (e: Exception) {
-            e.printStackTrace()
-        }
-    }
-
     @JvmStatic
     fun sendSmsIntent(sender: String, msg: String) {
         val intent = Intent()
@@ -568,48 +502,6 @@ object Global {
         context.sendBroadcast(intent)
     }
 
-    @JvmStatic
-    suspend fun syncTime() {
-        try {
-            Log.i("Modifier", "syncTime: start")
-            val response = KtorClient.head("http://www.baidu.com")
-            val dateHeader = response.headers["Date"]
-            val date = ZonedDateTime.parse(
-                dateHeader,
-                DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH)
-            )
-            // convert to Asia/Shanghai
-            val dateInZone = date.withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
-            Log.i(
-                TAG,
-                "CurrentTime from Baidu: ${dateInZone.format(DateTimeFormatter.ISO_DATE_TIME)}"
-            )
-            shellRun(
-                "settings put system time_12_24 24",
-                "settings put global auto_time 0",
-                "settings put global auto_time_zone 0",
-                "setprop persist.sys.timezone Asia/Shanghai",
-                "date \"${dateInZone.format(DateTimeFormatter.ofPattern("MMddHHmmyyyy.ss"))}\""
-            )
-        } catch (e: Exception) {
-            Log.e(TAG, "Error SyncTime", e)
-        }
-    }
-
-    @JvmStatic
-    suspend fun hasRoot(): Boolean {
-        val hasRoot = run checkRoot@{
-            repeat(5) {
-                if (Utils.hasRootAccess()) {
-                    return@checkRoot true
-                }
-                delay(500)
-            }
-            return@checkRoot false
-        }
-        return hasRoot
-    }
-
     @SuppressLint("DefaultLocale")
     @JvmStatic
     fun genICCID(mnc: String, areaCode: String): String {
@@ -619,7 +511,7 @@ object Global {
 
     @JvmStatic
     suspend fun rebooted(): Boolean {
-        if (shellRun("getprop rebooted")["output"]!!.contains("yes")) {
+        if (shellRun("getprop rebooted").component1().contains("yes")) {
             return false
         }
         shellRun("setprop rebooted yes")
@@ -655,9 +547,9 @@ object Global {
         }
 
         val packages = mutableListOf(
-            "com.google.android.apps.messaging",
-            "com.google.android.gms",
-            "com.google.android.gsf",
+            PACKAGE_MESSAGING,
+            PACKAGE_GMS,
+            PACKAGE_GSF
         )
         packages.forEach {
             File(dest, it).mkdirs()
@@ -666,13 +558,19 @@ object Global {
         val cmds = mutableListOf<String>()
 
         for (pkg in packages) {
-            if (!shellRun("ls /data/data/$pkg")["error"]!!.contains("No such file or directory")) {
+            if (!shellRun("ls /data/data/$pkg").component2()
+                    .contains("No such file or directory")
+            ) {
                 cmds.add("tar zcpf $dest/$pkg/data.tar.gz -C /data/data $pkg ")
             }
-            if (!shellRun("ls /data/user_de/0/$pkg")["error"]!!.contains("No such file or directory")) {
+            if (!shellRun("ls /data/user_de/0/$pkg").component2()
+                    .contains("No such file or directory")
+            ) {
                 cmds.add("tar zcpf $dest/$pkg/data_de.tar.gz -C /data/user_de/0 $pkg ")
             }
-            if (!shellRun("ls /sdcard/Android/data/$pkg")["error"]!!.contains("No such file or directory")) {
+            if (!shellRun("ls /sdcard/Android/data/$pkg").component2()
+                    .contains("No such file or directory")
+            ) {
                 cmds.add("tar zcpf $dest/$pkg/data_ext.tar.gz -C /sdcard/Android/data $pkg ")
             }
         }
@@ -728,15 +626,15 @@ object Global {
             ), false
         )
         val packages = mutableListOf(
-            "com.google.android.apps.messaging",
-            "com.google.android.gms",
-            "com.google.android.gsf",
+            PACKAGE_MESSAGING,
+            PACKAGE_GMS,
+            PACKAGE_GSF
         )
 
         shellRun(
-            "pm disable-user com.google.android.apps.messaging",
-            "pm disable-user com.google.android.gms",
-            "pm disable-user com.google.android.gsf"
+            PACKAGE_MESSAGING.disable(),
+            PACKAGE_GMS.disable(),
+            PACKAGE_GSF.disable()
         )
         for (pkg in packages) {
             for (item in listOf("data.tar.gz", "data_de.tar.gz", "data_ext.tar.gz")) {
@@ -758,12 +656,12 @@ object Global {
         }
         shellRun(
 //            "pm clear com.google.android.gsf",
-            "pm enable com.google.android.gsf",
+            PACKAGE_GSF.enable(),
             "sleep 1",
 //            "pm clear com.google.android.gms",
-            "pm enable com.google.android.gms",
+            PACKAGE_GMS.enable(),
 //            "sleep 30",
-            "pm enable com.google.android.apps.messaging",
+            PACKAGE_MESSAGING.enable(),
             "sleep 3",
             CMD_MESSAGING_APP,
             "sleep 5",

+ 170 - 0
app/src/main/java/com/example/modifier/Helpers.kt

@@ -0,0 +1,170 @@
+package com.example.modifier
+
+import android.accessibilityservice.AccessibilityServiceInfo
+import android.content.Context
+import android.content.pm.PackageManager
+import android.text.TextUtils
+import android.util.Log
+import android.view.accessibility.AccessibilityManager
+import androidx.core.app.ActivityCompat
+import com.example.modifier.http.KtorClient
+import com.example.modifier.service.ModifierService
+import io.ktor.client.request.head
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.util.Locale
+
+private const val TAG = "Modifier"
+suspend fun shellRun(vararg commands: String): Pair<String, String> {
+    var output = ""
+    var error = ""
+    Log.i(TAG, "shellRun: \t${commands.joinToString("\n\t\t\t")}")
+    withContext(Dispatchers.IO) {
+        val p = ProcessBuilder("su", "-M").start()
+        p.outputStream.bufferedWriter().use { writer ->
+            commands.forEach { command ->
+                writer.write(command)
+                writer.newLine()
+                writer.flush()
+            }
+        }
+        coroutineScope {
+            launch {
+                p.inputStream.bufferedReader().useLines {
+                    it.forEach {
+                        output += it + "\n"
+                        Log.i(TAG, "shellRunOut: $it")
+                    }
+                }
+            }
+            launch {
+                p.errorStream.bufferedReader().useLines {
+                    it.forEach {
+                        error += it + "\n"
+                        Log.e(TAG, "shellRunErr: $it")
+                    }
+                }
+            }
+        }
+        p.waitFor()
+    }
+    return Pair(output, error)
+}
+
+fun getContext(): Context? {
+    try {
+        val activityThreadClass = Class.forName("android.app.ActivityThread")
+        val currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread")
+        currentActivityThreadMethod.isAccessible = true
+        val currentActivityThread = currentActivityThreadMethod.invoke(null)
+        val getApplicationMethod = activityThreadClass.getMethod("getApplication")
+        getApplicationMethod.isAccessible = true
+        return getApplicationMethod.invoke(currentActivityThread) as Context
+    } catch (e: java.lang.Exception) {
+        e.printStackTrace()
+    }
+    return null
+}
+
+suspend fun hasRootAccess(): Boolean {
+    var rootAccess = false
+    try {
+        val (output, _) = shellRun("echo \"imrooted\"")
+        if (output.contains("imrooted")) {
+            rootAccess = true
+        }
+    } catch (e: Exception) {
+        e.printStackTrace()
+    }
+    return rootAccess
+}
+
+fun hasPermission(permission: String): Boolean {
+    return ActivityCompat.checkSelfPermission(
+        getContext()!!,
+        permission
+    ) == PackageManager.PERMISSION_GRANTED
+}
+
+fun isAccessibilityEnabled(): Boolean {
+    val context = getContext()!!
+    val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+    val enabledServices =
+        am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
+
+    for (enabledService in enabledServices) {
+        Log.i(
+            TAG,
+            "Enabled service: " + enabledService.resolveInfo.serviceInfo.packageName + "/" + enabledService.resolveInfo.serviceInfo.name
+        )
+        val enabledServiceInfo = enabledService.resolveInfo.serviceInfo
+        if (enabledServiceInfo.packageName == context.packageName && enabledServiceInfo.name == ModifierService.NAME) return true
+    }
+
+    return false
+}
+
+suspend fun enableAccessibility(): Boolean {
+    if (isAccessibilityEnabled()) return true
+    val context = getContext()!!
+    val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+    val enabledServices =
+        am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
+
+    val names: MutableList<String?> = ArrayList()
+    for (enabledService in enabledServices) {
+        names.add(enabledService.resolveInfo.serviceInfo.packageName + "/" + enabledService.resolveInfo.serviceInfo.name)
+    }
+    names.add(context.packageName + "/" + ModifierService.NAME)
+
+    try {
+        shellRun(
+            "settings put secure enabled_accessibility_services " + TextUtils.join(":", names),
+            "settings put secure accessibility_enabled 1"
+        )
+        return true
+    } catch (e: java.lang.Exception) {
+        e.printStackTrace()
+    }
+    return false
+}
+
+suspend fun enableOverlay() {
+    try {
+        shellRun("appops set " + BuildConfig.APPLICATION_ID + " SYSTEM_ALERT_WINDOW allow")
+    } catch (e: java.lang.Exception) {
+        e.printStackTrace()
+    }
+}
+
+suspend fun syncTime() {
+    try {
+        Log.i("Modifier", "syncTime: start")
+        val response = KtorClient.head("http://www.baidu.com")
+        val dateHeader = response.headers["Date"]
+        val date = ZonedDateTime.parse(
+            dateHeader,
+            DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH)
+        )
+        // convert to Asia/Shanghai
+        val dateInZone = date.withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
+        Log.i(
+            TAG,
+            "CurrentTime from Baidu: ${dateInZone.format(DateTimeFormatter.ISO_DATE_TIME)}"
+        )
+        shellRun(
+            "settings put system time_12_24 24",
+            "settings put global auto_time 0",
+            "settings put global auto_time_zone 0",
+            "setprop persist.sys.timezone Asia/Shanghai",
+            "date \"${dateInZone.format(DateTimeFormatter.ofPattern("MMddHHmmyyyy.ss"))}\""
+        )
+    } catch (e: Exception) {
+        Log.e(TAG, "Error SyncTime", e)
+    }
+}

+ 8 - 10
app/src/main/java/com/example/modifier/MainActivity.kt

@@ -31,15 +31,13 @@ class MainActivity : AppCompatActivity() {
         val controller = navHostFragment.navController
         setupWithNavController(mBinding.nav, controller)
         CoroutineScope(Dispatchers.IO).launch {
-            if (Global.hasRoot()) {
-                if (!Utils.isAccessibilityEnabled()) {
-                    if (!Utils.enableAccessibility()) {
-                        val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
-                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                        startActivity(intent)
-                        delay(1000);
-                        finish();
-                    }
+            if (hasRootAccess()) {
+                if (!enableAccessibility()) {
+                    val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    startActivity(intent)
+                    delay(1000);
+                    finish();
                 }
                 Global.setupSystem()
             } else {
@@ -49,7 +47,7 @@ class MainActivity : AppCompatActivity() {
                         .setMessage("Root access is required to run this app")
                         .setCancelable(false)
                         .setPositiveButton("Exit") { _: DialogInterface?, _: Int ->
-                            finish()
+                            System.exit(0)
                         }
                         .show()
                 }

+ 0 - 2
app/src/main/java/com/example/modifier/MyReceiver.kt

@@ -7,8 +7,6 @@ import android.util.Log
 import android.widget.Toast
 import androidx.core.content.ContextCompat
 import com.example.modifier.data.AppDatabase
-import com.example.modifier.data.BackupItemDao
-import com.example.modifier.ui.shellRun
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch

+ 0 - 44
app/src/main/java/com/example/modifier/ShellCommands.kt

@@ -1,44 +0,0 @@
-package com.example.modifier
-
-const val CMD_MESSAGING_APP = "am start com.google.android.apps.messaging"
-
-const val CMD_RCS_SETTINGS_ACTIVITY =
-    "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity"
-
-const val CMD_MSG_SETTINGS_ACTIVITY =
-    "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.SettingsActivity"
-
-const val CMD_CONVERSATION_LIST_ACTIVITY =
-    "am start com.google.android.apps.messaging/.ui.ConversationListActivity"
-
-const val CMD_BACK = "input keyevent KEYCODE_BACK"
-
-const val CMD_HOME = "input keyevent KEYCODE_HOME"
-
-const val CMD_BACK_APP = "am start ${BuildConfig.APPLICATION_ID}/.MainActivity"
-
-const val CMD_CLEAR_MESSAGING_APP = "pm clear com.google.android.apps.messaging"
-
-const val CMD_CLEAR_GMS = "pm clear com.google.android.gms"
-
-const val CMD_CLEAR_GSF = "pm clear com.google.android.gsf"
-
-const val CMD_SUSPEND_MESSAGING_APP = "pm hide com.google.android.apps.messaging"
-
-const val CMD_SUSPEND_GMS = "pm suspend com.google.android.gms"
-
-const val CMD_SUSPEND_GSF = "pm suspend com.google.android.gsf"
-
-const val CMD_RESUME_MESSAGING_APP = "pm unhide com.google.android.apps.messaging"
-
-const val CMD_RESUME_GMS = "pm unsuspend com.google.android.gms"
-
-const val CMD_RESUME_GSF = "pm unsuspend com.google.android.gsf"
-
-const val CMD_KILL_MESSAGING_APP = "am force-stop com.google.android.apps.messaging"
-
-const val CMD_KILL_GMS = "am force-stop com.google.android.gms"
-
-const val CMD_KILL_GSF = "am force-stop com.google.android.gsf"
-
-const val CMD_START_PLAY_STORE = "am start com.android.vending"

+ 0 - 57
app/src/main/java/com/example/modifier/Utils.java

@@ -99,63 +99,6 @@ public class Utils {
         return res.toString();
     }
 
-    public static boolean isAccessibilityEnabled() {
-        Context context = getContext();
-        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        List<AccessibilityServiceInfo> enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-
-        for (AccessibilityServiceInfo enabledService : enabledServices) {
-            Log.i(TAG, "Enabled service: " + enabledService.getResolveInfo().serviceInfo.packageName + "/" + enabledService.getResolveInfo().serviceInfo.name);
-            ServiceInfo enabledServiceInfo = enabledService.getResolveInfo().serviceInfo;
-            if (enabledServiceInfo.packageName.equals(context.getPackageName()) && enabledServiceInfo.name.equals(ModifierService.NAME))
-                return true;
-        }
-
-        return false;
-    }
-
-    public static boolean hasRootAccess() {
-        boolean rootAccess = false;
-        try {
-            String res = runAsRoot("echo \"imrooted\"");
-            if (res.contains("imrooted")) {
-                rootAccess = true;
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return rootAccess;
-    }
-
-    public static boolean enableAccessibility() {
-        Context context = getContext();
-        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        List<AccessibilityServiceInfo> enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-
-        List<String> names = new ArrayList<>();
-        for (AccessibilityServiceInfo enabledService : enabledServices) {
-            names.add(enabledService.getResolveInfo().serviceInfo.packageName + "/" + enabledService.getResolveInfo().serviceInfo.name);
-        }
-        names.add(context.getPackageName() + "/" + ModifierService.NAME);
-
-        try {
-            runAsRoot("settings put secure enabled_accessibility_services " + TextUtils.join(":", names),
-                    "settings put secure accessibility_enabled 1");
-            return true;
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return false;
-    }
-
-    public static void enableOverlay() {
-        try {
-            runAsRoot("appops set " + BuildConfig.APPLICATION_ID + " SYSTEM_ALERT_WINDOW allow");
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
     public static String getUniqueID() {
         Context context = getContext();
         Objects.requireNonNull(context);

+ 9 - 0
app/src/main/java/com/example/modifier/constants/Packages.kt

@@ -0,0 +1,9 @@
+package com.example.modifier.constants
+
+const val PACKAGE_GMS = "com.google.android.gms"
+
+const val PACKAGE_GSF = "com.google.android.gsf"
+
+const val PACKAGE_MESSAGING = "com.google.android.apps.messaging"
+
+const val PACKAGE_CLASH = "com.github.metacubex.clash.meta"

+ 22 - 0
app/src/main/java/com/example/modifier/constants/ShellCommands.kt

@@ -0,0 +1,22 @@
+package com.example.modifier.constants
+
+import com.example.modifier.BuildConfig
+
+const val CMD_MESSAGING_APP = "am start com.google.android.apps.messaging"
+
+const val CMD_RCS_SETTINGS_ACTIVITY =
+    "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity"
+
+const val CMD_MSG_SETTINGS_ACTIVITY =
+    "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.SettingsActivity"
+
+const val CMD_CONVERSATION_LIST_ACTIVITY =
+    "am start com.google.android.apps.messaging/.ui.ConversationListActivity"
+
+const val CMD_BACK = "input keyevent KEYCODE_BACK"
+
+const val CMD_HOME = "input keyevent KEYCODE_HOME"
+
+const val CMD_BACK_APP = "am start ${BuildConfig.APPLICATION_ID}/.MainActivity"
+
+const val CMD_START_PLAY_STORE = "am start com.android.vending"

+ 37 - 0
app/src/main/java/com/example/modifier/extension/packageManager.kt

@@ -0,0 +1,37 @@
+package com.example.modifier.extension
+
+fun String.suspend(): String {
+    return "pm suspend $this"
+}
+
+fun String.resume(): String {
+    return "pm unsuspend $this"
+}
+
+fun String.clear(): String {
+    return "pm clear $this"
+}
+
+fun String.kill(): String {
+    return "am force-stop $this"
+}
+
+fun String.hide(): String {
+    return "pm hide $this"
+}
+
+fun String.unhide(): String {
+    return "pm unhide $this"
+}
+
+fun String.disable(): String {
+    return "pm disable-user $this"
+}
+
+fun String.enable(): String {
+    return "pm enable $this"
+}
+
+fun String.launch(): String {
+    return "am start $this"
+}

+ 18 - 19
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -18,7 +18,6 @@ import android.util.DisplayMetrics
 import android.util.Log
 import android.view.Gravity
 import android.view.LayoutInflater
-import android.view.MenuItem
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.OnTouchListener
@@ -38,12 +37,6 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Observer
 import androidx.lifecycle.liveData
 import com.example.modifier.BuildConfig
-import com.example.modifier.CMD_BACK
-import com.example.modifier.CMD_CONVERSATION_LIST_ACTIVITY
-import com.example.modifier.CMD_KILL_GMS
-import com.example.modifier.CMD_KILL_MESSAGING_APP
-import com.example.modifier.CMD_MESSAGING_APP
-import com.example.modifier.CMD_RCS_SETTINGS_ACTIVITY
 import com.example.modifier.Global
 import com.example.modifier.Global.backup
 import com.example.modifier.Global.changeClashProfile
@@ -54,11 +47,19 @@ import com.example.modifier.Global.stopClash
 import com.example.modifier.R
 import com.example.modifier.TraverseResult
 import com.example.modifier.Utils
+import com.example.modifier.constants.CMD_BACK
+import com.example.modifier.constants.CMD_CONVERSATION_LIST_ACTIVITY
+import com.example.modifier.constants.CMD_MESSAGING_APP
+import com.example.modifier.constants.CMD_RCS_SETTINGS_ACTIVITY
+import com.example.modifier.constants.PACKAGE_GMS
+import com.example.modifier.constants.PACKAGE_MESSAGING
 import com.example.modifier.data.AppDatabase
 import com.example.modifier.data.BackupItemDao
 import com.example.modifier.databinding.FloatingWindowBinding
 import com.example.modifier.enums.RcsConfigureState
 import com.example.modifier.enums.RcsConnectionStatus
+import com.example.modifier.extension.kill
+import com.example.modifier.hasRootAccess
 import com.example.modifier.http.KtorClient
 import com.example.modifier.http.api.DeviceApi
 import com.example.modifier.http.api.RcsNumberApi
@@ -72,7 +73,7 @@ import com.example.modifier.model.TaskAction
 import com.example.modifier.model.TaskExecutionResult
 import com.example.modifier.model.TelephonyConfig
 import com.example.modifier.serializer.Json
-import com.example.modifier.ui.shellRun
+import com.example.modifier.shellRun
 import com.google.android.material.color.DynamicColors
 import io.ktor.client.call.body
 import io.ktor.client.plugins.resources.get
@@ -310,7 +311,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             preparing.postValue(true)
             val hasRoot = run checkRoot@{
                 repeat(30) {
-                    if (Global.hasRoot()) {
+                    if (hasRootAccess()) {
                         return@checkRoot true
                     }
                     delay(1000)
@@ -325,11 +326,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             } else {
                 delay(5000)
             }
-            if (Global.hasRoot()) {
-                Global.setupSystem()
-                if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
-                    Global.killPhoneProcess(force = false)
-                }
+            Global.setupSystem()
+            if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
+                Global.killPhoneProcess(force = false)
             }
             preparing.postValue(false)
         }
@@ -453,7 +452,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 }
                 updateCountUI()
             }
-            Utils.runAsRoot(CMD_BACK)
+            shellRun(CMD_BACK)
             mSocket.emit(
                 "callback",
                 JSONObject(
@@ -472,7 +471,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             } else if (cleanCount in 1..counter) {
                 delay(3000)
                 Global.clearConv();
-                Utils.runAsRoot(CMD_MESSAGING_APP)
+                shellRun(CMD_MESSAGING_APP)
                 delay(3000)
                 counter = 0
             } else {
@@ -968,7 +967,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                     }
                     if (switchAppear != true) {
                         shellRun(
-                            CMD_KILL_GMS, CMD_KILL_MESSAGING_APP, "sleep 1",
+                            PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1",
                             CMD_MESSAGING_APP
                         )
                         switchAppear = waitForRcsState(
@@ -1031,7 +1030,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                     }
                     if (switchAppear != true) {
                         shellRun(
-                            CMD_KILL_GMS, CMD_KILL_MESSAGING_APP, "sleep 1",
+                            PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1",
                             CMD_MESSAGING_APP
                         )
                         switchAppear = waitForRcsState(
@@ -1329,7 +1328,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             Log.i(TAG, "requestNumber success")
             if (isOldVersion()) {
                 delay(5000)
-                shellRun(CMD_KILL_MESSAGING_APP, "sleep 1", CMD_MESSAGING_APP)
+                shellRun(PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP)
                 delay(2000)
             }
         } else {

+ 0 - 44
app/src/main/java/com/example/modifier/ui/Helpers.kt

@@ -1,44 +0,0 @@
-package com.example.modifier.ui
-
-import android.util.Log
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-private const val TAG = "Modifier"
-suspend fun shellRun(vararg commands: String): Map<String, String> {
-    var output = ""
-    var error = ""
-    Log.i(TAG, "shellRun: \t${commands.joinToString("\n\t\t\t")}")
-    withContext(Dispatchers.IO) {
-        val p = ProcessBuilder("su", "-M").start()
-        p.outputStream.bufferedWriter().use { writer ->
-            commands.forEach { command ->
-                writer.write(command)
-                writer.newLine()
-                writer.flush()
-            }
-        }
-        coroutineScope {
-            launch {
-                p.inputStream.bufferedReader().useLines {
-                    it.forEach {
-                        output += it + "\n"
-                        Log.i(TAG, "shellRunOut: $it")
-                    }
-                }
-            }
-            launch {
-                p.errorStream.bufferedReader().useLines {
-                    it.forEach {
-                        error += it + "\n"
-                        Log.e(TAG, "shellRunErr: $it")
-                    }
-                }
-            }
-        }
-        p.waitFor()
-    }
-    return mapOf("output" to output, "error" to error)
-}

+ 32 - 40
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -31,25 +31,21 @@ 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.ChannelApi
 import com.example.modifier.http.api.RcsNumberApi
 import com.example.modifier.http.request.RcsNumberRequest
-import com.example.modifier.http.response.GetChannelListResponse
 import com.example.modifier.http.response.RcsNumberResponse
-import com.example.modifier.model.Channel
 import com.example.modifier.model.TelephonyConfig
 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.post
 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.launch
 import kotlinx.coroutines.withContext
 import org.apache.commons.lang3.RandomStringUtils
@@ -57,7 +53,6 @@ import org.apache.commons.lang3.StringUtils
 import org.apache.commons.validator.routines.UrlValidator
 import java.util.Objects
 import java.util.Optional
-import java.util.concurrent.ScheduledThreadPoolExecutor
 
 @SuppressLint("SetTextI18n", "MissingPermission", "HardwareIds", "NewApi")
 class SettingsFragment : Fragment() {
@@ -66,8 +61,6 @@ class SettingsFragment : Fragment() {
     private lateinit var binding: FragmentSettingsBinding
 
     var handler: Handler = Handler(Looper.getMainLooper())
-    var executor: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(8)
-    var channels: List<Channel> = emptyList()
 
     lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
 
@@ -155,17 +148,17 @@ class SettingsFragment : Fragment() {
             val modifierService = instance
             modifierService?.connect()
 
-            Utils.makeLoadingButton(context, binding.btnServer)
-            binding.btnServer.isEnabled = false
-            handler.postDelayed({
+            lifecycleScope.launch {
+                Utils.makeLoadingButton(context, binding.btnServer)
+                binding.btnServer.isEnabled = false
+                delay(500)
                 binding.btnServer.setIconResource(R.drawable.ic_done)
                 binding.btnServer.text = "OK"
-                handler.postDelayed({
-                    binding.btnServer.isEnabled = true
-                    binding.btnServer.icon = null
-                    binding.btnServer.text = "Save"
-                }, 1500)
-            }, 500)
+                delay(500)
+                binding.btnServer.isEnabled = true
+                binding.btnServer.icon = null
+                binding.btnServer.text = "Save"
+            }
         }
 
         binding.btnRequest.setOnClickListener { v: View? ->
@@ -353,31 +346,30 @@ class SettingsFragment : Fragment() {
     private fun onSave() {
         Utils.makeLoadingButton(context, binding.btnSave)
         binding.btnSave.isEnabled = false
-        executor.execute {
-            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()
+        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()
+                    )
                 )
-            )
-            handler.post {
-                binding.btnSave.setIconResource(R.drawable.ic_done)
-                binding.btnSave.text = "OK"
-                handler.postDelayed({
-                    binding.btnSave.isEnabled = true
-                    binding.btnSave.icon = null
-                    binding.btnSave.text = "Save"
-                }, 1500)
             }
+            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"
         }
     }
 }

+ 116 - 101
app/src/main/java/com/example/modifier/ui/utils/UtilsFragment.kt

@@ -1,8 +1,6 @@
 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
@@ -10,31 +8,32 @@ 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 androidx.lifecycle.lifecycleScope
 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.Global.resumePackage
+import com.example.modifier.Global.suspendPackage
 import com.example.modifier.R
 import com.example.modifier.Utils
+import com.example.modifier.constants.CMD_BACK_APP
+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.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.service.ModifierService
+import com.example.modifier.shellRun
+import com.example.modifier.syncTime
 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
@@ -47,15 +46,10 @@ 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)
     }
@@ -76,7 +70,7 @@ class UtilsFragment : Fragment() {
         }
         binding.btnSend.setOnClickListener { v: View? ->
             Utils.makeLoadingButton(context, binding.btnSend)
-            CoroutineScope(Dispatchers.Main).launch {
+            lifecycleScope.launch {
                 delay(1000L)
 
                 val otp = binding.etOtp.text.toString()
@@ -97,7 +91,7 @@ class UtilsFragment : Fragment() {
         binding.btnClearConv.setOnClickListener { v: View? ->
             binding.btnClearConv.isEnabled = false
             Utils.makeLoadingButton(context, binding.btnClearConv)
-            CoroutineScope(Dispatchers.IO).launch {
+            lifecycleScope.launch {
                 clearConv()
                 withContext(Dispatchers.Main) {
                     binding.btnClearConv.setIconResource(R.drawable.ic_done)
@@ -112,11 +106,11 @@ class UtilsFragment : Fragment() {
 
         binding.btnCheckA10y.setOnClickListener {
             Utils.makeLoadingButton(context, binding.btnCheckA10y)
-            CoroutineScope(Dispatchers.Main).launch {
+            lifecycleScope.launch {
                 var a10y: Boolean? = null
                 withContext(Dispatchers.IO) {
                     a10y = ModifierService.instance?.checkRcsAvailability()
-                    Utils.runAsRoot(CMD_BACK_APP)
+                    shellRun(CMD_BACK_APP)
                 }
                 if (isAdded) {
                     MaterialAlertDialogBuilder(requireContext())
@@ -137,44 +131,57 @@ class UtilsFragment : Fragment() {
         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)
+            lifecycleScope.launch {
+
+                val packages = mutableListOf<String>()
+                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)
-            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)
+            lifecycleScope.launch {
+                val packages = mutableListOf<String>()
+                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 {
-            CoroutineScope(Dispatchers.Main).launch {
+            lifecycleScope.launch {
                 withContext(Dispatchers.IO) {
                     Frida.start()
                 }
@@ -183,7 +190,7 @@ class UtilsFragment : Fragment() {
         }
 
         binding.btnStopFrida.setOnClickListener {
-            CoroutineScope(Dispatchers.Main).launch {
+            lifecycleScope.launch {
                 withContext(Dispatchers.IO) {
                     Frida.stop()
                 }
@@ -194,42 +201,43 @@ class UtilsFragment : Fragment() {
         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"
+            lifecycleScope.launch {
+                val success = withContext(Dispatchers.IO) {
+                    Global.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)
-            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"
+            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 {
-            CoroutineScope(Dispatchers.IO).launch {
+            lifecycleScope.launch {
                 try {
                     val config = KtorClient.get(SysConfigApi.Id(SysConfigApi(), "modifier_apk"))
                         .body<SysConfigResponse>()
@@ -241,7 +249,7 @@ class UtilsFragment : Fragment() {
         }
 
         binding.btnUpdateMessage.setOnClickListener {
-            CoroutineScope(Dispatchers.IO).launch {
+            lifecycleScope.launch {
                 try {
                     val config = KtorClient.get(SysConfigApi.Id(SysConfigApi(), "message_apk"))
                         .body<SysConfigResponse>()
@@ -262,20 +270,20 @@ class UtilsFragment : Fragment() {
         val gsf = binding.cbGsf.isChecked
         val gms = binding.cbGms.isChecked
         val sms = binding.cbSms.isChecked
-        CoroutineScope(Dispatchers.IO).launch {
-            if (all) {
-                resetAll()
-            } else {
-                clear(gsf, gms, sms)
-            }
-            withContext(Dispatchers.Main) {
-                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"
+        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"
         }
 
     }
@@ -283,21 +291,28 @@ class UtilsFragment : Fragment() {
     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)
+        lifecycleScope.launch {
+            withContext(Dispatchers.IO) {
+                val packages = mutableListOf<String>()
+                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"
         }
     }
 
@@ -353,7 +368,7 @@ class UtilsFragment : Fragment() {
                         }
                     }
                 Log.i("Modifier", "A file saved to ${file.path}")
-                Utils.runAsRoot("pm install -d -r ${file.path}")
+                shellRun("pm install -d -r ${file.path}")
             } catch (e: Exception) {
                 Log.e("Modifier", "Failed to download apk", e)