x1ongzhu há 1 ano atrás
pai
commit
728585c882

+ 7 - 0
app/src/main/java/com/example/modifier/Global.kt

@@ -294,8 +294,15 @@ object Global {
 
     @JvmStatic
     fun resetAll() {
+        val context = Utils.getContext()
         try {
+            val dataDir = ContextCompat.getDataDir(context)
+            Utils.copyAssetFolder(context.assets, "providerDB", File(dataDir, "providerDB").path)
+            val providerDBPath = File(dataDir, "providerDB/mmssms.db").path
+
             Utils.runAsRoot(
+                "cp $providerDBPath /data/data/com.android.providers.telephony/databases/mmssms.db",
+                "chmod 660 /data/data/com.android.providers.telephony/databases/mmssms.db",
                 "pm suspend com.google.android.apps.messaging",
                 "am force-stop com.google.android.apps.messaging",
                 "pm clear com.google.android.apps.messaging",

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

@@ -0,0 +1,6 @@
+package com.example.modifier.http.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RcsNumberRequest(val deviceId: String, val taskId: String)

+ 241 - 200
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -3,6 +3,7 @@ package com.example.modifier.service
 import android.accessibilityservice.AccessibilityService
 import android.accessibilityservice.AccessibilityServiceInfo
 import android.annotation.SuppressLint
+import android.content.Context
 import android.content.Intent
 import android.graphics.PixelFormat
 import android.graphics.Rect
@@ -14,11 +15,16 @@ import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
+import android.view.View.OnTouchListener
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.CompoundButton
 import android.widget.FrameLayout
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStore
+import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.liveData
 import com.example.modifier.BuildConfig
@@ -32,12 +38,14 @@ 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.request.RcsNumberRequest
 import com.example.modifier.http.response.RcsNumberResponse
 import com.example.modifier.model.TelephonyConfig
 import com.google.android.material.color.DynamicColors
 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 io.socket.client.IO
@@ -49,6 +57,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
 import org.apache.commons.lang3.RandomStringUtils
 import org.apache.commons.lang3.StringUtils
 import org.json.JSONArray
@@ -61,7 +70,12 @@ import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
 import kotlin.math.max
 import kotlin.math.min
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.seconds
 
+val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "serverConfig")
+
+@SuppressLint("SetTextI18n")
 class ModifierService : AccessibilityService(), Emitter.Listener {
     companion object {
         private const val TAG = "ModifierService"
@@ -75,16 +89,40 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
 
     private val mExecutor: ScheduledExecutorService = ScheduledThreadPoolExecutor(8)
 
-    private lateinit var mSocket: Socket
     private val mSocketOpts = IO.Options()
-    private var canSend = false
+    private lateinit var mSocket: Socket
     private lateinit var binding: FloatingWindowBinding
+
+    private var canSend: Boolean
+        get() {
+            return getSharedPreferences(
+                BuildConfig.APPLICATION_ID,
+                MODE_PRIVATE
+            ).getBoolean("canSend", false)
+        }
+        set(value) {
+            getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
+                .putBoolean("canSend", value).apply()
+            reportDeviceStatues()
+        }
     private var counter = 0
-    private var _busy = false
     private var cleanCount = 0
     private var lastSend = 0L
     private var rcsInterval = 0L
     private var requestNumberInterval = 0
+    private val running = MutableLiveData(false)
+    private val requesting = MutableLiveData(false)
+    private var currentTaskId = 0
+
+    private var busy = MediatorLiveData<Boolean>().apply {
+        addSource(running) {
+            value = it || requesting.value!!
+        }
+        addSource(requesting) {
+            value = it || running.value!!
+        }
+        value = requesting.value!! || running.value!!
+    }
 
     private val rcsConfigureState = MutableLiveData(RcsConfigureState.CONFIGURED)
 
@@ -112,8 +150,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 .putInt("requestNumberCount", value).apply()
         }
 
-
-    val logcat = liveData(Dispatchers.IO) {
+    private val logcat = liveData(Dispatchers.IO) {
         try {
             val p = Runtime.getRuntime().exec("su")
             p.outputStream.bufferedWriter().use { writer ->
@@ -148,53 +185,41 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
     }
 
-    private var busy: Boolean
-        get() {
-            return _busy
-        }
-        set(value) {
-            _busy = value
-            updateDevice("busy", value)
-        }
-
     fun connect() {
-        CoroutineScope(Dispatchers.IO).launch {
-            try {
-                load()
-                if (this@ModifierService::binding.isInitialized) {
-                    withContext(Dispatchers.Main) {
-                        binding.tvDeviceName.text = Global.name
-                    }
-                }
-                if (this@ModifierService::mSocket.isInitialized) {
-                    mSocket.disconnect()
-                }
-                canSend = getSharedPreferences(
-                    BuildConfig.APPLICATION_ID,
-                    MODE_PRIVATE
-                ).getBoolean("canSend", false)
-                mSocketOpts.query =
-                    "model=" + Build.MANUFACTURER + " " + Build.MODEL + "&name=" + Global.name + "&id=" + Utils.getUniqueID() + "&canSend=" + canSend
-                mSocket = IO.socket(Global.serverUrl, mSocketOpts)
-                mSocket.on("message", this@ModifierService)
-                mSocket.on(Socket.EVENT_CONNECT) {
-                    Log.i(TAG, "Connected to server")
-                }
-                mSocket.on(Socket.EVENT_DISCONNECT) {
-                    Log.i(TAG, "Disconnected from server")
+        try {
+            load()
+            if (this@ModifierService::binding.isInitialized) {
+                binding.swSend.text = Global.name
+            }
+            if (this@ModifierService::mSocket.isInitialized) {
+                mSocket.disconnect()
+            }
+            mSocketOpts.query =
+                "model=${Build.MANUFACTURER} ${Build.MODEL}&name=${Global.name}&id=${Utils.getUniqueID()}"
+            mSocketOpts.transports = arrayOf("websocket")
+            Log.i(TAG, "Connection query: ${mSocketOpts.query}")
+            mSocket = IO.socket(Global.serverUrl, mSocketOpts)
+            mSocket.on("message", this@ModifierService)
+            mSocket.on(Socket.EVENT_CONNECT) {
+                Log.i(TAG, "Connected to server")
+                CoroutineScope(Dispatchers.IO).launch {
+                    delay(500)
+                    reportDeviceStatues()
                 }
-                mSocket.on(Socket.EVENT_CONNECT_ERROR) { args ->
-                    Log.i(TAG, "Connection error: " + args[0])
-                    if (args[0] is Exception) {
-                        val e = args[0] as Exception
-                        e.printStackTrace()
-                    }
+            }
+            mSocket.on(Socket.EVENT_DISCONNECT) {
+                Log.i(TAG, "Disconnected from server")
+            }
+            mSocket.on(Socket.EVENT_CONNECT_ERROR) { args ->
+                Log.i(TAG, "Connection error: " + args[0])
+                if (args[0] is Exception) {
+                    val e = args[0] as Exception
+                    e.printStackTrace()
                 }
-
-                mSocket.connect()
-            } catch (e: Exception) {
-                e.printStackTrace()
             }
+            mSocket.connect()
+        } catch (e: Exception) {
+            e.printStackTrace()
         }
     }
 
@@ -227,6 +252,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 } else if ("task" == action) {
                     val data = json.optJSONObject("data")
                     val id = json.optString("id")
+                    currentTaskId = json.optInt("id", 0)
                     if (data != null && StringUtils.isNoneBlank(id)) {
                         runTask(id, data)
                     }
@@ -243,9 +269,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         cleanCount = config.optInt("cleanCount", 20)
         rcsInterval = config.optLong("rcsInterval", 0)
         requestNumberInterval = config.optInt("requestNumberInterval", 0)
-        val tasks = data.optJSONArray("tasks")
+        val tasks = data.optJSONArray("tasks")!!
         mExecutor.submit {
-            busy = true
+            running.postValue(true)
 
             val success = JSONArray()
             val fail = JSONArray()
@@ -281,9 +307,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 runBlocking {
                     requestNumber()
                 }
-                sendCount = 0
             }
-            busy = false
+            running.postValue(false)
         }
     }
 
@@ -304,10 +329,10 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                     while (System.currentTimeMillis() - ts < rcsWait) {
                         val root = rootInActiveWindow
                         val packageName = root.packageName.toString()
-                        val result = TraverseResult()
-                        traverseNode(root, result)
-                        if (result.isRcsCapable) {
-                            if (result.sendBtn == null) {
+                        val traverseResult = TraverseResult()
+                        traverseNode(root, traverseResult)
+                        if (traverseResult.isRcsCapable) {
+                            if (traverseResult.sendBtn == null) {
                                 Log.i(TAG, "Send button not found")
                             } else {
                                 Log.i(TAG, "Clicking send button")
@@ -317,7 +342,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                                     Log.i(TAG, "Waiting for RCS interval")
                                     Thread.sleep(rcsInterval - dt)
                                 }
-                                result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+                                traverseResult.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                                 lastSend = System.currentTimeMillis()
                                 success = true
                                 sendCount++
@@ -440,7 +465,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val newContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
         val inflater = LayoutInflater.from(newContext)
         binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
-        binding.tvDeviceName.text = Global.name
+        binding.swSend.text = Global.name
         windowManager.addView(mLayout, layoutParams)
 
         val maxX = width - binding.root.measuredWidth
@@ -450,14 +475,13 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val downParamX = AtomicReference(0)
         val downParamY = AtomicReference(0)
 
-        binding.floatingWindow.setOnTouchListener { v: View?, event: MotionEvent ->
+        var touchListener = OnTouchListener { v, event ->
             when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
                     downX.set(event.rawX)
                     downY.set(event.rawY)
                     downParamX.set(layoutParams.x)
                     downParamY.set(layoutParams.y)
-                    return@setOnTouchListener true
                 }
 
                 MotionEvent.ACTION_MOVE -> {
@@ -472,12 +496,17 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                     )
                         .toInt()
                     windowManager.updateViewLayout(mLayout, layoutParams)
-                    return@setOnTouchListener true
+                }
+
+                MotionEvent.ACTION_UP -> {
+                    return@OnTouchListener event.eventTime - event.downTime >= 200
                 }
             }
             false
         }
 
+        binding.swSend.setOnTouchListener(touchListener)
+
         binding.swConnect.isChecked = true
         binding.swConnect.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
             if (isChecked) {
@@ -489,30 +518,17 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             }
         }
 
-        canSend = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getBoolean(
-            "canSend",
-            false
-        )
         binding.swSend.isChecked = canSend
         binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
-            getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
-                .putBoolean("canSend", isChecked).apply()
             canSend = isChecked
-            updateDevice("canSend", canSend)
         }
 
         logcat.observeForever {
             if (it.contains("destState="))
                 binding.tvLog.text = it
         }
-        rcsConfigureState.observeForever {
-            when (it) {
-                RcsConfigureState.CONFIGURED -> {
-                    binding.btnReq.isEnabled = true
-                }
-
-                else -> {}
-            }
+        requesting.observeForever {
+            binding.btnReq.isEnabled = !it
         }
         binding.btnReq.setOnClickListener {
             requestNumberCount = 6
@@ -522,165 +538,190 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
         binding.btnInspect.setOnClickListener {
             CoroutineScope(Dispatchers.IO).launch {
-                traverseNode(getRootInActiveWindow(), TraverseResult())
+                traverseNode(rootInActiveWindow, TraverseResult())
             }
         }
+
+        busy.observeForever {
+            reportDeviceStatues()
+        }
     }
 
-    private fun updateDevice(key: String, value: Any) {
+    private fun reportDeviceStatues() {
         if (this::mSocket.isInitialized) {
             val data = JSONObject()
             try {
                 data.put("action", "updateDevice")
                 val dataObj = JSONObject()
-                dataObj.put(key, value)
+                dataObj.put("canSend", canSend)
+                dataObj.put("busy", busy.value)
                 data.put("data", dataObj)
-
                 mSocket.emit("message", data)
-
             } catch (e: JSONException) {
                 e.printStackTrace()
             }
         }
     }
 
-    suspend fun requestNumber() {
-        withContext(Dispatchers.Main) {
-            binding.btnReq.isEnabled = false
-        }
-        while (true) {
-            withContext(Dispatchers.Main) {
-                binding.tvLog.text = "Waiting for logs..."
-            }
-            requestNumberCount++
-            if (requestNumberCount > 5) {
-                requestNumberCount = 0
+    private suspend fun requestNumber() {
+        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
 
-                while (true) {
-                    withContext(Dispatchers.Main) {
-                        binding.tvLog.text = "Waiting for logs..."
-                    }
-                    resetAll()
-                    var resetSuccess = false
-                    val time = System.currentTimeMillis()
                     while (true) {
-                        if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_DEFAULT_ON) {
-                            Utils.runAsRoot(
-                                "am start com.google.android.apps.messaging/com.google.android.apps.messaging.ui.appsettings.RcsSettingsActivity",
-                                "sleep 1"
-                            )
-                            val res = TraverseResult()
-                            traverseNode(getRootInActiveWindow(), res)
-                            if (res.rcsSwitch != null) {
-                                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",
-                                )
-                                resetSuccess = true
-                                Log.i(TAG, "RCS switch turned on, waiting 60 seconds...")
-                                delay(60000)
+                        withContext(Dispatchers.Main) {
+                            binding.tvLog.text = "Waiting for logs..."
+                        }
+                        resetAll()
+                        val switchAppear = withTimeoutOrNull(60.seconds) {
+                            while (true) {
+                                if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_DEFAULT_ON) {
+                                    break
+                                }
+                                delay(1.seconds)
                             }
-                            break
+                            true
+                        } ?: false
+                        if (!switchAppear) {
+                            Log.e(TAG, "RCS not entered default on state, retrying...")
+                            continue
                         }
-                        if (System.currentTimeMillis() - time > 60000) {
-                            Log.e(TAG, "rcsConfigureState not changed in 60 seconds")
-                            break
-                        } else {
-                            delay(3000)
+                        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
                         }
-                    }
-                    if (resetSuccess) {
+                        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)
                 }
-                rcsRes = response.body<RcsNumberResponse>()
-                Log.i(TAG, "requestNumber response: $rcsRes")
-            } catch (exception: Exception) {
-                exception.printStackTrace()
-                delay(2000)
-                continue
-            }
+                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.toString()
+                            )
+                        )
+                    }
+                    rcsRes = response.body<RcsNumberResponse>()
+                    Log.i(TAG, "requestNumber response: $rcsRes")
+                } catch (exception: Exception) {
+                    exception.printStackTrace()
+                    delay(2000)
+                    continue
+                }
 
-            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.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
+                    )
                 )
-            )
-            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)
+                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 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
+                val waitingForOtp = withTimeoutOrNull(30000) {
+                    repeat(1000) {
+                        if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_OTP) {
+                            return@repeat
+                        }
+                        delay(1000)
+                    }
+                    true
+                } ?: false
+
+                if (!waitingForOtp) {
+                    Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
+                    continue
                 }
-                delay(1000)
-            }
 
-            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
+                withTimeoutOrNull(20000) {
+                    while (true) {
+                        try {
+                            rcsRes = KtorClient.get(RcsNumberApi.Id(id = rcsRes.id))
+                                .body<RcsNumberResponse>()
+                            Log.i(TAG, "wait for otp response: $rcsRes")
+                            if (rcsRes.status == RcsNumberResponse.STATUS_SUCCESS) {
+                                break
+                            }
+                        } catch (exception: Exception) {
+                            Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
+                        }
+                        delay(2000)
+                    }
                 }
-                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}")
+
+                if (rcsRes.status != RcsNumberResponse.STATUS_SUCCESS) {
+                    Log.e(TAG, "OTP not received, retrying...")
+                    continue
                 }
-                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
+                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)
+                    break
+                }
             }
