x1ongzhu 1 год назад
Родитель
Сommit
3fe7385a3a

+ 42 - 0
app/src/main/java/com/example/modifier/exception/RequestNumberException.kt

@@ -0,0 +1,42 @@
+package com.example.modifier.exception
+
+class RequestNumberException(code: Int) : Exception() {
+    override val message: String?
+
+    companion object {
+        object ErrorCode {
+            const val CODE_NUMBER_EXPIRED = 100
+            const val CODE_TIMEOUT_TOO_SHORT = 101
+            const val CODE_RCS_TOGGLED_OFF = 102
+            const val CODE_REPLAY_RETRY = 103
+            const val CODE_OTP_NOT_SENT = 104
+            const val CODE_OTP_NOT_RECEIVED = 105
+            const val CODE_OTP_VERIFY_FAILED = 106
+        }
+    }
+
+    init {
+        when (code) {
+            ErrorCode.CODE_NUMBER_EXPIRED -> {
+                message = "Number expired"
+            }
+
+            ErrorCode.CODE_TIMEOUT_TOO_SHORT -> {
+                message = "OTP timeout too short"
+            }
+
+            ErrorCode.CODE_RCS_TOGGLED_OFF -> {
+                message = "RCS toggled off or disappeared"
+            }
+
+            ErrorCode.CODE_REPLAY_RETRY -> {
+                message = "REPLAY_REQUEST or RETRY detected, may reset after 3 retry"
+            }
+
+            else -> {
+                message = "Unknown error"
+            }
+        }
+    }
+
+}

+ 19 - 0
app/src/main/java/com/example/modifier/http/api/DeviceApi.kt

@@ -1,5 +1,9 @@
 package com.example.modifier.http.api
 
+import com.example.modifier.http.ktorClient
+import com.example.modifier.http.response.DeviceResponse
+import io.ktor.client.call.body
+import io.ktor.client.plugins.resources.get
 import io.ktor.resources.Resource
 
 @Resource("api/device")
@@ -9,4 +13,19 @@ class DeviceApi() {
     class Id(val parent: DeviceApi = DeviceApi(), val id: String) {
 
     }
+
+    companion object {
+        suspend fun getDevice(deviceId: String): DeviceResponse {
+            try {
+                return ktorClient.get(Id(id = deviceId))
+                    .body<DeviceResponse>()
+            } catch (_: Exception) {
+            }
+            return DeviceResponse(
+                id = deviceId,
+                pinCountry = null,
+                clashProfile = null
+            )
+        }
+    }
 }

+ 95 - 1
app/src/main/java/com/example/modifier/http/api/RcsNumberApi.kt

@@ -1,10 +1,29 @@
 package com.example.modifier.http.api
 
