x1ongzhu 1 年間 前
コミット
ef7c5322a5
23 ファイル変更524 行追加59 行削除
  1. 1 0
      app/build.gradle
  2. 1 1
      app/src/main/AndroidManifest.xml
  3. 17 6
      app/src/main/java/com/example/modifier/Global.kt
  4. 1 2
      app/src/main/java/com/example/modifier/Utils.java
  5. 9 0
      app/src/main/java/com/example/modifier/enums/RcsConfigureState.kt
  6. 24 0
      app/src/main/java/com/example/modifier/extension/waitUntilValueIs.kt
  7. 43 0
      app/src/main/java/com/example/modifier/http/KtorClient.kt
  8. 13 0
      app/src/main/java/com/example/modifier/http/RcsNumberApi.kt
  9. 11 0
      app/src/main/java/com/example/modifier/http/SysConfigApi.kt
  10. 1 1
      app/src/main/java/com/example/modifier/http/request/LoginRequest.kt
  11. 1 1
      app/src/main/java/com/example/modifier/http/response/ErrorResponse.kt
  12. 1 1
      app/src/main/java/com/example/modifier/http/response/LoginResponse.kt
  13. 31 0
      app/src/main/java/com/example/modifier/http/response/RcsNumberResponse.kt
  14. 4 0
      app/src/main/java/com/example/modifier/http/response/SysConfigResponse.kt
  15. 22 0
      app/src/main/java/com/example/modifier/serializer/LocalDateTimeSerializer.kt
  16. 219 24
      app/src/main/java/com/example/modifier/service/ModifierService.kt
  17. 3 3
      app/src/main/java/com/example/modifier/ui/login/LoginViewModel.kt
  18. 17 15
      app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt
  19. 59 0
      app/src/main/java/com/example/modifier/ui/settings/SettingsViewModel.kt
  20. 10 0
      app/src/main/res/drawable/ic_download.xml
  21. 27 4
      app/src/main/res/layout/floating_window.xml
  22. 8 1
      app/src/test/java/com/example/modifier/ExampleUnitTest.java
  23. 1 0
      gradle/libs.versions.toml

+ 1 - 0
app/build.gradle