+            true
+        } ?: false
+        requesting.postValue(false)
+        if (result) {
+            sendCount = 0
+            counter = 0
+            Log.i(TAG, "requestNumber success")
+        } else {
+            Log.e(TAG, "requestNumber failed")
         }
     }
-
 }

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

@@ -9,10 +9,10 @@ import androidx.datastore.preferences.core.stringPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
-import com.example.modifier.model.ServerConfig
 import com.example.modifier.http.request.LoginRequest
 import com.example.modifier.http.response.ErrorResponse
 import com.example.modifier.http.response.LoginResponse
+import com.example.modifier.model.ServerConfig
 import dagger.hilt.android.lifecycle.HiltViewModel
 import dagger.hilt.android.qualifiers.ApplicationContext
 import io.ktor.client.HttpClient
@@ -37,7 +37,7 @@ val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "se
 
 @HiltViewModel
 class LoginViewModel @Inject constructor() : ViewModel() {
-    val TAG = "LoginViewModel"
+    private val TAG = "LoginViewModel"
 
     companion object {
         val URL = stringPreferencesKey("url")

+ 10 - 0
app/src/main/res/drawable/ic_autorenew.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="M204,642Q182,604 171,564Q160,524 160,482Q160,348 253,254Q346,160 480,160L487,160L423,96L479,40L639,200L479,360L423,304L487,240L480,240Q380,240 310,310.5Q240,381 240,482Q240,508 246,533Q252,558 264,582L204,642ZM481,920L321,760L481,600L537,656L473,720L480,720Q580,720 650,649.5Q720,579 720,478Q720,452 714,427Q708,402 696,378L756,318Q778,356 789,396Q800,436 800,478Q800,612 707,706Q614,800 480,800L473,800L537,864L481,920Z"/>
+</vector>

+ 13 - 19
app/src/main/res/layout/floating_window.xml

@@ -12,23 +12,13 @@
             style="?attr/materialCardViewElevatedStyle"
             android:layout_width="200dp"
             android:layout_height="200dp"
-            android:layout_gravity="center">
+            android:layout_gravity="center"
+            android:alpha="0.7">
 
             <androidx.constraintlayout.widget.ConstraintLayout
                 android:layout_width="match_parent"
                 android:layout_height="match_parent">
 
-                <com.google.android.material.textview.MaterialTextView
-                    android:id="@+id/tv_device_name"
-                    android:layout_width="match_parent"
-                    android:layout_height="24dp"
-                    android:ellipsize="end"
-                    android:gravity="center"
-                    android:lines="1"
-                    android:text="01-001"
-                    android:textColor="?attr/colorPrimary"
-                    android:textSize="14sp"
-                    app:layout_constraintTop_toTopOf="parent" />
 
                 <com.google.android.material.checkbox.MaterialCheckBox
                     android:id="@+id/sw_connect"
@@ -40,29 +30,33 @@
                     android:textSize="14sp"
                     android:visibility="gone"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@id/tv_device_name" />
+                    app:layout_constraintTop_toTopOf="parent" />
 
                 <com.google.android.material.checkbox.MaterialCheckBox
                     android:id="@+id/sw_send"
                     style="@style/Widget.Material3.CompoundButton.CheckBox"
-                    android:layout_width="wrap_content"
+                    android:layout_width="match_parent"
                     android:layout_height="36dp"
                     android:checked="true"
-                    android:text="Enabled"
-                    android:textSize="14sp"
+                    android:gravity="center"
+                    android:text="Device Label"
+                    android:textSize="18sp"
                     app:layout_constraintStart_toStartOf="@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:layout_height="0dp"
+                    android:ellipsize="start"
                     android:paddingStart="8dp"
                     android:paddingEnd="8dp"
                     android:textSize="12sp"
+                    app:layout_constraintBottom_toTopOf="@id/btns"
                     app:layout_constraintTop_toBottomOf="@id/sw_send" />
 
                 <LinearLayout
+                    android:id="@+id/btns"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:orientation="horizontal"
@@ -73,9 +67,9 @@
                     <com.google.android.material.button.MaterialButton
                         android:id="@+id/btn_req"
                         android:layout_width="50dp"
-                        android:layout_height="30dp"
+                        android:layout_height="34dp"
                         android:padding="0dp"
-                        app:icon="@drawable/ic_download"
+                        app:icon="@drawable/ic_autorenew"
                         app:iconGravity="textStart"
                         app:iconPadding="0dp" />
 

+ 23 - 11
app/src/test/java/com/example/modifier/ExampleUnitTest.kt

@@ -1,24 +1,36 @@
-package com.example.modifier;
+package com.example.asdfasdfawf
 
-import org.junit.Test;
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.Test
 
-import static org.junit.Assert.*;
-
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
+import org.junit.Assert.*
 
 /**
  * Example local unit test, which will execute on the development machine (host).
  *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ * See [testing documentation](http://d.android.com/tools/testing).
  */
-public class ExampleUnitTest {
+class ExampleUnitTest {
     @Test
-    public void addition_isCorrect() {
-        System.out.println(LocalDateTime.parse("2020-01-01T00:00:00Z", DateTimeFormatter.ISO_ZONED_DATE_TIME));
+    fun addition_isCorrect() {  println("Hello, World!")
+        CoroutineScope(Dispatchers.Default).launch {
+            runBlocking {
+                test()
+            }
+        }
     }
 
-    public static void main(String[] args) {
+    suspend fun test() {
+        withTimeout(1000) {
 
+            println("Hello, World!")
+            delay(2000)
+        }
     }
 }