+import android.util.Log
+import com.example.modifier.baseTag
+import com.example.modifier.http.ktorClient
+import com.example.modifier.http.request.RcsNumberRequest
+import com.example.modifier.http.response.RcsNumberResponse
+import io.ktor.client.call.body
+import io.ktor.client.plugins.resources.get
+import io.ktor.client.plugins.resources.post
+import io.ktor.client.plugins.resources.put
+import io.ktor.client.plugins.timeout
+import io.ktor.client.request.setBody
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
 import io.ktor.resources.Resource
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
+import kotlin.coroutines.coroutineContext
+import kotlin.time.Duration.Companion.seconds
 
 @Resource("api/rcs-number")
 class RcsNumberApi() {
-
     @Resource("{id}")
     class Id(val parent: RcsNumberApi = RcsNumberApi(), val id: Int) {
         @Resource("delete")
@@ -16,4 +35,79 @@ class RcsNumberApi() {
         @Resource("configured")
         class Configured(val parent: Id)
     }
+
+    companion object {
+        private const val TAG = "$baseTag/RcsNumberApi"
+        suspend fun getRcsNumber(
+            deviceId: String,
+            taskId: Int? = null,
+            pinCountry: String?
+        ): RcsNumberResponse {
+            val req = RcsNumberRequest(
+                deviceId = deviceId,
+                taskId = taskId,
+                country = pinCountry
+            )
+            val response = ktorClient.put(
+                RcsNumberApi()
+            ) {
+                contentType(ContentType.Application.Json)
+                setBody(req)
+                timeout {
+                    requestTimeoutMillis = 60 * 1000
+                    socketTimeoutMillis = 60 * 1000
+                }
+            }
+            return response.body<RcsNumberResponse>()
+        }
+
+        suspend fun notifyOtpState(id: Int) {
+            CoroutineScope(coroutineContext).launch {
+                try {
+                    ktorClient.post(Id.OtpState(Id(RcsNumberApi(), id)))
+                } catch (e: Exception) {
+                    Log.e(TAG, "Send OtpState Error: ${e.message}", e)
+                }
+            }
+        }
+
+        suspend fun notifyConfigured(id: Int) {
+            CoroutineScope(coroutineContext).launch {
+                try {
+                    ktorClient.post(Id.Configured(Id(RcsNumberApi(), id)))
+                } catch (e: Exception) {
+                    Log.e(TAG, "Send Configured Error: ${e.message}", e)
+                }
+            }
+        }
+
+        suspend fun waitForOtp(id: Int): String? {
+            return withTimeoutOrNull(60.seconds) {
+                while (true) {
+                    try {
+                        val rcsNumber =
+                            ktorClient.get(Id(id = id))
+                                .body<RcsNumberResponse>()
+                        Log.i(TAG, "wait for otp response: $rcsNumber")
+                        if (rcsNumber.status == RcsNumberResponse.STATUS_SUCCESS) {
+                            val match =
+                                Regex("Your Messenger verification code is G-(\\d{6})")
+                                    .find(rcsNumber.message!!)
+                            if (match != null) {
+                                val otp = match.groupValues[1]
+                                Log.i(TAG, "OTP: $otp")
+                                return@withTimeoutOrNull otp
+                            }
+                        } else if (rcsNumber.status == RcsNumberResponse.STATUS_EXPIRED) {
+                            break
+                        }
+                    } catch (exception: Exception) {
+                        Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
+                    }
+                    delay(2.seconds)
+                }
+                null
+            }
+        }
+    }
 }

+ 119 - 234
app/src/main/java/com/example/modifier/service/TaskRunner.kt

@@ -13,18 +13,16 @@ 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.AppState
 import com.example.modifier.enums.RcsConfigureState
 import com.example.modifier.enums.RcsConnectionStatus
 import com.example.modifier.enums.RequestNumberState
+import com.example.modifier.exception.RequestNumberException
+import com.example.modifier.exception.RequestNumberException.Companion.ErrorCode
 import com.example.modifier.extension.kill
 import com.example.modifier.http.api.DeviceApi
 import com.example.modifier.http.api.RcsNumberApi
 import com.example.modifier.http.api.SysConfigApi
 import com.example.modifier.http.ktorClient
-import com.example.modifier.http.request.RcsNumberRequest
-import com.example.modifier.http.response.DeviceResponse
-import com.example.modifier.http.response.RcsNumberResponse
 import com.example.modifier.http.response.SysConfigResponse
 import com.example.modifier.model.InstallApkAction
 import com.example.modifier.model.RunScriptAction
@@ -48,28 +46,19 @@ import com.example.modifier.utils.isOldVersion
 import com.example.modifier.utils.resetAll
 import com.example.modifier.utils.shellRun
 import com.example.modifier.utils.smsIntent
-import com.example.modifier.utils.spoofSmsIntent
+import com.example.modifier.utils.injectOTP
 import io.ktor.client.HttpClient
 import io.ktor.client.call.body
 import io.ktor.client.engine.okhttp.OkHttp
 import io.ktor.client.plugins.HttpResponseValidator
 import io.ktor.client.plugins.ServerResponseException
 import io.ktor.client.plugins.resources.get
-import io.ktor.client.plugins.resources.post
-import io.ktor.client.plugins.resources.put
-import io.ktor.client.plugins.timeout
 import io.ktor.client.request.prepareGet
-import io.ktor.client.request.setBody
-import io.ktor.http.ContentType
-import io.ktor.http.contentType
 import io.ktor.utils.io.ByteReadChannel
 import io.ktor.utils.io.core.isEmpty
 import io.ktor.utils.io.core.readBytes
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeout
 import kotlinx.coroutines.withTimeoutOrNull
@@ -363,6 +352,112 @@ class TaskRunner(
         }
     }
 