@@ -102,6 +102,7 @@ dependencies {
     implementation libs.ktor.client.cio
     implementation libs.ktor.client.cio
     implementation libs.ktor.client.okhttp
     implementation libs.ktor.client.okhttp
     implementation libs.ktor.client.content.negotiation
     implementation libs.ktor.client.content.negotiation
+    implementation libs.ktor.client.resources
     implementation libs.ktor.serialization.kotlinx.json
     implementation libs.ktor.serialization.kotlinx.json
     implementation libs.kotlinx.serialization.json
     implementation libs.kotlinx.serialization.json
 
 

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

@@ -38,7 +38,7 @@
         </activity>
         </activity>
 
 
         <service
         <service
-            android:name=".ModifierService"
+            android:name=".service.ModifierService"
             android:enabled="true"
             android:enabled="true"
             android:exported="true"
             android:exported="true"
             android:label="@string/accessibility_service_label"
             android:label="@string/accessibility_service_label"

+ 17 - 6
app/src/main/java/com/example/modifier/Global.kt

@@ -13,7 +13,7 @@ import java.io.FileWriter
 
 
 object Global {
 object Global {
     @JvmField
     @JvmField
-    var serverUrl: String? = ""
+    var serverUrl: String = ""
 
 
     @JvmField
     @JvmField
     var name: String? = ""
     var name: String? = ""
@@ -25,7 +25,7 @@ object Global {
     fun load() {
     fun load() {
         val context = Utils.getContext()
         val context = Utils.getContext()
         val prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE)
         val prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE)
-        serverUrl = prefs.getString("server", "https://rcs.izouma.com")
+        serverUrl = prefs.getString("server", "https://rcs.izouma.com") ?: ""
         name = prefs.getString("name", Build.DEVICE)
         name = prefs.getString("name", Build.DEVICE)
         try {
         try {
             val file = File(ContextCompat.getDataDir(context), "config.json")
             val file = File(ContextCompat.getDataDir(context), "config.json")
@@ -123,18 +123,19 @@ object Global {
     }
     }
 
 
     @JvmStatic
     @JvmStatic
-    fun stop(gsf: Boolean, gms: Boolean, sms: Boolean) {
+    fun stop(gsf: Boolean? = false, gms: Boolean? = false, sms: Boolean? = false) {
         try {
         try {
             val cmds: MutableList<String> = ArrayList()
             val cmds: MutableList<String> = ArrayList()
-            if (gsf) {
+            if (gsf == true) {
                 cmds.add("am force-stop com.google.android.gsf")
                 cmds.add("am force-stop com.google.android.gsf")
                 cmds.add("echo 'stopped gsf'")
                 cmds.add("echo 'stopped gsf'")
             }
             }
-            if (gms) {
+            if (gms == true) {
                 cmds.add("am force-stop com.google.android.gms")
                 cmds.add("am force-stop com.google.android.gms")
                 cmds.add("echo 'stopped gms'")
                 cmds.add("echo 'stopped gms'")
+                Thread.sleep(1000)
             }
             }
-            if (sms) {
+            if (sms == true) {
                 cmds.add("am force-stop com.google.android.apps.messaging")
                 cmds.add("am force-stop com.google.android.apps.messaging")
                 cmds.add("echo 'stopped sms'")
                 cmds.add("echo 'stopped sms'")
             }
             }
@@ -183,4 +184,14 @@ object Global {
             e.printStackTrace()
             e.printStackTrace()
         }
         }
     }
     }
+
+    @JvmStatic
+    fun revealMessaging() {
+        try {
+            Utils.runAsRoot("am start com.google.android.apps.messaging")
+            Utils.runAsRoot("input keyevent KEYCODE_BACK")
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
 }
 }

+ 1 - 2
app/src/main/java/com/example/modifier/Utils.java

@@ -10,8 +10,7 @@ import android.text.TextUtils;
 import android.util.Log;
 import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager;
 
 
-import androidx.annotation.DrawableRes;
-
+import com.example.modifier.service.ModifierService;
 import com.google.android.material.button.MaterialButton;
 import com.google.android.material.button.MaterialButton;
 import com.google.android.material.progressindicator.CircularProgressIndicatorSpec;
 import com.google.android.material.progressindicator.CircularProgressIndicatorSpec;
 import com.google.android.material.progressindicator.IndeterminateDrawable;
 import com.google.android.material.progressindicator.IndeterminateDrawable;

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

@@ -0,0 +1,9 @@
+package com.example.modifier.enums
+
+enum class RcsConfigureState {
+    NOT_CONFIGURED,
+    WAITING_FOR_OTP,
+    VERIFYING_OTP,
+    CONFIGURING,
+    CONFIGURED,
+}

+ 24 - 0
app/src/main/java/com/example/modifier/extension/waitUntilValueIs.kt

@@ -0,0 +1,24 @@
+package com.example.modifier.extension
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+suspend fun <T> MutableLiveData<T>.waitUntilValueIs(expectedValue: T) {
+    if (this.value == expectedValue) return
+
+    suspendCancellableCoroutine<Unit> { continuation ->
+        val observer = Observer<T> { value ->
+            if (value == expectedValue) {
+                continuation.resume(Unit)
+            }
+        }
+
+        observeForever(observer)
+
+        continuation.invokeOnCancellation {
+            removeObserver(observer)
+        }
+    }
+}

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

@@ -0,0 +1,43 @@
+package com.example.modifier.http
+
+import com.example.modifier.Global
+import com.example.modifier.http.response.ErrorResponse
+import io.ktor.client.HttpClient
+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.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.defaultRequest
+import io.ktor.client.plugins.resources.Resources
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+
+@OptIn(ExperimentalSerializationApi::class)
+val KtorClient = HttpClient(OkHttp) {
+    defaultRequest {
+        Global.load()
+        url(Global.serverUrl.endsWith("/").let {
+            if (it) Global.serverUrl else "${Global.serverUrl}/"
+        })
+    }
+    install(Resources)
+    install(ContentNegotiation) {
+        json(Json {
+            prettyPrint = true
+            isLenient = true
+            ignoreUnknownKeys = true
+            explicitNulls = false
+        })
+    }
+    HttpResponseValidator {
+        handleResponseExceptionWithRequest { exception, request ->
+            val clientException =
+                exception as? ClientRequestException ?: return@handleResponseExceptionWithRequest
+            val exceptionResponse = clientException.response
+            val err = exceptionResponse.body<ErrorResponse>()
+            throw Exception(err.message ?: "Unknown error")
+        }
+    }
+}

+ 13 - 0
app/src/main/java/com/example/modifier/http/RcsNumberApi.kt

@@ -0,0 +1,13 @@
+package com.example.modifier.http
+
+import io.ktor.resources.Resource
+
+@Resource("api/rcs-number")
+class RcsNumberApi() {
+
+    @Resource("{id}")
+    class Id(val parent: RcsNumberApi = RcsNumberApi(), val id: Long) {
+        @Resource("delete")
+        class Delete(val parent: Id)
+    }
+}

+ 11 - 0
app/src/main/java/com/example/modifier/http/SysConfigApi.kt

@@ -0,0 +1,11 @@
+package com.example.modifier.http
+
+import io.ktor.resources.Resource
+
+@Resource("api/sys-config")
+class SysConfigApi {
+
+    @Resource("{id}")
+    class Id(val parent: SysConfigApi = SysConfigApi(), val id: Long) {
+    }
+}

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

@@ -1,4 +1,4 @@
-package com.example.modifier.request
+package com.example.modifier.http.request
 
 
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Serializable
 
 

+ 1 - 1
app/src/main/java/com/example/modifier/response/ErrorResponse.kt → app/src/main/java/com/example/modifier/http/response/ErrorResponse.kt

@@ -1,4 +1,4 @@
-package com.example.modifier.response
+package com.example.modifier.http.response
 
 
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Serializable
 
 

+ 1 - 1
app/src/main/java/com/example/modifier/response/LoginResponse.kt → app/src/main/java/com/example/modifier/http/response/LoginResponse.kt

@@ -1,4 +1,4 @@
-package com.example.modifier.response
+package com.example.modifier.http.response
 
 
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Serializable
 
 

+ 31 - 0
app/src/main/java/com/example/modifier/http/response/RcsNumberResponse.kt

@@ -0,0 +1,31 @@
+package com.example.modifier.http.response
+
+import com.example.modifier.serializer.LocalDateTimeSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonElement
+import java.time.LocalDateTime
+
+@Serializable
+data class RcsNumberResponse(
+    val id: Long,
+    @Serializable(with = LocalDateTimeSerializer::class)
+    val createdAt: LocalDateTime,
+    @Serializable(with = LocalDateTimeSerializer::class)
+    val expiryTime: LocalDateTime,
+    val from: String,
+    val mcc: String,
+    val mnc: String,
+    val country: String,
+    val number: String,
+    val message: String?,
+    val status: String,
+    val extra: JsonElement,
+    val deviceId: String?,
+) {
+    companion object {
+        const val STATUS_PENDING = "pending"
+        const val STATUS_SUCCESS = "success"
+        const val STATUS_EXPIRED = "expired"
+        const val STATUS_ERROR = "error"
+    }
+}

+ 4 - 0
app/src/main/java/com/example/modifier/http/response/SysConfigResponse.kt

@@ -0,0 +1,4 @@
+package com.example.modifier.http.response
+
+data class SysConfigResponse(val id: Int, val name: String, val value: String) {
+}

+ 22 - 0
app/src/main/java/com/example/modifier/serializer/LocalDateTimeSerializer.kt

@@ -0,0 +1,22 @@
+package com.example.modifier.serializer
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+@Serializer(forClass = LocalDateTime::class)
+class LocalDateTimeSerializer : KSerializer<LocalDateTime> {
+
+    private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME
+
+    override fun serialize(encoder: Encoder, value: LocalDateTime) {
+        encoder.encodeString(value.format(formatter))
+    }
+
+    override fun deserialize(decoder: Decoder): LocalDateTime {
+        return LocalDateTime.parse(decoder.decodeString(), formatter)
+    }
+}

+ 219 - 24
app/src/main/java/com/example/modifier/ModifierService.kt → app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -1,4 +1,4 @@
-package com.example.modifier
+package com.example.modifier.service
 
 
 import android.accessibilityservice.AccessibilityService
 import android.accessibilityservice.AccessibilityService
 import android.accessibilityservice.AccessibilityServiceInfo
 import android.accessibilityservice.AccessibilityServiceInfo
@@ -18,20 +18,36 @@ import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.CompoundButton
 import android.widget.CompoundButton
 import android.widget.FrameLayout
 import android.widget.FrameLayout
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.liveData
+import com.example.modifier.BuildConfig
+import com.example.modifier.Global
 import com.example.modifier.Global.load
 import com.example.modifier.Global.load
+import com.example.modifier.R
+import com.example.modifier.TraverseResult
+import com.example.modifier.Utils
 import com.example.modifier.databinding.FloatingWindowBinding
 import com.example.modifier.databinding.FloatingWindowBinding
+import com.example.modifier.enums.RcsConfigureState
+import com.example.modifier.http.KtorClient
+import com.example.modifier.http.RcsNumberApi
+import com.example.modifier.http.response.RcsNumberResponse
+import com.example.modifier.model.TelephonyConfig
 import com.google.android.material.color.DynamicColors
 import com.google.android.material.color.DynamicColors
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.dialog.MaterialDialogs
+import io.ktor.client.call.body
+import io.ktor.client.plugins.resources.get
+import io.ktor.client.plugins.resources.put
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
 import io.socket.client.IO
 import io.socket.client.IO
 import io.socket.client.Socket
 import io.socket.client.Socket
 import io.socket.emitter.Emitter
 import io.socket.emitter.Emitter
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withContext
+import org.apache.commons.lang3.RandomStringUtils
 import org.apache.commons.lang3.StringUtils
 import org.apache.commons.lang3.StringUtils
 import org.json.JSONArray
 import org.json.JSONArray
 import org.json.JSONException
 import org.json.JSONException
@@ -45,6 +61,15 @@ import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.min
 
 
 class ModifierService : AccessibilityService(), Emitter.Listener {
 class ModifierService : AccessibilityService(), Emitter.Listener {
+    companion object {
+        private const val TAG = "ModifierService"
+
+        const val NAME: String = BuildConfig.APPLICATION_ID + ".service.ModifierService"
+
+        @JvmStatic
+        var instance: ModifierService? = null
+            private set
+    }
 
 
     private val mExecutor: ScheduledExecutorService = ScheduledThreadPoolExecutor(8)
     private val mExecutor: ScheduledExecutorService = ScheduledThreadPoolExecutor(8)
 
 
@@ -57,6 +82,56 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     private var cleanCount = 0
     private var cleanCount = 0
     private var lastSend = 0L
     private var lastSend = 0L
     private var rcsInterval = 0L
     private var rcsInterval = 0L
+    private var requestNumberInterval = 0
+
+    private val rcsConfigureState = MutableLiveData(RcsConfigureState.CONFIGURED)
+
+    private var sendCount: Int
+        get() {
+            return getSharedPreferences(
+                BuildConfig.APPLICATION_ID,
+                MODE_PRIVATE
+            ).getInt("sendCount", 0)
+        }
+        set(value) {
+            getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
+                .putInt("sendCount", value).apply()
+        }
+
+
+    val logcat = liveData(Dispatchers.IO) {
+        try {
+            val p = Runtime.getRuntime().exec("su")
+            p.outputStream.bufferedWriter().use { writer ->
+                writer.write("logcat -c")
+                writer.newLine()
+                writer.flush()
+                writer.write("logcat BugleRcsEngine:D *:S -v tag")
+                writer.newLine()
+                writer.flush()
+            }
+            p.inputStream
+                .bufferedReader()
+                .useLines { lines ->
+                    Log.d(TAG, "logcat new lines")
+                    lines.forEach { line ->
+                        emit(line)
+                        if (line.contains("destState=CheckPreconditionsState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                        } else if (line.contains("destState=WaitingForOtpState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_OTP)
+                        } else if (line.contains("destState=VerifyOtpState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.VERIFYING_OTP)
+                        } else if (line.contains("destState=ConfiguredState")) {
+                            rcsConfigureState.postValue(RcsConfigureState.CONFIGURED)
+                        }
+                    }
+                }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
     private var busy: Boolean
     private var busy: Boolean
         get() {
         get() {
             return _busy
             return _busy
@@ -151,10 +226,11 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val rcsWait = config.optLong("rcsWait", 2000)
         val rcsWait = config.optLong("rcsWait", 2000)
         cleanCount = config.optInt("cleanCount", 20)
         cleanCount = config.optInt("cleanCount", 20)
         rcsInterval = config.optLong("rcsInterval", 0)
         rcsInterval = config.optLong("rcsInterval", 0)
-        counter = 0
+        requestNumberInterval = config.optInt("requestNumberInterval", 0)
         val tasks = data.optJSONArray("tasks")
         val tasks = data.optJSONArray("tasks")
         mExecutor.submit {
         mExecutor.submit {
             busy = true
             busy = true
+
             val success = JSONArray()
             val success = JSONArray()
             val fail = JSONArray()
             val fail = JSONArray()
             for (i in 0 until tasks.length()) {
             for (i in 0 until tasks.length()) {
@@ -185,6 +261,12 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             } catch (e: JSONException) {
             } catch (e: JSONException) {
             }
             }
             mSocket.emit("callback", res)
             mSocket.emit("callback", res)
+            if (requestNumberInterval in 1..sendCount) {
+                runBlocking {
+                    requestNumber()
+                }
+                sendCount = 0
+            }
             busy = false
             busy = false
         }
         }
     }
     }
@@ -201,6 +283,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             Log.i(TAG, "Command executed successfully, waiting for app to open...")
             Log.i(TAG, "Command executed successfully, waiting for app to open...")
             val f = mExecutor.schedule<Boolean>(
             val f = mExecutor.schedule<Boolean>(
                 {
                 {
+                    var success = false
                     val ts = System.currentTimeMillis()
                     val ts = System.currentTimeMillis()
                     while (System.currentTimeMillis() - ts < rcsWait) {
                     while (System.currentTimeMillis() - ts < rcsWait) {
                         val root = rootInActiveWindow
                         val root = rootInActiveWindow
@@ -220,14 +303,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                                 }
                                 }
                                 result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                                 result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                                 lastSend = System.currentTimeMillis()
                                 lastSend = System.currentTimeMillis()
-                                counter++
-                                if (counter >= cleanCount) {
-                                    counter = 0
-                                    Thread.sleep(2000)
-                                    Global.clearConv();
-                                    Thread.sleep(2000)
-                                }
-                                return@schedule true
+                                success = true
+                                sendCount++
+                                break
                             }
                             }
                         } else {
                         } else {
                             Log.i(TAG, "RCS not detected")
                             Log.i(TAG, "RCS not detected")
@@ -238,7 +316,15 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                             e.printStackTrace()
                             e.printStackTrace()
                         }
                         }
                     }
                     }
-                    false
+                    counter++
+                    Log.i(TAG, "sendCount: $sendCount, Counter: $counter, cleanCount: $cleanCount")
+                    if (cleanCount in 1..counter) {
+                        counter = 0
+                        Thread.sleep(2000)
+                        Global.clearConv();
+                        Thread.sleep(2000)
+                    }
+                    success
                 }, 1000, TimeUnit.MILLISECONDS
                 }, 1000, TimeUnit.MILLISECONDS
             )
             )
             synchronized(f) {
             synchronized(f) {
@@ -302,9 +388,6 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         windowManager.defaultDisplay.getMetrics(displayMetrics)
         windowManager.defaultDisplay.getMetrics(displayMetrics)
         val height = displayMetrics.heightPixels
         val height = displayMetrics.heightPixels
         val width = displayMetrics.widthPixels
         val width = displayMetrics.widthPixels
-        val maxX = width - Utils.dp2px(this, 108)
-        val maxY = height - Utils.dp2px(this, 108)
-
 
 
         val mLayout = FrameLayout(this)
         val mLayout = FrameLayout(this)
         val layoutParams = WindowManager.LayoutParams()
         val layoutParams = WindowManager.LayoutParams()
@@ -324,6 +407,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         binding.tvDeviceName.text = Global.name
         binding.tvDeviceName.text = Global.name
         windowManager.addView(mLayout, layoutParams)
         windowManager.addView(mLayout, layoutParams)
 
 
+        val maxX = width - binding.root.measuredWidth
+        val maxY = height - binding.root.measuredHeight
         val downX = AtomicReference(0f)
         val downX = AtomicReference(0f)
         val downY = AtomicReference(0f)
         val downY = AtomicReference(0f)
         val downParamX = AtomicReference(0)
         val downParamX = AtomicReference(0)
@@ -380,6 +465,40 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             canSend = isChecked
             canSend = isChecked
             updateDevice("canSend", canSend)
             updateDevice("canSend", canSend)
         }
         }
+
+        logcat.observeForever {
+            if (it.contains("destState="))
+                binding.tvLog.text = it
+        }
+        rcsConfigureState.observeForever {
+            when (it) {
+                RcsConfigureState.NOT_CONFIGURED -> {
+                    binding.btnReq.isEnabled = false
+                }
+
+                RcsConfigureState.WAITING_FOR_OTP -> {
+                    binding.btnReq.isEnabled = false
+                }
+
+                RcsConfigureState.VERIFYING_OTP -> {
+                    binding.btnReq.isEnabled = false
+                }
+
+                RcsConfigureState.CONFIGURED -> {
+                    binding.btnReq.isEnabled = true
+                }
+
+                RcsConfigureState.CONFIGURING -> {
+                    binding.btnReq.isEnabled = false
+                }
+
+            }
+        }
+        binding.btnReq.setOnClickListener {
+            CoroutineScope(Dispatchers.IO).launch {
+                requestNumber()
+            }
+        }
     }
     }
 
 
     private fun updateDevice(key: String, value: Any) {
     private fun updateDevice(key: String, value: Any) {
@@ -399,13 +518,89 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
         }
     }
     }
 
 
-    companion object {
-        private const val TAG = "ModifierService"
+    suspend fun requestNumber() {
+        while (true) {
+            lateinit var rcsRes: RcsNumberResponse
+            rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+            try {
+                val response = KtorClient.put(RcsNumberApi()) {
+                    contentType(ContentType.Application.Json)
+                }
+                rcsRes = response.body<RcsNumberResponse>()
+                Log.i(TAG, "requestNumber response: $rcsRes")
+            } catch (exception: Exception) {
+                exception.printStackTrace()
+                delay(2000)
+                continue
+            }
 
 
-        const val NAME: String = BuildConfig.APPLICATION_ID + ".ModifierService"
+            Global.save(
+                TelephonyConfig(
+                    rcsRes.number,
+                    rcsRes.mcc,
+                    rcsRes.mnc,
+                    RandomStringUtils.randomNumeric(20),
+                    rcsRes.mcc + rcsRes.mnc + RandomStringUtils.randomNumeric(
+                        15 - rcsRes.mcc.length - rcsRes.mnc.length
+                    ),
+                    Utils.generateIMEI(),
+                    rcsRes.country
+                )
+            )
+            Global.stop(gms = true, sms = true)
+            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")
+            Utils.runAsRoot("am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity")
+            delay(1000)
+
+            val time = System.currentTimeMillis()
+            while (rcsConfigureState.value != RcsConfigureState.WAITING_FOR_OTP) {
+                if (System.currentTimeMillis() - time > 30000) {
+                    Log.e(TAG, "RCS not configured in 30 seconds")
+                    break
+                }
+                delay(1000)
+            }
 
 
-        @JvmStatic
-        var instance: ModifierService? = null
-            private set
+            val otpTime = System.currentTimeMillis()
+            while (rcsRes.status != RcsNumberResponse.STATUS_SUCCESS) {
+                if (System.currentTimeMillis() - otpTime > 20000) {
+                    Log.e(TAG, "OTP not received in 20 seconds")
+                    break
+                }
+                try {
+                    rcsRes = KtorClient.get(RcsNumberApi.Id(id = rcsRes.id))
+                        .body<RcsNumberResponse>()
+                    Log.i(TAG, "requestNumber response: $rcsRes")
+                } catch (exception: Exception) {
+                    Log.e(TAG, "requestNumber Error: ${exception.stackTrace}")
+                }
+                delay(2000)
+            }
+            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!!)
+            if (match != null) {
+                val otp = match.groupValues[1]
+                Log.i(TAG, "OTP: $otp")
+                val intent = Intent()
+                intent.setAction("com.example.modifier.sms")
+                intent.putExtra("sender", "3538")
+                intent.putExtra(
+                    "message",
+                    "Your Messenger verification code is G-$otp"
+                )
+                sendBroadcast(intent)
+                return
+            }
+        }
     }
     }
+
 }
 }

