x1ongzhu 1 жил өмнө
parent
commit
d90caacc5c

+ 1 - 1
app/build.gradle

@@ -24,7 +24,7 @@ android {
         applicationId "com.example.modifier"
         minSdk 26
         targetSdk 34
-        versionCode 110
+        versionCode 112
         versionName "1.0.1"
         archivesBaseName = "modifier-${versionCode}"
 

+ 1 - 0
app/src/main/AndroidManifest.xml

@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
     <application
         android:name=".MyApplication"

+ 110 - 70
app/src/main/java/com/example/modifier/Global.kt

@@ -8,8 +8,8 @@ import android.util.Log
 import androidx.core.content.ContextCompat
 import com.example.modifier.data.BackupItem
 import com.example.modifier.data.BackupItemDao
+import com.example.modifier.enums.RcsConfigureState
 import com.example.modifier.http.KtorClient
-import com.example.modifier.model.Backup
 import com.example.modifier.model.TelephonyConfig
 import com.example.modifier.service.ModifierService
 import com.example.modifier.ui.shellRun
@@ -34,6 +34,7 @@ import java.time.format.DateTimeFormatter
 import java.util.Base64
 import java.util.Date
 import java.util.Locale
+import kotlin.time.Duration.Companion.minutes
 
 object Global {
     @JvmField
@@ -141,29 +142,56 @@ object Global {
         }
     }
 
-    fun saveMock() {
-        val content = Utils.getContext().assets.open("us_numbers.txt").bufferedReader().use {
-            it.readText()
+    private fun isOldVersion(): Boolean {
+        val info =
+            Utils.getContext().packageManager.getPackageInfo("com.google.android.apps.messaging", 0)
+        var oldVersion = false
+        if (info != null) {
+            if (info.versionCode < 170545910) {
+                oldVersion = true
+            }
         }
-        // get random number
-        content.split("\n")
-            .filter { it.isNotBlank() }
-            .shuffled()
-            .firstOrNull()
-            ?.let {
-                save(
-                    TelephonyConfig(
-                        number = it,
-                        mcc = "310",
-                        mnc = "240",
-                        iccid = genICCID("310", "1"),
-                        "310240" + RandomStringUtils.randomNumeric(9),
-                        Utils.generateIMEI(),
-                        "us",
-                        "1"
-                    )
+        return oldVersion
+    }
+
+    fun saveMock() {
+        if (isOldVersion()) {
+            save(
+                TelephonyConfig(
+                    number = "910" + RandomStringUtils.randomNumeric(6),
+                    mcc = "520",
+                    mnc = "01",
+                    iccid = genICCID("520", "66"),
+                    "52001" + RandomStringUtils.randomNumeric(10),
+                    Utils.generateIMEI(),
+                    "th",
+                    "66"
                 )
-            }
+            )
+        } else {
+            val content = Utils.getContext().assets.open("us_numbers.txt").bufferedReader().use {
+                it.readText()
+            }
+            // get random number
+            content.split("\n")
+                .filter { it.isNotBlank() }
+                .shuffled()
+                .firstOrNull()
+                ?.let {
+                    save(
+                        TelephonyConfig(
+                            number = it,
+                            mcc = "310",
+                            mnc = "240",
+                            iccid = genICCID("310", "1"),
+                            "310240" + RandomStringUtils.randomNumeric(9),
+                            Utils.generateIMEI(),
+                            "us",
+                            "1"
+                        )
+                    )
+                }
+        }
     }
 
     @JvmStatic
@@ -294,6 +322,34 @@ object Global {
         }
     }
 
+    fun sqlite3path(): String {
+        val context = Utils.getContext()
+        val dataDir = ContextCompat.getDataDir(context)
+        val binDir = File(dataDir, "bin")
+        val dbDir = File(dataDir, "providerDB")
+        if (!binDir.exists()) {
+            Utils.copyAssetFolder(context.assets, "bin", binDir.path)
+            Utils.copyAssetFolder(context.assets, "providerDB", dbDir.path)
+            binDir.listFiles()?.forEach {
+                it.setExecutable(true)
+            }
+        }
+        Log.i("Modifier", "arch: " + Build.SUPPORTED_ABIS.joinToString(", "))
+        val file = File(dataDir, "bin/sqlite3.arm")
+        file.setExecutable(true)
+        return file.path
+    }
+
+    fun copyDB(): File {
+        val context = Utils.getContext()
+        val dataDir = ContextCompat.getDataDir(context)
+        val dbDir = File(dataDir, "providerDB")
+        if (!dbDir.exists()) {
+            Utils.copyAssetFolder(context.assets, "providerDB", dbDir.path)
+        }
+        return dbDir
+    }
+
     @JvmStatic
     fun clearConv() {
         val context = Utils.getContext()
@@ -303,43 +359,19 @@ object Global {
             return
         }
         try {
-            val dataDir = ContextCompat.getDataDir(context)
-            val binDir = File(dataDir, "bin")
-            val dbDir = File(dataDir, "providerDB")
-            if (!binDir.exists()) {
-                Utils.copyAssetFolder(context.assets, "bin", binDir.path)
-                Utils.copyAssetFolder(context.assets, "providerDB", dbDir.path)
-            }
-            Log.i("Modifier", "arch: " + Build.SUPPORTED_ABIS.joinToString(", "))
-
-            var arch: String? = null
-            for (supportedAbi in Build.SUPPORTED_ABIS) {
-                if ("x86" == supportedAbi) {
-                    arch = "x86"
-                } else if ("x86_64" == supportedAbi) {
-                    arch = "x64"
-                } else if ("arm64-v8a" == supportedAbi) {
-                    arch = "arm64"
-                } else if ("armeabi-v7a" == supportedAbi) {
-                    arch = "arm"
-                }
-                if (StringUtils.isNoneBlank(arch)) {
-                    suspend(sms = true)
-                    val binPath = File(dataDir, "bin/sqlite3.$arch").path
-                    val providerDBPath = File(dataDir, "providerDB/mmssms.db").path
-                    Log.i("Modifier", "sqlite3 binPath: $binPath")
-                    Utils.runAsRoot(
-                        "chmod +x $binPath",
-                        "$binPath /data/data/com.google.android.apps.messaging/databases/bugle_db \"DELETE FROM conversations;\"",
-                        "$binPath /data/data/com.google.android.apps.messaging/databases/bugle_db \"DELETE FROM messages;\"",
-                        "cp $providerDBPath /data/data/com.android.providers.telephony/databases/mmssms.db",
-                        "chmod 660 /data/data/com.android.providers.telephony/databases/mmssms.db",
-                        "echo ok"
-                    )
-                    unsuspend(sms = true)
-                    break
-                }
-            }
+            val sqlite3 = sqlite3path()
+            val dbDir = copyDB()
+            val dbPath = File(dbDir, "mmssms.db").path
+            suspend(sms = true)
+            Utils.runAsRoot(
+                "$sqlite3 /data/data/com.google.android.apps.messaging/databases/bugle_db \"DELETE FROM conversations;\"",
+                "$sqlite3 /data/data/com.google.android.apps.messaging/databases/bugle_db \"DELETE FROM messages;\"",
+                "cp $dbPath /data/data/com.android.providers.telephony/databases/mmssms.db",
+                "chmod 660 /data/data/com.android.providers.telephony/databases/mmssms.db",
+                "chown radio:radio /data/data/com.android.providers.telephony/databases/mmssms.db",
+                "echo ok"
+            )
+            unsuspend(sms = true)
         } catch (e: Exception) {
             e.printStackTrace()
         }
@@ -347,15 +379,14 @@ object Global {
 
     @JvmStatic
     suspend fun resetAll() {
-        val context = Utils.getContext()
         try {
-            val dataDir = ContextCompat.getDataDir(context)
-            Utils.copyAssetFolder(context.assets, "providerDB", File(dataDir, "providerDB").path)
-            val providerDBPath = File(dataDir, "providerDB/mmssms.db").path
-
+            val sqlite3 = sqlite3path()
+            val dbDir = copyDB()
+            val dbPath = File(dbDir, "mmssms.db").path
             shellRun(
-                "cp $providerDBPath /data/data/com.android.providers.telephony/databases/mmssms.db",
+                "cp $dbPath /data/data/com.android.providers.telephony/databases/mmssms.db",
                 "chmod 660 /data/data/com.android.providers.telephony/databases/mmssms.db",
+                "chown radio:radio /data/data/com.android.providers.telephony/databases/mmssms.db",
                 CMD_SUSPEND_MESSAGING_APP,
                 CMD_KILL_MESSAGING_APP,
                 CMD_CLEAR_MESSAGING_APP,
@@ -543,7 +574,7 @@ object Global {
 
     @JvmStatic
     suspend fun rebooted(): Boolean {
-        if (shellRun("getprop rebooted").contains("yes")) {
+        if (shellRun("getprop rebooted")["output"]!!.contains("yes")) {
             return false
         }
         shellRun("setprop rebooted yes")
@@ -617,7 +648,7 @@ object Global {
             type = type
         )
 
-        backupItemDao.findBackup(telephonyConfig.country, telephonyConfig.number)?.let {
+        backupItemDao.findBackupForNumber(telephonyConfig.country, telephonyConfig.number)?.let {
             File(it.path).deleteRecursively()
             backupItemDao.delete(it)
             backup.sendCount += it.sendCount
@@ -663,12 +694,21 @@ object Global {
                 cmds.add("chown -R $uid:$uid /data/user_de/0/$pkg")
             }
             if (File("${backup.path}/data/$pkg/external").exists()) {
-                cmds.add("rm -rf /sdcard/Android/data/$pkg/*")
-                cmds.add("cp -r ${backup.path}/data/$pkg/external/* /sdcard/Android/data/$pkg")
-                cmds.add("chown -R $uid:$uid /sdcard/Android/data/$pkg")
+                if (File("${backup.path}/data/$pkg/external").listFiles()?.isNotEmpty() == true) {
+                    cmds.add("rm -rf /sdcard/Android/data/$pkg/*")
+                    cmds.add("cp -r ${backup.path}/data/$pkg/external/* /sdcard/Android/data/$pkg")
+                    cmds.add("chown -R $uid:$uid /sdcard/Android/data/$pkg")
+                }
             }
         }
         cmds.addAll(packages.reversed().map { "pm unsuspend $it" })
+        cmds.addAll(listOf(CMD_MESSAGING_APP))
         shellRun(*cmds.toTypedArray())
+        delay(2000)
+        ModifierService.instance!!.toggleRcsSwitch(false)
+        delay(1000)
+        ModifierService.instance!!.toggleRcsSwitch(true)
+        ModifierService.instance!!.waitForRcsState(arrayOf(RcsConfigureState.CONFIGURED), 2.minutes)
+        shellRun("am start -a android.intent.action.SENDTO -d sms:+18583199738 --es sms_body \"Test\" --ez exit_on_sent false")
     }
 }

+ 1 - 0
app/src/main/java/com/example/modifier/MainActivity.kt

@@ -58,5 +58,6 @@ class MainActivity : AppCompatActivity() {
                 Utils.enableOverlay()
             }
         }
+        Utils.startPreferenceActivity("com.google")
     }
 }

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

@@ -1,10 +1,16 @@
 package com.example.modifier;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -19,6 +25,7 @@ import com.google.android.material.progressindicator.IndeterminateDrawable;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.xmlpull.v1.XmlPullParser;
 
 import java.io.DataOutputStream;
 import java.io.File;
@@ -300,4 +307,57 @@ public class Utils {
         return (int) (dp * scale + 0.5f);
     }
 
+    public static void startPreferenceActivity(String type) {
+        Context context = getContext();
+        AccountManager accountManager = AccountManager.get(context);
+        AuthenticatorDescription[] descriptions = accountManager.getAuthenticatorTypes();
+        AuthenticatorDescription neededDescription = null;
+        for (AuthenticatorDescription description : descriptions) {
+            if (description.type.equals(type)) {
+                neededDescription = description;
+                break;
+            }
+        }
+        if (neededDescription != null) {
+            String packageName = neededDescription.packageName;
+            int prefsId = neededDescription.accountPreferencesId;
+            try {
+                Resources resources = context.getPackageManager().getResourcesForApplication(packageName);
+                XmlResourceParser xpp = resources.getLayout(prefsId);
+                xpp.next();
+                String action = null;
+                String targetPackage = packageName; //default to the account pref package name...?
+                String targetClass = null;
+                int eventType = xpp.getEventType();
+                while (eventType != XmlPullParser.END_DOCUMENT) {
+                    if (eventType == XmlPullParser.START_TAG) {
+                        if (xpp.getName().equals("intent")) {
+                            int count = xpp.getAttributeCount();
+                            for (int i = 0; i < count; i++) {
+                                String name = xpp.getAttributeName(i);
+                                if (name.equals("action")) {
+                                    action = xpp.getAttributeValue(i);
+                                } else if (name.equals("targetPackage")) {
+                                    targetPackage = xpp.getAttributeValue(i);
+                                } else if (name.equals("targetClass")) {
+                                    targetClass = xpp.getAttributeValue(i);
+                                }
+                            }
+                        }
+                    }
+                    eventType = xpp.next();
+                }
+                if (action != null) {
+                    context.startActivity(new Intent(action));
+                } else if (targetClass != null) {
+                    context.startActivity(new Intent(context.createPackageContext(targetPackage, Context.CONTEXT_IGNORE_SECURITY), Class.forName(targetClass)));
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                e.printStackTrace();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+        }
+    }
 }

+ 4 - 2
app/src/main/java/com/example/modifier/data/BackupItemDao.kt

@@ -5,7 +5,6 @@ import androidx.room.Delete
 import androidx.room.Insert
 import androidx.room.Query
 import androidx.room.Update
-import kotlinx.coroutines.flow.Flow
 
 @Dao
 interface BackupItemDao {
@@ -26,5 +25,8 @@ interface BackupItemDao {
     suspend fun getAll(): List<BackupItem>
 
     @Query("SELECT * FROM backupitem WHERE country = :country AND number = :number limit 1")
-    suspend fun findBackup(country: String, number: String): BackupItem?
+    suspend fun findBackupForNumber(country: String, number: String): BackupItem?
+
+    @Query("SELECT * FROM backupitem WHERE number != :number AND (sendCount = 0 OR lastUse < :time) order by createdAt limit 1")
+    suspend fun findBackupForRestore(number: String, time: Long): BackupItem?
 }

+ 1 - 0
app/src/main/java/com/example/modifier/enums/RcsConfigureState.kt

@@ -4,6 +4,7 @@ enum class RcsConfigureState {
     NOT_CONFIGURED,
     READY,
     WAITING_FOR_DEFAULT_ON,
+    WAITING_FOR_TOS,
     WAITING_FOR_OTP,
     VERIFYING_OTP,
     CONFIGURING,

+ 1 - 0
app/src/main/java/com/example/modifier/enums/RcsConnectionStatus.kt

@@ -2,6 +2,7 @@ package com.example.modifier.enums
 
 enum  class RcsConnectionStatus {
     CONNECTED,
+    CONNECTING,
     DISCONNECTED,
     UNKNOWN
 }

+ 6 - 0
app/src/main/java/com/example/modifier/http/KtorClient.kt

@@ -7,6 +7,7 @@ import io.ktor.client.call.body
 import io.ktor.client.engine.okhttp.OkHttp
 import io.ktor.client.plugins.ClientRequestException
 import io.ktor.client.plugins.HttpResponseValidator
+import io.ktor.client.plugins.HttpTimeout
 import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
 import io.ktor.client.plugins.defaultRequest
 import io.ktor.client.plugins.resources.Resources
@@ -22,6 +23,11 @@ val KtorClient = HttpClient(OkHttp) {
             if (it) Global.serverUrl else "${Global.serverUrl}/"
         })
     }
+    install(HttpTimeout) {
+        requestTimeoutMillis = 180000
+        connectTimeoutMillis = 180000
+        socketTimeoutMillis = 180000
+    }
     install(Resources)
     install(ContentNegotiation) {
         json(Json {

+ 3 - 3
app/src/main/java/com/example/modifier/model/TaskConfig.kt

@@ -8,6 +8,6 @@ class TaskConfig(
     val rcsInterval: Long,
     val cleanCount: Int,
     val requestNumberInterval: Long,
-    val checkConnection: Boolean
-) {
-}
+    val checkConnection: Boolean,
+    val useBackup: Boolean
+)

+ 12 - 0
app/src/main/java/com/example/modifier/serializer/Json.kt

@@ -0,0 +1,12 @@
+package com.example.modifier.serializer
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+
+@OptIn(ExperimentalSerializationApi::class)
+val Json = Json {
+    prettyPrint = true
+    isLenient = true
+    ignoreUnknownKeys = true
+    explicitNulls = false
+}

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

@@ -0,0 +1,3 @@
+package com.example.modifier.service
+
+

+ 337 - 205
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -6,7 +6,6 @@ import android.annotation.SuppressLint
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.pm.ActivityInfo
 import android.graphics.PixelFormat
 import android.graphics.Rect
 import android.net.Uri
@@ -62,6 +61,7 @@ import com.example.modifier.model.SocketCallback
 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.google.android.material.color.DynamicColors
 import io.ktor.client.call.body
@@ -83,7 +83,6 @@ import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeout
 import kotlinx.coroutines.withTimeoutOrNull
 import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
 import org.apache.commons.collections4.queue.CircularFifoQueue
 import org.apache.commons.lang3.RandomStringUtils
 import org.json.JSONException
@@ -220,6 +219,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                             rcsConfigureState.postValue(RcsConfigureState.CONFIGURED)
                         } else if (line.contains("destState=WaitingForRcsDefaultOnState")) {
                             rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_DEFAULT_ON)
+                        } else if (line.contains("destState=WaitingForGoogleTosState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_TOS)
                         } else if (line.contains("destState=RetryState")) {
                             rcsConfigureState.postValue(RcsConfigureState.RETRY)
                         }
@@ -252,6 +253,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         AppDatabase.getDatabase(this).itemDao()
     }
 
+    private var requestMode = 1;
+    private var currentActivity = ""
+
     fun connect() {
         try {
             load()
@@ -317,17 +321,21 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     }
 
     override fun onAccessibilityEvent(event: AccessibilityEvent) {
-//        traverseNode(getRootInActiveWindow(), new TraverseResult());
-//        if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-//            if (event.packageName != null && event.className != null) {
-//                val componentName = ComponentName(
-//                    event.packageName.toString(),
-//                    event.className.toString()
-//                )
-//                Log.i(TAG, "packageName: ${event.packageName}")
-//                Log.i(TAG, "className: ${event.className}")
-//            }
-//        }
+        if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+            if (event.packageName != null && event.className != null) {
+                val componentName = ComponentName(
+                    event.packageName.toString(),
+                    event.className.toString()
+                )
+                try {
+                    packageManager.getActivityInfo(componentName, 0)
+                    currentActivity = componentName.flattenToShortString()
+                    Log.d(TAG, "Activity: $currentActivity")
+                } catch (_: Exception) {
+                }
+
+            }
+        }
     }
 
     override fun onInterrupt() {
@@ -382,6 +390,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             rcsInterval = taskAction.data.config.rcsInterval
             requestNumberInterval = taskAction.data.config.requestNumberInterval
             currentTaskId = taskAction.data.taskId
+            requestMode = if (taskAction.data.config.useBackup) 2 else 1
 
             if (taskAction.data.config.checkConnection) {
                 checkingConnection.postValue(true)
@@ -540,6 +549,10 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             return
         }
 
+        val packageInfo = packageManager.getPackageInfo(
+            node.packageName.toString(), 0
+        )
+
         val className = node.className.toString()
         val name = node.viewIdResourceName
         val text = Optional.ofNullable(node.text).map { obj: CharSequence -> obj.toString() }
@@ -560,7 +573,13 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             result.isRcsCapable = true
         }
 
-        if (text != null && (text.contains("Turn on RCS chats") || text.contains("开启 RCS 聊天功能"))) {
+        if ("com.google.android.apps.messaging:id/tombstone_message" == id) {
+            result.isRcsCapable = text.contains("聊天") || text.contains("Chatting with")
+        }
+
+        if (text != null && (text.contains("Turn on RCS chats") || text.contains("开启 RCS 聊天功能")
+                    || text.contains("Enable chat features") || text.contains("启用聊天功能"))
+        ) {
             fun findSwitch(node: AccessibilityNodeInfo): Boolean {
                 if ("com.google.android.apps.messaging:id/switchWidget" == node.viewIdResourceName) {
                     result.rcsSwitch = node
@@ -583,6 +602,13 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             }
         }
 
+        if ("android:id/title" == id) {
+            when (text) {
+                "状态:已连接" -> result.rcsConnectionStatus = RcsConnectionStatus.CONNECTED
+                "Status: Connected" -> result.rcsConnectionStatus = RcsConnectionStatus.CONNECTED
+            }
+        }
+
         if (node.childCount != 0) {
             for (i in 0 until node.childCount) {
                 traverseNode(node.getChild(i), result)
@@ -704,7 +730,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
 
         binding.btnReq.setOnClickListener {
             CoroutineScope(Dispatchers.IO).launch {
-                requestNumber(true)
+                requestNumber(reset = false, noBackup = true)
             }
         }
         binding.btnInspect.setOnClickListener {
@@ -768,7 +794,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
     }
 
-    private suspend fun waitForRcsState(
+    suspend fun waitForRcsState(
         states: Array<RcsConfigureState>,
         timeout: Duration
     ): RcsConfigureState? {
@@ -800,12 +826,10 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     }
 
     suspend fun toggleRcsSwitch(on: Boolean, retry: Int = 3): Boolean {
-
         val res = TraverseResult()
 
         shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 0.5")
         val success = run repeatBlock@{
-
             repeat(retry) {
                 res.rcsSwitch = null
                 traverseNode(rootInActiveWindow, res)
@@ -820,19 +844,29 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                     if (on) {
                         shellRun(
                             "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
-                            CMD_BACK, "sleep 0.5",
-                            CMD_RCS_SETTINGS_ACTIVITY, "sleep 1",
                         )
+                        if (isOldVersion()) {
+                            rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
+                                .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+                            delay(1000)
+                        }
+                        while ("com.google.android.apps.messaging/.ui.appsettings.RcsSettingsActivity" == currentActivity) {
+                            shellRun(CMD_BACK)
+                            delay(500)
+                        }
+                        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
                     } else {
                         shellRun(
                             "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
                         )
                         rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
                             .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
-                        shellRun(
-                            "sleep 0.5", CMD_BACK, "sleep 0.5",
-                            CMD_RCS_SETTINGS_ACTIVITY, "sleep 0.5",
-                        )
+                        delay(1000)
+                        while ("com.google.android.apps.messaging/.ui.appsettings.RcsSettingsActivity" == currentActivity) {
+                            shellRun(CMD_BACK)
+                            delay(500)
+                        }
+                        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
                     }
                     res.rcsSwitch = null
                     traverseNode(rootInActiveWindow, res)
@@ -843,73 +877,152 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             }
             false
         }
-        shellRun(CMD_BACK, "sleep 0.5")
+        while ("com.google.android.apps.messaging/.ui.appsettings.RcsSettingsActivity" == currentActivity) {
+            shellRun(CMD_BACK)
+            delay(500)
+        }
         return success
     }
 
+    private fun isOldVersion(): Boolean {
+        val info = packageManager.getPackageInfo("com.google.android.apps.messaging", 0)
+        var oldVersion = false
+        if (info != null) {
+            if (info.versionCode < 170545910) {
+                oldVersion = true
+            }
+        }
+        return oldVersion
+    }
+
     private suspend fun reset() {
-        withTimeout(1.hours) {
-            while (true) {
-                delay(100)
-                withContext(Dispatchers.Main) {
-                    binding.tvLog.text = "Waiting for RCS switch on..."
-                }
-                rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
-                Global.saveMock()
-                resetAll()
-                var switchAppear = waitForRcsState(
-                    arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
-                    1.minutes
-                )?.let {
-                    it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
+        if (isOldVersion()) {
+            withTimeout(1.hours) {
+                while (true) {
+                    delay(100)
+                    withContext(Dispatchers.Main) {
+                        binding.tvLog.text = "Waiting for RCS switch on..."
+                    }
+                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                    Global.saveMock()
+                    resetAll()
+                    var switchAppear = waitForRcsState(
+                        arrayOf(RcsConfigureState.WAITING_FOR_TOS),
+                        1.minutes
+                    )?.let {
+                        it == RcsConfigureState.WAITING_FOR_TOS
+                    }
+                    if (switchAppear != true) {
+                        shellRun(
+                            CMD_KILL_GMS, CMD_KILL_MESSAGING_APP, "sleep 1",
+                            CMD_MESSAGING_APP
+                        )
+                        switchAppear = waitForRcsState(
+                            arrayOf(RcsConfigureState.WAITING_FOR_TOS),
+                            2.minutes
+                        )?.let {
+                            it == RcsConfigureState.WAITING_FOR_TOS
+                        }
+                        if (switchAppear != true) {
+                            Log.e(TAG, "RCS not entered default on state, retrying...")
+                            continue
+                        }
+                    }
+                    if (!toggleRcsSwitch(false)) {
+                        Log.e(TAG, "RCS switch not turned off, retrying...")
+                        continue
+                    }
+                    if (!toggleRcsSwitch(true)) {
+                        Log.e(TAG, "RCS switch not turned on, retrying...")
+                        continue
+                    }
+                    var resetSuccess = waitForRcsState(
+                        arrayOf(
+                            RcsConfigureState.READY
+                        ), 30.seconds
+                    ).let { it == RcsConfigureState.READY }
+                    if (!resetSuccess) {
+                        toggleRcsSwitch(false)
+                        delay(1000)
+                        toggleRcsSwitch(true)
+                        resetSuccess = waitForRcsState(
+                            arrayOf(
+                                RcsConfigureState.READY
+                            ), 1.minutes
+                        ).let { it == RcsConfigureState.READY }
+                    }
+                    Log.i(TAG, "waitForRcsState: $resetSuccess")
+                    requestNumberCount = 0
+                    if (resetSuccess) {
+                        delay(3000)
+                        break
+                    }
                 }
-                if (switchAppear != true) {
-                    shellRun(
-                        CMD_KILL_GMS, CMD_KILL_MESSAGING_APP, "sleep 1",
-                        CMD_MESSAGING_APP
-                    )
-                    switchAppear = waitForRcsState(
+            }
+        } else {
+            withTimeout(1.hours) {
+                while (true) {
+                    delay(100)
+                    withContext(Dispatchers.Main) {
+                        binding.tvLog.text = "Waiting for RCS switch on..."
+                    }
+                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                    Global.saveMock()
+                    resetAll()
+                    var switchAppear = waitForRcsState(
                         arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
-                        2.minutes
+                        1.minutes
                     )?.let {
                         it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
                     }
                     if (switchAppear != true) {
-                        Log.e(TAG, "RCS not entered default on state, retrying...")
+                        shellRun(
+                            CMD_KILL_GMS, CMD_KILL_MESSAGING_APP, "sleep 1",
+                            CMD_MESSAGING_APP
+                        )
+                        switchAppear = waitForRcsState(
+                            arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
+                            2.minutes
+                        )?.let {
+                            it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
+                        }
+                        if (switchAppear != true) {
+                            Log.e(TAG, "RCS not entered default on state, retrying...")
+                            continue
+                        }
+                    }
+                    val switchOn = toggleRcsSwitch(true)
+                    if (!switchOn) {
+                        Log.e(TAG, "RCS switch not turned on, retrying...")
                         continue
                     }
-                }
-                val switchOn = toggleRcsSwitch(true)
-                if (!switchOn) {
-                    Log.e(TAG, "RCS switch not turned on, retrying...")
-                    continue
-                }
-                var resetSuccess = waitForRcsState(
-                    arrayOf(
-                        RcsConfigureState.READY
-                    ), 30.seconds
-                ).let { it == RcsConfigureState.READY }
-                if (!resetSuccess) {
-                    toggleRcsSwitch(false)
-                    delay(1000)
-                    toggleRcsSwitch(true)
-                    resetSuccess = waitForRcsState(
+                    var resetSuccess = waitForRcsState(
                         arrayOf(
                             RcsConfigureState.READY
-                        ), 1.minutes
+                        ), 30.seconds
                     ).let { it == RcsConfigureState.READY }
-                }
-                Log.i(TAG, "waitForRcsState: $resetSuccess")
-                requestNumberCount = 0
-                if (resetSuccess) {
-                    delay(3000)
-                    break
+                    if (!resetSuccess) {
+                        toggleRcsSwitch(false)
+                        delay(1000)
+                        toggleRcsSwitch(true)
+                        resetSuccess = waitForRcsState(
+                            arrayOf(
+                                RcsConfigureState.READY
+                            ), 1.minutes
+                        ).let { it == RcsConfigureState.READY }
+                    }
+                    Log.i(TAG, "waitForRcsState: $resetSuccess")
+                    requestNumberCount = 0
+                    if (resetSuccess) {
+                        delay(3000)
+                        break
+                    }
                 }
             }
         }
     }
 
-    private suspend fun requestNumber(reset: Boolean = false) {
+    private suspend fun requestNumber(reset: Boolean = false, noBackup: Boolean = false) {
         val color = ContextCompat.getColorStateList(binding.root.context, R.color.btn_color)
         binding.btnReq.backgroundTintList = color
         if (getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -926,170 +1039,189 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         backup(backupItemDao, "auto", sendCount)
 
         var requestSuccess = false
-        withTimeoutOrNull(1.hours) {
-            var needRest = reset
-            while (true) {
-                delay(200)
-                try {
-                    if (needRest &&
-                        !getSharedPreferences("settings", Context.MODE_PRIVATE)
-                            .getBoolean("do_not_reset", false)
-                    ) {
-                        reset()
-                        needRest = false
-                    }
+        if (requestMode == 2 && !noBackup) {
+            val backup = backupItemDao.findBackupForRestore(
+                Global.telephonyConfig.number,
+                System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000
+            )
+            if (backup != null) {
+                Global.restore(backup)
+                requestSuccess = true
+            }
+        }
 
-                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
-                    withContext(Dispatchers.Main) {
-                        binding.tvLog.text = "Requesting number..."
-                    }
+        if (!requestSuccess) {
+            withTimeoutOrNull(1.hours) {
+                var needRest = reset
+                while (true) {
+                    delay(200)
+                    try {
+                        if (needRest &&
+                            !getSharedPreferences("settings", Context.MODE_PRIVATE)
+                                .getBoolean("do_not_reset", false)
+                        ) {
+                            reset()
+                            needRest = false
+                        }
+
+                        rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                        withContext(Dispatchers.Main) {
+                            binding.tvLog.text = "Requesting number..."
+                        }
 
-                    val response = KtorClient.put(
-                        RcsNumberApi()
-                    ) {
-                        contentType(ContentType.Application.Json)
-                        setBody(
-                            RcsNumberRequest(
-                                deviceId = Utils.getUniqueID(),
-                                taskId = currentTaskId
+                        val response = KtorClient.put(
+                            RcsNumberApi()
+                        ) {
+                            contentType(ContentType.Application.Json)
+                            setBody(
+                                RcsNumberRequest(
+                                    deviceId = Utils.getUniqueID(),
+                                    taskId = currentTaskId
+                                )
                             )
-                        )
-                    }
-                    var rcsNumber = response.body<RcsNumberResponse>()
-                    Log.i(TAG, "requestNumber response: $rcsNumber")
+                        }
+                        var rcsNumber = response.body<RcsNumberResponse>()
+                        Log.i(TAG, "requestNumber response: $rcsNumber")
 
-                    withContext(Dispatchers.Main) {
-                        binding.tvLog.text = "Requesting success, waiting for logs..."
-                    }
+                        withContext(Dispatchers.Main) {
+                            binding.tvLog.text = "Requesting success, waiting for logs..."
+                        }
 
-                    Global.save(
-                        TelephonyConfig(
-                            rcsNumber.number,
-                            rcsNumber.mcc,
-                            rcsNumber.mnc,
-                            Global.genICCID(rcsNumber.mnc, rcsNumber.areaCode),
-                            rcsNumber.mcc + rcsNumber.mnc + RandomStringUtils.randomNumeric(
-                                15 - rcsNumber.mcc.length - rcsNumber.mnc.length
-                            ),
-                            Utils.generateIMEI(),
-                            rcsNumber.country,
-                            rcsNumber.areaCode
+                        Global.save(
+                            TelephonyConfig(
+                                rcsNumber.number,
+                                rcsNumber.mcc,
+                                rcsNumber.mnc,
+                                Global.genICCID(rcsNumber.mnc, rcsNumber.areaCode),
+                                rcsNumber.mcc + rcsNumber.mnc + RandomStringUtils.randomNumeric(
+                                    15 - rcsNumber.mcc.length - rcsNumber.mnc.length
+                                ),
+                                Utils.generateIMEI(),
+                                rcsNumber.country,
+                                rcsNumber.areaCode
+                            )
                         )
-                    )
 
-                    shellRun(CMD_MESSAGING_APP)
-                    withContext(Dispatchers.Main) {
-                        binding.tvLog.text = "Waiting for logs..."
-                    }
+                        shellRun(CMD_MESSAGING_APP)
+                        withContext(Dispatchers.Main) {
+                            binding.tvLog.text = "Waiting for logs..."
+                        }
 
-                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
-                        Log.e(TAG, "RCS number expired, retrying...")
-                        continue
-                    }
-                    var sendOtpTimeout = ChronoUnit.SECONDS.between(
-                        LocalDateTime.now(),
-                        rcsNumber.expiryTime
-                    ).seconds
-                    if (sendOtpTimeout < 60.seconds) {
-                        Log.e(TAG, "OTP timeout too short, retrying...")
-                        continue
-                    }
-                    if (sendOtpTimeout > 2.minutes) {
-                        sendOtpTimeout = 2.minutes
-                    }
-                    if (waitForRcsState(
-                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
-                            sendOtpTimeout
-                        ) != RcsConfigureState.WAITING_FOR_OTP
-                    ) {
-                        if (!toggleRcsSwitch(true)) {
-                            needRest = true
+                        if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
+                            Log.e(TAG, "RCS number expired, retrying...")
+                            continue
+                        }
+                        var sendOtpTimeout = ChronoUnit.SECONDS.between(
+                            LocalDateTime.now(),
+                            rcsNumber.expiryTime
+                        ).seconds
+                        if (sendOtpTimeout < 60.seconds) {
+                            Log.e(TAG, "OTP timeout too short, retrying...")
+                            continue
+                        }
+                        if (sendOtpTimeout > 2.minutes) {
+                            sendOtpTimeout = 2.minutes
+                        }
+                        if (waitForRcsState(
+                                arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                                sendOtpTimeout
+                            ) != RcsConfigureState.WAITING_FOR_OTP
+                        ) {
+                            if (!toggleRcsSwitch(true)) {
+                                needRest = true
+                            }
+                            Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
+                            continue
+                        }
+                        if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
+                            Log.e(TAG, "RCS number expired, retrying...")
+                            continue
                         }
-                        Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
-                        continue
-                    }
-                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
-                        Log.e(TAG, "RCS number expired, retrying...")
-                        continue
-                    }
 
-                    withTimeoutOrNull(60.seconds) {
-                        while (true) {
-                            try {
-                                rcsNumber = KtorClient.get(RcsNumberApi.Id(id = rcsNumber.id))
-                                    .body<RcsNumberResponse>()
-                                Log.i(TAG, "wait for otp response: $rcsNumber")
-                                if (rcsNumber.status == RcsNumberResponse.STATUS_SUCCESS || rcsNumber.status == RcsNumberResponse.STATUS_EXPIRED) {
-                                    break
+                        withTimeoutOrNull(60.seconds) {
+                            while (true) {
+                                try {
+                                    rcsNumber = KtorClient.get(RcsNumberApi.Id(id = rcsNumber.id))
+                                        .body<RcsNumberResponse>()
+                                    Log.i(TAG, "wait for otp response: $rcsNumber")
+                                    if (rcsNumber.status == RcsNumberResponse.STATUS_SUCCESS || rcsNumber.status == RcsNumberResponse.STATUS_EXPIRED) {
+                                        break
+                                    }
+                                } catch (exception: Exception) {
+                                    Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
                                 }
-                            } catch (exception: Exception) {
-                                Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
+                                delay(2.seconds)
                             }
-                            delay(2.seconds)
                         }
-                    }
 
-                    if (rcsNumber.status != RcsNumberResponse.STATUS_SUCCESS) {
-                        Log.e(TAG, "OTP not received, retrying...")
-                        continue
-                    }
-
-                    val match =
-                        Regex("Your Messenger verification code is G-(\\d{6})")
-                            .matchEntire(rcsNumber.message!!)
-                    if (match != null) {
-                        val otp = match.groupValues[1]
-                        Log.i(TAG, "OTP: $otp")
-                        val sender = "3538"
-                        val msg = "Your Messenger verification code is G-$otp"
-
-                        val configured = run configuring@{
-                            repeat(2) {
-                                Global.sendSmsIntent(sender, msg)
-                                val state =
-                                    waitForRcsState(
-                                        arrayOf(
-                                            RcsConfigureState.CONFIGURED,
-                                            RcsConfigureState.RETRY
-                                        ), 60.seconds
-                                    )
-                                when (state) {
-                                    RcsConfigureState.CONFIGURED -> {
-                                        return@configuring true
-                                    }
+                        if (rcsNumber.status != RcsNumberResponse.STATUS_SUCCESS) {
+                            Log.e(TAG, "OTP not received, retrying...")
+                            continue
+                        }
 
-                                    RcsConfigureState.RETRY -> {
+                        val match =
+                            Regex("Your Messenger verification code is G-(\\d{6})")
+                                .matchEntire(rcsNumber.message!!)
+                        if (match != null) {
+                            val otp = match.groupValues[1]
+                            Log.i(TAG, "OTP: $otp")
+                            val sender = "3538"
+                            val msg = "Your Messenger verification code is G-$otp"
+
+                            val configured = run configuring@{
+                                repeat(2) {
+                                    Global.sendSmsIntent(sender, msg)
+                                    val state =
                                         waitForRcsState(
-                                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
-                                            60.seconds
+                                            arrayOf(
+                                                RcsConfigureState.CONFIGURED,
+                                                RcsConfigureState.RETRY
+                                            ), 60.seconds
                                         )
-                                    }
-
-                                    else -> {
-                                        Log.e(TAG, "verifyOtp fail, retrying...")
+                                    when (state) {
+                                        RcsConfigureState.CONFIGURED -> {
+                                            return@configuring true
+                                        }
+
+                                        RcsConfigureState.RETRY -> {
+                                            waitForRcsState(
+                                                arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                                                60.seconds
+                                            )
+                                        }
+
+                                        else -> {
+                                            Log.e(TAG, "verifyOtp fail, retrying...")
+                                        }
                                     }
                                 }
+                                false
+                            }
+                            if (!configured) {
+                                Log.e(TAG, "RCS not configured, retrying...")
+                                continue
+                            } else {
+                                requestSuccess = true
+                                break
                             }
-                            false
-                        }
-                        if (!configured) {
-                            Log.e(TAG, "RCS not configured, retrying...")
-                            continue
-                        } else {
-                            requestSuccess = true
-                            break
                         }
+                    } catch (e: Exception) {
+                        Log.e(TAG, "requestNumberError: ${e.message}", e)
                     }
-                } catch (e: Exception) {
-                    Log.e(TAG, "requestNumberError: ${e.message}", e)
                 }
             }
         }
         requesting.postValue(false)
+
         if (requestSuccess) {
             sendCount = 0
             counter = 0
             Log.i(TAG, "requestNumber success")
+            if (isOldVersion()) {
+                delay(5000)
+                shellRun(CMD_KILL_MESSAGING_APP, "sleep 1", CMD_MESSAGING_APP)
+                delay(2000)
+            }
         } else {
             Log.e(TAG, "requestNumber failed")
             canSend = false

+ 7 - 0
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -8,6 +8,7 @@ import android.content.pm.PackageManager
 import android.os.Bundle
 import android.os.Handler
 import android.os.Looper
+import android.telephony.SmsManager
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
 import android.text.Editable
@@ -255,6 +256,10 @@ class SettingsFragment : Fragment() {
                     |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 =
@@ -272,6 +277,8 @@ class SettingsFragment : Fragment() {
                         |Country: ${info.countryIso}
                         |MCC: ${info.mccString}
                         |MNC: ${info.mncString}
+                        |carrierId: ${info.carrierId}
+                        |roaming: ${info.dataRoaming}
                         """.trimMargin()
                     }
                     MaterialAlertDialogBuilder(requireContext())