+    suspend fun requestNumberAtomic() {
+        appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.REQUEST)
+        googleMessageStateRepository.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
+        val device = DeviceApi.getDevice(appPrefsRepo.appPrefs.value.id)
+        val rcsNumber = RcsNumberApi.getRcsNumber(
+            appPrefsRepo.appPrefs.value.id,
+            currentTaskId,
+            device.pinCountry
+        )
+        Log.i(tag, "requestNumber response: $rcsNumber")
+
+        appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_1)
+
+        spoofedSimInfoRepo.updateSpoofedSimInfo(
+            SpoofedSimInfo(
+                numberId = rcsNumber.id,
+                number = rcsNumber.number,
+                mcc = rcsNumber.mcc,
+                mnc = rcsNumber.mnc,
+                iccid = genICCID(rcsNumber.mnc, rcsNumber.areaCode),
+                imsi = genIMSI(rcsNumber.mcc + rcsNumber.mnc),
+                imei = genIMEI(),
+                country = rcsNumber.country,
+                areaCode = rcsNumber.areaCode,
+                available = false,
+                carrierId = rcsNumber.carrierId,
+                carrierName = rcsNumber.carrierName,
+            )
+        )
+
+        shellRun(CMD_MESSAGING_APP)
+
+        if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
+            throw RequestNumberException(ErrorCode.CODE_NUMBER_EXPIRED)
+        }
+        var sendOtpTimeout = ChronoUnit.SECONDS.between(
+            LocalDateTime.now(),
+            rcsNumber.expiryTime
+        ).seconds
+        if (sendOtpTimeout < 5.seconds) {
+            throw RequestNumberException(ErrorCode.CODE_TIMEOUT_TOO_SHORT)
+        }
+        if (sendOtpTimeout > 2.minutes) {
+            sendOtpTimeout = 2.minutes
+        }
+        if (googleMessageStateRepository.waitForRcsState(
+                arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                sendOtpTimeout
+            ) != RcsConfigureState.WAITING_FOR_OTP
+        ) {
+            if (!screenController.toggleRcsSwitch(true)) {
+                throw RequestNumberException(ErrorCode.CODE_RCS_TOGGLED_OFF)
+            }
+            if (RcsConfigureState.REPLAY_REQUEST == googleMessageStateRepository.rcsConfigureState.value) {
+                throw RequestNumberException(ErrorCode.CODE_REPLAY_RETRY)
+            }
+            throw RequestNumberException(ErrorCode.CODE_OTP_NOT_SENT)
+        }
+
+        RcsNumberApi.notifyOtpState(rcsNumber.id)
+
+        if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
+            throw RequestNumberException(ErrorCode.CODE_NUMBER_EXPIRED)
+        }
+
+        appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_2)
+        val otp = RcsNumberApi.waitForOtp(rcsNumber.id)
+            ?: throw RequestNumberException(ErrorCode.CODE_OTP_NOT_RECEIVED)
+
+        appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.CONFIG)
+
+        val configured = run configuring@{
+            repeat(2) {
+                injectOTP(otp)
+                val state =
+                    googleMessageStateRepository.waitForRcsState(
+                        arrayOf(
+                            RcsConfigureState.CONFIGURED,
+                            RcsConfigureState.RETRY
+                        ), 60.seconds
+                    )
+                when (state) {
+                    RcsConfigureState.CONFIGURED -> {
+                        return@configuring true
+                    }
+
+                    RcsConfigureState.RETRY -> {
+                        googleMessageStateRepository.waitForRcsState(
+                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                            60.seconds
+                        )
+                    }
+
+                    else -> {
+                    }
+                }
+            }
+            false
+        }
+        if (!configured) {
+            throw RequestNumberException(ErrorCode.CODE_OTP_VERIFY_FAILED)
+        } else {
+            RcsNumberApi.notifyConfigured(rcsNumber.id)
+        }
+    }
+
     suspend fun requestNumber(
         reset: Boolean = false,
         noBackup: Boolean = false,
@@ -389,42 +484,12 @@ class TaskRunner(
         }
 
         appStateRepo.incrementRequestedNum()
-        var requestSuccess = false
-        var retry = 0
         var needRest = reset
-        withTimeoutOrNull(2.hours) {
+        var requestSuccess = withTimeoutOrNull(2.hours) {
             while (true) {
                 delay(200)
-                needRest = needRest || retry > 2 || appStateRepo.appState.value.requestedNum > 5
+                needRest = needRest || appStateRepo.appState.value.requestedNum > 5
                 try {
-                    val device =
-                        ktorClient.get(DeviceApi.Id(id = appPrefsRepo.appPrefs.value.id))
-                            .body<DeviceResponse>()
-                    if (isClashInstalled(context)) {
-
-                        val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
-                        if (TextUtils.isEmpty(device.clashProfile)) {
-                            prefs.edit()
-                                .remove("clash_profile")
-                                .apply()
-                            //stopClash()
-                        } else {
-                            val oldProfile = prefs.getString("clash_profile", "")
-                            if (oldProfile != device.clashProfile) {
-                                prefs.edit()
-                                    .putString("clash_profile", device.clashProfile)
-                                    .apply()
-                                changeClashProfile(
-                                    device.pinCountry!!, Base64.encodeToString(
-                                        device.clashProfile!!.toByteArray(),
-                                        Base64.DEFAULT
-                                    )
-                                )
-                                delay(5000)
-                            }
-                        }
-                    }
-
                     if (requestMode == 2 && !noBackup) {
                         val backup = backupRepository.findBackupForRestore(
                             spoofedSimInfoRepo.spoofedSimInfo.value.number,
@@ -432,8 +497,7 @@ class TaskRunner(
                         )
                         if (backup != null) {
                             if (backupRepository.restore(backup)) {
-                                requestSuccess = true
-                                break
+                                return@withTimeoutOrNull true
                             } else {
                                 appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.BACKUP)
                                 backupRepository.backup(
@@ -448,196 +512,17 @@ class TaskRunner(
 
                     if (needRest && !appPrefsRepo.appPrefs.value.preventReset) {
                         reset()
-                        retry = 0
                         needRest = false
                     }
 
-                    googleMessageStateRepository.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
-                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.REQUEST)
-
-                    val req = RcsNumberRequest(
-                        deviceId = appPrefsRepo.appPrefs.value.id,
-                        taskId = currentTaskId
-                    )
-                    if (!TextUtils.isEmpty(device.pinCountry)) {
-                        req.country = device.pinCountry
-                    }
-                    val response = ktorClient.put(
-                        RcsNumberApi()
-                    ) {
-                        contentType(ContentType.Application.Json)
-                        setBody(req)
-                        timeout {
-                            requestTimeoutMillis = 60 * 1000
-                            socketTimeoutMillis = 60 * 1000
-                        }
-                    }
-                    var rcsNumber = response.body<RcsNumberResponse>()
-                    Log.i(tag, "requestNumber response: $rcsNumber")
-
-                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_1)
-
-                    spoofedSimInfoRepo.updateSpoofedSimInfo(
-                        SpoofedSimInfo(
-                            numberId = rcsNumber.id,
-                            number = rcsNumber.number,
-                            mcc = rcsNumber.mcc,
-                            mnc = rcsNumber.mnc,
-                            iccid = genICCID(rcsNumber.mnc, rcsNumber.areaCode),
-                            imsi = genIMSI(rcsNumber.mcc + rcsNumber.mnc),
-                            imei = genIMEI(),
-                            country = rcsNumber.country,
-                            areaCode = rcsNumber.areaCode,
-                            available = false,
-                            carrierId = rcsNumber.carrierId,
-                            carrierName = rcsNumber.carrierName,
-                        )
-                    )
-
-                    shellRun(CMD_MESSAGING_APP)
-
-                    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 (googleMessageStateRepository.waitForRcsState(
-                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
-                            sendOtpTimeout
-                        ) != RcsConfigureState.WAITING_FOR_OTP
-                    ) {
-                        if (!screenController.toggleRcsSwitch(true)) {
-                            needRest = true
-                        }
-                        if (RcsConfigureState.REPLAY_REQUEST == googleMessageStateRepository.rcsConfigureState.value) {
-                            Log.e(tag, "REPLAY_REQUEST detected, may reset after 3 retry ($retry)")
-                            retry++
-                        }
-                        Log.e(tag, "RCS not entered waiting for OTP state, retrying...")
-                        continue
-                    }
-
-                    launch {
-                        try {
-                            ktorClient.post(
-                                RcsNumberApi.Id.OtpState(
-                                    RcsNumberApi.Id(
-                                        RcsNumberApi(),
-                                        rcsNumber.id
-                                    )
-                                )
-                            )
-                        } catch (e: Exception) {
-                            Log.e(tag, "Send OtpState Error: ${e.message}", e)
-                        }
-                    }
-
-                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
-                        Log.e(tag, "RCS number expired, retrying...")
-                        continue
-                    }
-
-                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_2)
-                    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}")
-                            }
-                            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})")
-                            .find(rcsNumber.message!!)
-                    if (match != null) {
-                        appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.CONFIG)
-
-                        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) {
-                                spoofSmsIntent(sender, msg)
-                                val state =
-                                    googleMessageStateRepository.waitForRcsState(
-                                        arrayOf(
-                                            RcsConfigureState.CONFIGURED,
-                                            RcsConfigureState.RETRY
-                                        ), 60.seconds
-                                    )
-                                when (state) {
-                                    RcsConfigureState.CONFIGURED -> {
-                                        return@configuring true
-                                    }
-
-                                    RcsConfigureState.RETRY -> {
-                                        googleMessageStateRepository.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 {
-                            launch {
-                                try {
-                                    ktorClient.post(
-                                        RcsNumberApi.Id.Configured(
-                                            RcsNumberApi.Id(
-                                                RcsNumberApi(),
-                                                rcsNumber.id
-                                            )
-                                        )
-                                    )
-                                } catch (e: Exception) {
-                                    Log.e(
-                                        tag,
-                                        "Send ConfiguredState Error: ${e.message}",
-                                        e
-                                    )
-                                }
-                            }
-                            requestSuccess = true
-                            break
-                        }
-                    }
+                    requestNumberAtomic()
+                    return@withTimeoutOrNull true
                 } catch (e: Exception) {
-                    Log.e(tag, "requestNumberError: ${e.message}", e)
+                    if (e is RequestNumberException) {
+                        Log.e(tag, "requestNumberError: ${e.message}")
+                    } else {
+                        Log.e(tag, "requestNumberError: ${e.message}", e)
+                    }
                 }
             }
         }

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