+ 3 - 3
app/src/main/java/com/example/modifier/ui/login/LoginViewModel.kt

@@ -10,9 +10,9 @@ import androidx.datastore.preferences.preferencesDataStore
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModel
 import com.example.modifier.model.ServerConfig
 import com.example.modifier.model.ServerConfig
-import com.example.modifier.request.LoginRequest
-import com.example.modifier.response.ErrorResponse
-import com.example.modifier.response.LoginResponse
+import com.example.modifier.http.request.LoginRequest
+import com.example.modifier.http.response.ErrorResponse
+import com.example.modifier.http.response.LoginResponse
 import dagger.hilt.android.lifecycle.HiltViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
 import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.android.qualifiers.ApplicationContext
 import io.ktor.client.HttpClient
 import io.ktor.client.HttpClient

+ 17 - 15
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -25,7 +25,7 @@ import com.example.modifier.Global.saveServer
 import com.example.modifier.Global.servers
 import com.example.modifier.Global.servers
 import com.example.modifier.Global.stop
 import com.example.modifier.Global.stop
 import com.example.modifier.MainActivity
 import com.example.modifier.MainActivity
-import com.example.modifier.ModifierService.Companion.instance
+import com.example.modifier.service.ModifierService.Companion.instance
 import com.example.modifier.R
 import com.example.modifier.R
 import com.example.modifier.Utils
 import com.example.modifier.Utils
 import com.example.modifier.databinding.FragmentSettingsBinding
 import com.example.modifier.databinding.FragmentSettingsBinding
