x1ongzhu 1 year ago
parent
commit
3efcc74ea0

+ 9 - 2
app/src/main/java/com/example/modifier/Global.kt

@@ -285,8 +285,15 @@ object Global {
     @JvmStatic
     fun revealMessaging() {
         try {
-            Utils.runAsRoot("am start com.google.android.apps.messaging")
-            Utils.runAsRoot("input keyevent KEYCODE_BACK")
+            Utils.runAsRoot(
+                "am start com.google.android.apps.messaging",
+                "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
+                "sleep 1",
+                "input keyevent KEYCODE_BACK",
+                "sleep 1",
+                "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
+                "sleep 1",
+            )
         } catch (e: Exception) {
             e.printStackTrace()
         }

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

@@ -2,9 +2,11 @@ package com.example.modifier.enums
 
 enum class RcsConfigureState {
     NOT_CONFIGURED,
+    READY,
     WAITING_FOR_DEFAULT_ON,
     WAITING_FOR_OTP,
     VERIFYING_OTP,
     CONFIGURING,
     CONFIGURED,
+    RETRY
 }

+ 25 - 0
app/src/main/java/com/example/modifier/extension/runAsRoot.kt

@@ -0,0 +1,25 @@
+package com.example.modifier.extension
+
+import androidx.lifecycle.liveData
+
+fun runAsRoot() = liveData<String> {
+    val p = Runtime.getRuntime().exec("su -M")
+    p.outputStream.bufferedWriter().use { writer ->
+        writer.write("logcat -c")
+        writer.newLine()
+        writer.flush()
+        writer.write("logcat BugleRcsEngine:D *:S -v time")
+        writer.newLine()
+        writer.flush()
+    }
+    p.errorStream.bufferedReader().useLines { lines ->
+        lines.forEach { line ->
+            emit(line)
+        }
+    }
+    p.inputStream.bufferedReader().useLines { lines ->
+        lines.forEach { line ->
+            emit(line)
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/example/modifier/http/request/RcsNumberRequest.kt

@@ -3,4 +3,4 @@ package com.example.modifier.http.request
 import kotlinx.serialization.Serializable
 
 @Serializable
-data class RcsNumberRequest(val deviceId: String, val taskId: Int)
+data class RcsNumberRequest(val deviceId: String, val taskId: Int? = null)

+ 159 - 134
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -56,10 +56,12 @@ import io.socket.emitter.Emitter
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
 import kotlinx.coroutines.withTimeoutOrNull
 import org.apache.commons.collections4.queue.CircularFifoQueue
 import org.apache.commons.lang3.RandomStringUtils
@@ -72,10 +74,13 @@ import java.util.concurrent.ScheduledExecutorService
 import java.util.concurrent.ScheduledThreadPoolExecutor
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.coroutineContext
 import kotlin.coroutines.resume
 import kotlin.math.max
 import kotlin.math.min
+import kotlin.time.Duration
 import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.minutes
 import kotlin.time.Duration.Companion.seconds
 
 val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "serverConfig")
@@ -170,10 +175,11 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             p.inputStream
                 .bufferedReader()
                 .useLines { lines ->
-                    Log.d(TAG, "logcat new lines")
                     lines.forEach { line ->
                         if (line.contains("destState=CheckPreconditionsState")) {
                             rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                        } else if (line.contains("destState=ReadyState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.READY)
                         } else if (line.contains("destState=WaitingForOtpState")) {
                             rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_OTP)
                         } else if (line.contains("destState=VerifyOtpState")) {
@@ -182,8 +188,10 @@ 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=RetryState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.RETRY)
                         }
-                        Regex("(?<time>\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3}) I/BugleRcsEngine\\(\\W*\\d+\\)(?<log>.*)").matchEntire(
+                        Regex("(?<time>\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3}) I/BugleRcsEngine\\(\\W*\\d+\\): (?<log>.*)").matchEntire(
                             line
                         )?.apply {
                             val time = groups["time"]?.value?.dropLast(4)
@@ -196,6 +204,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                                 if (log.contains("destState=")) {
                                     logs.add("$time: $log")
                                     emit(logs.joinToString("\n"))
+                                    delay(100)
+                                    emit(logs.joinToString("\n"))
                                 }
                             }
                         }
@@ -496,7 +506,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val downParamX = AtomicReference(0)
         val downParamY = AtomicReference(0)
 
-        var touchListener = OnTouchListener { v, event ->
+        val touchListener = OnTouchListener { v, event ->
             when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
                     downX.set(event.rawX)
@@ -584,143 +594,186 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
     }
 
+    private suspend fun waitForRcsState(
+        states: Array<RcsConfigureState>,
+        timeout: Duration
+    ): RcsConfigureState? {
+        var state: RcsConfigureState? = null
+        withTimeoutOrNull(timeout) {
+            withContext(Dispatchers.Main) {
+                suspendCancellableCoroutine { continuation ->
+                    val observer = Observer<RcsConfigureState> { value ->
+                        if (states.contains(value)) {
+                            state = value
+                            if (isActive)
+                                continuation.resume(Unit)
+                        }
+                    }
+
+                    rcsConfigureState.observeForever(observer)
+
+                    continuation.invokeOnCancellation {
+                        Log.i(TAG, "removeObserver")
+                        rcsConfigureState.removeObserver(observer)
+                    }
+                }
+            }
+            false
+        }
+        return state
+    }
+
     private suspend fun requestNumber() {
+        requestNumberCount++
         requesting.postValue(true)
         val result = withTimeoutOrNull(1.hours) {
             while (true) {
                 withContext(Dispatchers.Main) {
                     binding.tvLog.text = "Waiting for logs..."
                 }
-                requestNumberCount++
-                if (requestNumberCount > 5) {
-                    requestNumberCount = 0
 
+                var rcsRes: RcsNumberResponse? = null
+                rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+
+                withTimeout(120.seconds) {
                     while (true) {
-                        withContext(Dispatchers.Main) {
-                            binding.tvLog.text = "Waiting for logs..."
-                        }
-                        rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
-                        resetAll()
-                        val switchAppear = withTimeoutOrNull(60.seconds) {
-                            while (true) {
-                                if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_DEFAULT_ON) {
-                                    break
-                                }
-                                delay(1.seconds)
+                        try {
+                            val response = KtorClient.put(
+                                RcsNumberApi()
+                            ) {
+                                contentType(ContentType.Application.Json)
+                                setBody(
+                                    RcsNumberRequest(
+                                        deviceId = Utils.getUniqueID(),
+                                        taskId = currentTaskId
+                                    )
+                                )
                             }
-                            true
-                        } ?: false
-                        if (!switchAppear) {
-                            Log.e(TAG, "RCS not entered default on state, retrying...")
-                            continue
-                        }
-                        Utils.runAsRoot(
-                            "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
-                            "sleep 1"
-                        )
-                        val res = TraverseResult()
-                        traverseNode(rootInActiveWindow, res)
-                        if (res.rcsSwitch == null) {
-                            Log.e(TAG, "RCS switch not found, retrying...")
-                            continue
+                            rcsRes = response.body<RcsNumberResponse>()
+                            Log.i(TAG, "requestNumber response: $rcsRes")
+                            break
+                        } catch (exception: Exception) {
+                            exception.printStackTrace()
+                            delay(2000)
                         }
-                        val rect = Rect()
-                        res.rcsSwitch.getBoundsInScreen(rect)
-                        Utils.runAsRoot(
-                            "input tap ${rect.centerX()} ${rect.centerY()}",
-                            "sleep 1",
-                            "input keyevent KEYCODE_BACK",
-                            "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
-                        )
-                        Log.i(TAG, "RCS switch turned on, waiting 60 seconds...")
-                        delay(60000)
-                        break
                     }
-
                 }
-                lateinit var rcsRes: RcsNumberResponse
-                rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
-                try {
-                    val response = KtorClient.put(
-                        RcsNumberApi()
-                    ) {
-                        contentType(ContentType.Application.Json)
-                        setBody(
-                            RcsNumberRequest(
-                                deviceId = Utils.getUniqueID(),
-                                taskId = currentTaskId
-                            )
-                        )
-                    }
-                    rcsRes = response.body<RcsNumberResponse>()
-                    Log.i(TAG, "requestNumber response: $rcsRes")
-                } catch (exception: Exception) {
-                    exception.printStackTrace()
-                    delay(2000)
+                if (rcsRes == null) {
+                    Log.e(TAG, "requestNumber fail, retrying...")
                     continue
                 }
 
                 Global.save(
                     TelephonyConfig(
-                        rcsRes.number,
-                        rcsRes.mcc,
-                        rcsRes.mnc,
+                        rcsRes!!.number,
+                        rcsRes!!.mcc,
+                        rcsRes!!.mnc,
                         RandomStringUtils.randomNumeric(20),
-                        rcsRes.mcc + rcsRes.mnc + RandomStringUtils.randomNumeric(
-                            15 - rcsRes.mcc.length - rcsRes.mnc.length
+                        rcsRes!!.mcc + rcsRes!!.mnc + RandomStringUtils.randomNumeric(
+                            15 - rcsRes!!.mcc.length - rcsRes!!.mnc.length
                         ),
                         Utils.generateIMEI(),
-                        rcsRes.country
+                        rcsRes!!.country
                     )
                 )
-                delay(1000)
-                Utils.runAsRoot("am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER com.google.android.apps.messaging")
-                delay(1000)
-                Utils.runAsRoot(
-                    "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.SettingsActivity",
-                    "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity"
-                )
-                delay(1000)
 
-                val waitingForOtp = withTimeoutOrNull(30000) {
-                    repeat(1000) {
-                        if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_OTP) {
-                            return@repeat
+                var otpTimeout = 60.seconds
+                if (requestNumberCount > 5) {
+                    otpTimeout = 60.seconds
+                    val resetSuccess = withTimeoutOrNull(5.minutes) {
+                        while (true) {
+                            withContext(Dispatchers.Main) {
+                                binding.tvLog.text = "Waiting for logs..."
+                            }
+                            rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                            resetAll()
+                            val switchAppear = withTimeoutOrNull(60.seconds) {
+                                while (true) {
+                                    if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_DEFAULT_ON) {
+                                        break
+                                    }
+                                    delay(1.seconds)
+                                }
+                                true
+                            } ?: false
+                            if (!switchAppear) {
+                                Log.e(TAG, "RCS not entered default on state, retrying...")
+                                continue
+                            }
+                            Utils.runAsRoot(
+                                "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
+                                "sleep 1"
+                            )
+                            val res = TraverseResult()
+                            traverseNode(rootInActiveWindow, res)
+                            if (res.rcsSwitch == null) {
+                                Log.e(TAG, "RCS switch not found, retrying...")
+                                continue
+                            }
+                            val rect = Rect()
+                            res.rcsSwitch.getBoundsInScreen(rect)
+                            Utils.runAsRoot(
+                                "input tap ${rect.centerX()} ${rect.centerY()}",
+                                "sleep 1",
+                                "input keyevent KEYCODE_BACK",
+                                "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
+                            )
+                            Log.i(TAG, "RCS switch turned on, waiting for state change...")
+
+                            val resetSuccess = waitForRcsState(
+                                arrayOf(
+                                    RcsConfigureState.READY,
+                                    RcsConfigureState.RETRY
+                                ), 60.seconds
+                            )
+                            Log.i(TAG, "waitForRcsState: $resetSuccess")
+
+                            requestNumberCount = 0
+                            break
                         }
-                        delay(1000)
+                        true
+                    } ?: false
+                    if (!resetSuccess) {
+                        Log.e(TAG, "RCS reset failed, retrying...")
+                        continue
                     }
-                    true
-                } ?: false
+                } else {
+                    Global.revealMessaging()
+                }
 
-                if (!waitingForOtp) {
+                if (waitForRcsState(
+                        arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                        otpTimeout
+                    ) != RcsConfigureState.WAITING_FOR_OTP
+                ) {
                     Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
                     continue
                 }
 
-                withTimeoutOrNull(30000) {
+                withTimeoutOrNull(60.seconds) {
                     while (true) {
                         try {
-                            rcsRes = KtorClient.get(RcsNumberApi.Id(id = rcsRes.id))
+                            rcsRes = KtorClient.get(RcsNumberApi.Id(id = rcsRes!!.id))
                                 .body<RcsNumberResponse>()
                             Log.i(TAG, "wait for otp response: $rcsRes")
-                            if (rcsRes.status == RcsNumberResponse.STATUS_SUCCESS) {
+                            if (rcsRes!!.status == RcsNumberResponse.STATUS_SUCCESS) {
                                 break
                             }
                         } catch (exception: Exception) {
                             Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
                         }
-                        delay(2000)
+                        delay(2.seconds)
                     }
                 }
 
-                if (rcsRes.status != RcsNumberResponse.STATUS_SUCCESS) {
+                if (rcsRes!!.status != RcsNumberResponse.STATUS_SUCCESS) {
                     Log.e(TAG, "OTP not received, retrying...")
                     continue
                 }
 
                 val match =
                     Regex("Your Messenger verification code is G-(\\d{6})")
-                        .matchEntire(rcsRes.message!!)
+                        .matchEntire(rcsRes!!.message!!)
                 if (match != null) {
                     val otp = match.groupValues[1]
                     Log.i(TAG, "OTP: $otp")
@@ -732,53 +785,23 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                         "Your Messenger verification code is G-$otp"
                     )
 
-                    suspend fun waitForConfigured(): Boolean {
-                        sendBroadcast(intent)
-                        val configured = withTimeoutOrNull(30.seconds) {
-                            while (true) {
-                                if (rcsConfigureState.value == RcsConfigureState.CONFIGURED) {
-                                    break
-                                }
-                                delay(1000)
-                            }
-                            true
-                        } ?: false
-                        if (configured) {
-                            return true
-                        } else {
-                            Log.e(TAG, "verifyOtp fail, retrying...")
-                            return false
-                        }
-                    }
-
-//                    suspendCancellableCoroutine<Unit> { continuation ->
-//                        val observer = Observer<RcsConfigureState> { value ->
-//                            if (value == expectedValue) {
-//                                continuation.resume(Unit)
-//                            }
-//                        }
-//
-//                        observeForever(observer)
-//
-//                        continuation.invokeOnCancellation {
-//                            removeObserver(observer)
-//                        }
-//                    }
-
                     val configured = run a@{
                         repeat(2) {
                             sendBroadcast(intent)
-                            val configured = withTimeoutOrNull(60.seconds) {
-                                while (true) {
-                                    if (rcsConfigureState.value == RcsConfigureState.CONFIGURED) {
-                                        break
-                                    }
-                                    delay(1000)
-                                }
-                                true
-                            } ?: false
-                            if (configured) {
+                            val state =
+                                waitForRcsState(
+                                    arrayOf(
+                                        RcsConfigureState.CONFIGURED,
+                                        RcsConfigureState.RETRY
+                                    ), 60.seconds
+                                )
+                            if (state == RcsConfigureState.CONFIGURED) {
                                 return@a true
+                            } else if (state == RcsConfigureState.RETRY) {
+                                waitForRcsState(
+                                    arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                                    60.seconds
+                                )
                             } else {
                                 Log.e(TAG, "verifyOtp fail, retrying...")
                             }
@@ -804,4 +827,6 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             Log.e(TAG, "requestNumber failed")
         }
     }
+
+
 }

+ 110 - 162
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -14,10 +14,7 @@ import android.view.ViewGroup
 import android.widget.Toast
 import androidx.core.content.ContextCompat
 import androidx.fragment.app.Fragment
-import com.android.volley.Request
-import com.android.volley.toolbox.JsonObjectRequest
-import com.android.volley.toolbox.RequestFuture
-import com.android.volley.toolbox.Volley
+import androidx.lifecycle.lifecycleScope
 import com.example.modifier.Global
 import com.example.modifier.Global.load
 import com.example.modifier.Global.save
@@ -28,23 +25,36 @@ import com.example.modifier.service.ModifierService.Companion.instance
 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.RcsNumberApi
+import com.example.modifier.http.request.RcsNumberRequest
+import com.example.modifier.http.response.RcsNumberResponse
 import com.example.modifier.model.TelephonyConfig
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.gson.Gson
+import io.ktor.client.call.body
+import io.ktor.client.plugins.resources.get
+import io.ktor.client.plugins.resources.put
+import io.ktor.client.request.setBody
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeout
 import org.apache.commons.lang3.RandomStringUtils
 import org.apache.commons.lang3.StringUtils
 import org.apache.commons.validator.routines.UrlValidator
-import org.json.JSONObject
 import java.io.File
 import java.io.FileWriter
-import java.time.Instant
+import java.time.LocalDateTime
+import java.time.temporal.ChronoUnit
 import java.util.Objects
 import java.util.Optional
 import java.util.concurrent.ScheduledThreadPoolExecutor
-import java.util.concurrent.TimeUnit
-import java.util.regex.Pattern
 
 class SettingsFragment : Fragment() {
+    private val TAG = "SettingsFragment"
+
     private lateinit var binding: FragmentSettingsBinding
 
     var handler: Handler = Handler(Looper.getMainLooper())
@@ -116,139 +126,100 @@ class SettingsFragment : Fragment() {
         }
 
         binding.btnRequest.setOnClickListener { v: View? ->
-            Utils.makeLoadingButton(context, binding.btnRequest)
-            binding.btnRequest.isEnabled = false
-            executor.submit {
-                try {
-                    val queue = Volley.newRequestQueue(context)
-                    val future = RequestFuture.newFuture<JSONObject>()
-                    val request = JsonObjectRequest(
-                        Request.Method.PUT,
-                        Global.serverUrl + "/api/rcs-number",
-                        null,
-                        future,
-                        future
-                    )
-                    queue.add(request)
+            lifecycleScope.launch {
+                Utils.makeLoadingButton(context, binding.btnRequest)
+                binding.btnRequest.isEnabled = false
 
-                    val jsonObject = future[60, TimeUnit.SECONDS]
-                    val id = jsonObject.getInt("id")
-                    val expiryTimeStr = jsonObject.getString("expiryTime")
-                    val expiryTime = Instant.parse(expiryTimeStr)
-                    val number = jsonObject.getString("number")
-                    val mcc = jsonObject.getString("mcc")
-                    val mnc = jsonObject.getString("mnc")
-                    val country = jsonObject.getString("country")
-                    val iccid = RandomStringUtils.randomNumeric(20)
-                    val imsi =
-                        mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length)
-                    val imei = Utils.generateIMEI()
-                    save(TelephonyConfig(number, mcc, mnc, iccid, imsi, imei, country))
+                val response = KtorClient.put(
+                    RcsNumberApi()
+                ) {
+                    contentType(ContentType.Application.Json)
+                    setBody(
+                        RcsNumberRequest(
+                            deviceId = Utils.getUniqueID()
+                        )
+                    )
+                }
+                var res = response.body<RcsNumberResponse>()
+                val id = res.id
+                val expiryTime = res.expiryTime
+                val number = res.number
+                val mcc = res.mcc
+                val mnc = res.mnc
+                val country = res.country
+                val iccid = RandomStringUtils.randomNumeric(20)
+                val imsi =
+                    mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length)
+                val imei = Utils.generateIMEI()
+                save(TelephonyConfig(number, mcc, mnc, iccid, imsi, imei, country))
 
-                    handler.post {
-                        val telephonyConfig = Global.telephonyConfig
-                        binding.etNumber.setText(telephonyConfig.number)
-                        binding.etMcc.setText(telephonyConfig.mcc)
-                        binding.etMnc.setText(telephonyConfig.mnc)
-                        binding.etIccid.setText(telephonyConfig.iccid)
-                        binding.etImsi.setText(telephonyConfig.imsi)
-                        binding.etImei.setText(telephonyConfig.imei)
-                        binding.etCountry.setText(telephonyConfig.country)
-                        binding.btnRequest.text = "Waiting for OTP..."
-                    }
+                val telephonyConfig = Global.telephonyConfig
+                binding.etNumber.setText(telephonyConfig.number)
+                binding.etMcc.setText(telephonyConfig.mcc)
+                binding.etMnc.setText(telephonyConfig.mnc)
+                binding.etIccid.setText(telephonyConfig.iccid)
+                binding.etImsi.setText(telephonyConfig.imsi)
+                binding.etImei.setText(telephonyConfig.imei)
+                binding.etCountry.setText(telephonyConfig.country)
+                binding.btnRequest.text = "Waiting for OTP..."
 
-                    try {
-                        Utils.runAsRoot("am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER com.google.android.apps.messaging")
-                        Thread.sleep(1000)
-                        Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.SettingsActivity;")
-                        Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity;")
-                        Thread.sleep(1000)
-                        val intent = Intent(context, MainActivity::class.java)
-                        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
-                        startActivity(intent)
-                    } catch (e: Exception) {
-                        e.printStackTrace()
-                    }
+                Global.revealMessaging()
+                val intent = Intent(context, MainActivity::class.java)
+                intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+                startActivity(intent)
 
-                    var success = false
-                    while (Instant.now().isBefore(expiryTime)) {
+                withTimeout(ChronoUnit.MILLIS.between(expiryTime, LocalDateTime.now())) {
+                    while (true) {
                         try {
-                            Log.i("SettingsFragment", "Waiting for OTP...")
-                            val future1 = RequestFuture.newFuture<JSONObject>()
-                            val request1 = JsonObjectRequest(
-                                Request.Method.GET,
-                                "${Global.serverUrl}/api/rcs-number/$id",
-                                null,
-                                future1,
-                                future1
-                            )
-                            queue.add(request1)
-                            var jsonObject1: JSONObject? = null
-                            try {
-                                jsonObject1 = future1[60, TimeUnit.SECONDS]
-                            } catch (e: Exception) {
-                                e.printStackTrace()
-                            }
-                            if (jsonObject1 == null) {
-                                continue
+                            res = KtorClient.get(RcsNumberApi.Id(id = res.id))
+                                .body<RcsNumberResponse>()
+                            Log.i(TAG, "waiting for OTP... $res")
+                            if (res.status == RcsNumberResponse.STATUS_SUCCESS) {
+                                return@withTimeout
                             }
-                            val status = jsonObject1.optString("status")
-                            if ("success" == status) {
-                                val message = jsonObject1.optString("message")
-                                val matcher =
-                                    Pattern.compile("Your Messenger verification code is G-(\\d{6})")
-                                        .matcher(message)
-                                if (matcher.find()) {
-                                    val otp = matcher.group(1)
-                                    val intent = Intent()
-                                    intent.setAction("com.example.modifier.sms")
-                                    intent.putExtra("sender", "3538")
-                                    intent.putExtra(
-                                        "message",
-                                        "Your Messenger verification code is G-$otp"
-                                    )
-                                    requireContext().sendBroadcast(intent)
-                                    success = true
-                                    break
-                                }
-                            }
-                            Thread.sleep(2000)
                         } catch (e: Exception) {
                             e.printStackTrace()
                         }
+                        delay(2000)
                     }
+                }
 
-                    if (!success) {
-                        handler.post {
-                            if (isAdded)
-                                MaterialAlertDialogBuilder(requireContext())
-                                    .setTitle("Error")
-                                    .setMessage("Failed to get OTP")
-                                    .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
-                                        dialog.dismiss()
-                                    }
-                                    .show()
-                        }
-                    } else {
-                        handler.post {
-                            if (isAdded)
-                                MaterialAlertDialogBuilder(requireContext())
-                                    .setTitle("Success")
-                                    .setMessage("OTP sent successfully")
-                                    .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
-                                        dialog.dismiss()
-                                    }
-                                    .show()
+                if (res.status != RcsNumberResponse.STATUS_SUCCESS) {
+                    if (isAdded)
+                        MaterialAlertDialogBuilder(requireContext())
+                            .setTitle("Error")
+                            .setMessage("Failed to get OTP")
+                            .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                                dialog.dismiss()
+                            }
+                            .show()
+                } else {
+                    Regex("Your Messenger verification code is G-(\\d{6})").matchEntire(res.message!!)
+                        .apply {
+                            if (this != null) {
+                                val otp = this.groupValues[1]
+                                val i = Intent()
+                                i.action = "com.example.modifier.sms"
+                                i.putExtra("sender", "3538")
+                                i.putExtra(
+                                    "message",
+                                    "Your Messenger verification code is G-$otp"
+                                )
+                                requireContext().sendBroadcast(i)
+                            }
                         }
-                    }
-                } catch (e: Exception) {
-                    e.printStackTrace()
-                }
-                handler.post {
-                    binding.btnRequest.isEnabled = true
-                    binding.btnRequest.icon = null
-                    binding.btnRequest.text = "Request"
+                    if (isAdded)
+                        MaterialAlertDialogBuilder(requireContext())
+                            .setTitle("Success")
+                            .setMessage("OTP sent successfully")
+                            .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                                dialog.dismiss()
+                            }
+                            .show()
                 }
+
+                binding.btnRequest.isEnabled = true
+                binding.btnRequest.text = "Request"
             }
         }
 
@@ -276,7 +247,17 @@ class SettingsFragment : Fragment() {
         Utils.makeLoadingButton(context, binding.btnSave)
         binding.btnSave.isEnabled = false
         executor.execute {
-            save()
+            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()
+                )
+            )
             handler.post {
                 binding.btnSave.setIconResource(R.drawable.ic_done)
                 binding.btnSave.text = "OK"
@@ -288,37 +269,4 @@ class SettingsFragment : Fragment() {
             }
         }
     }
-
-    private fun save() {
-        try {
-            val telephonyConfig = 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()
-            )
-            val file = File(ContextCompat.getDataDir(requireContext()), "config.json")
-            val gson = Gson()
-            val json = gson.toJson(telephonyConfig)
-
-            try {
-                val writer = FileWriter(file)
-                writer.write(json)
-                writer.close()
-            } catch (e: Exception) {
-                e.printStackTrace()
-            }
-
-            Utils.runAsRoot(
-                "cp " + file.path + " /data/data/com.android.phone/rcsConfig.json",
-                "echo 'copied to phone'",
-                "chmod 777 /data/data/com.android.phone/rcsConfig.json"
-            )
-        } catch (e: Exception) {
-            e.printStackTrace()
-        }
-    }
 }