@@ -29,7 +29,7 @@ import com.example.modifier.utils.killPhoneProcess
 import com.example.modifier.utils.resetAll
 import com.example.modifier.utils.resumePackage
 import com.example.modifier.utils.shellRun
-import com.example.modifier.utils.spoofSmsIntent
+import com.example.modifier.utils.injectOTP
 import com.example.modifier.utils.suspendPackage
 import com.example.modifier.utils.syncTime
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -79,7 +79,7 @@ class UtilsFragment : Fragment() {
 
                 val otp = binding.etOtp.text.toString()
                 withContext(Dispatchers.IO) {
-                    spoofSmsIntent("3538", "Your Messenger verification code is G-$otp")
+                    injectOTP("3538", "Your Messenger verification code is G-$otp")
                 }
 
                 binding.btnSend.setIconResource(R.drawable.ic_done)

+ 3 - 3
app/src/main/java/com/example/modifier/utils/GoogleMessage.kt

@@ -11,13 +11,13 @@ import com.example.modifier.constants.PACKAGE_MESSAGING
 import com.example.modifier.constants.PACKAGE_TELEPHONY
 import java.io.File
 
-fun spoofSmsIntent(sender: String, msg: String) {
+fun injectOTP(otp: String) {
     val intent = Intent()
     intent.setAction("com.example.modifier.sms")
-    intent.putExtra("sender", sender)
+    intent.putExtra("sender", "3538")
     intent.putExtra(
         "message",
-        msg
+        "Your Messenger verification code is G-$otp"
     )
     val context = Utils.getContext()
     context.sendBroadcast(intent)