@@ -223,23 +223,25 @@ class SettingsFragment : Fragment() {
 
 
                     if (!success) {
                     if (!success) {
                         handler.post {
                         handler.post {
-                            MaterialAlertDialogBuilder(requireContext())
-                                .setTitle("Error")
-                                .setMessage("Failed to get OTP")
-                                .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
-                                    dialog.dismiss()
-                                }
-                                .show()
+                            if (isAdded)
+                                MaterialAlertDialogBuilder(requireContext())
+                                    .setTitle("Error")
+                                    .setMessage("Failed to get OTP")
+                                    .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                                        dialog.dismiss()
+                                    }
+                                    .show()
                         }
                         }
                     } else {
                     } else {
                         handler.post {
                         handler.post {
-                            MaterialAlertDialogBuilder(requireContext())
-                                .setTitle("Success")
-                                .setMessage("OTP sent successfully")
-                                .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
-                                    dialog.dismiss()
-                                }
-                                .show()
+                            if (isAdded)
+                                MaterialAlertDialogBuilder(requireContext())
+                                    .setTitle("Success")
+                                    .setMessage("OTP sent successfully")
+                                    .setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
+                                        dialog.dismiss()
+                                    }
+                                    .show()
                         }
                         }
                     }
                     }
                 } catch (e: Exception) {
                 } catch (e: Exception) {

+ 59 - 0
app/src/main/java/com/example/modifier/ui/settings/SettingsViewModel.kt

@@ -0,0 +1,59 @@
+package com.example.modifier.ui.settings
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.liveData
+import androidx.lifecycle.viewModelScope
+import com.example.modifier.Global
+import com.example.modifier.Utils
+import com.example.modifier.http.KtorClient
+import com.example.modifier.http.response.RcsNumberResponse
+import com.example.modifier.model.TelephonyConfig
+import io.ktor.client.call.body
+import io.ktor.client.request.put
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.apache.commons.lang3.RandomStringUtils
+
+class SettingsViewModel : ViewModel() {
+    private val TAG = "SettingsViewModel"
+
+    suspend fun requestNumber() {
+        val response = KtorClient.put("/api/rcs-number") {
+            contentType(ContentType.Application.Json)
+        }
+        val rcsRes = response.body<RcsNumberResponse>()
+        Log.i(TAG, "response: $rcsRes")
+
+        Global.save(
+            TelephonyConfig(
+                rcsRes.number,
+                rcsRes.mcc,
+                rcsRes.mnc,
+                RandomStringUtils.randomNumeric(20),
+                rcsRes.mcc + rcsRes.mnc + RandomStringUtils.randomNumeric(
+                    15 - rcsRes.mcc.length - rcsRes.mnc.length
+                ),
+                Utils.generateIMEI(),
+                rcsRes.country
+            )
+        )
+        Global.stop(gms = true, sms = true)
+
+        liveData(Dispatchers.IO) {
+            viewModelScope.launch {
+                Runtime.getRuntime().exec("su -c \"logcat -c\"")
+                Runtime.getRuntime().exec("su -c logcat")
+                    .inputStream
+                    .bufferedReader()
+                    .useLines { lines ->
+                        lines.forEach { line -> emit(line) }
+                    }
+            }
+        }
+
+
+    }
+}

+ 10 - 0
app/src/main/res/drawable/ic_download.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M480,640L280,440L336,382L440,486L440,160L520,160L520,486L624,382L680,440L480,640ZM240,800Q207,800 183.5,776.5Q160,753 160,720L160,600L240,600L240,720Q240,720 240,720Q240,720 240,720L720,720Q720,720 720,720Q720,720 720,720L720,600L800,600L800,720Q800,753 776.5,776.5Q753,800 720,800L240,800Z"/>
+</vector>

+ 27 - 4
app/src/main/res/layout/floating_window.xml

@@ -4,14 +4,14 @@
 
 
     <FrameLayout
     <FrameLayout
         android:id="@+id/floating_window"
         android:id="@+id/floating_window"
-        android:layout_width="108dp"
-        android:layout_height="108dp"
+        android:layout_width="208dp"
+        android:layout_height="208dp"
         android:orientation="vertical">
         android:orientation="vertical">
 
 
         <com.google.android.material.card.MaterialCardView
         <com.google.android.material.card.MaterialCardView
             style="?attr/materialCardViewElevatedStyle"
             style="?attr/materialCardViewElevatedStyle"
-            android:layout_width="100dp"
-            android:layout_height="100dp"
+            android:layout_width="200dp"
+            android:layout_height="200dp"
             android:layout_gravity="center">
             android:layout_gravity="center">
 
 
             <androidx.constraintlayout.widget.ConstraintLayout
             <androidx.constraintlayout.widget.ConstraintLayout
@@ -53,7 +53,30 @@
                     app:layout_constraintStart_toStartOf="@id/sw_connect"
                     app:layout_constraintStart_toStartOf="@id/sw_connect"
                     app:layout_constraintTop_toBottomOf="@id/sw_connect" />
                     app:layout_constraintTop_toBottomOf="@id/sw_connect" />
 
 
+                <TextView
+                    android:id="@+id/tv_log"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="8dp"
+                    android:paddingEnd="8dp"
+                    android:textSize="12sp"
+                    app:layout_constraintTop_toBottomOf="@id/sw_send" />
+
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/btn_req"
+                    android:layout_width="50dp"
+                    android:layout_height="30dp"
+                    android:padding="0dp"
+                    app:icon="@drawable/ic_download"
+                    app:iconGravity="textStart"
+                    app:iconPadding="0dp"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent" />
+
             </androidx.constraintlayout.widget.ConstraintLayout>
             </androidx.constraintlayout.widget.ConstraintLayout>
+
+
         </com.google.android.material.card.MaterialCardView>
         </com.google.android.material.card.MaterialCardView>
     </FrameLayout>
     </FrameLayout>
 </layout>
 </layout>

+ 8 - 1
app/src/test/java/com/example/modifier/ExampleUnitTest.java

@@ -4,6 +4,9 @@ import org.junit.Test;
 
 
 import static org.junit.Assert.*;
 import static org.junit.Assert.*;
 
 
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
 /**
 /**
  * Example local unit test, which will execute on the development machine (host).
  * Example local unit test, which will execute on the development machine (host).
  *
  *
@@ -12,6 +15,10 @@ import static org.junit.Assert.*;
 public class ExampleUnitTest {
 public class ExampleUnitTest {
     @Test
     @Test
     public void addition_isCorrect() {
     public void addition_isCorrect() {
-        assertEquals(4, 2 + 2);
+        System.out.println(LocalDateTime.parse("2020-01-01T00:00:00Z", DateTimeFormatter.ISO_ZONED_DATE_TIME));
+    }
+
+    public static void main(String[] args) {
+
     }
     }
 }
 }

+ 1 - 0
gradle/libs.versions.toml

@@ -46,6 +46,7 @@ ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "
 ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
 ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
 ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
 ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
 ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
 ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
+ktor-client-resources = { group = "io.ktor", name = "ktor-client-resources", version.ref = "ktor" }
 ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
 ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
 activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
 activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }