x1ongzhu 1 سال پیش
والد
کامیت
95a166156d

+ 2 - 2
app/build.gradle

@@ -20,8 +20,8 @@ android {
         applicationId "com.example.modifier"
         minSdk 26
         targetSdk 34
-        versionCode 1
-        versionName "1.0"
+        versionCode 101
+        versionName "1.0.1"
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }

BIN
app/src/main/assets/bin/frida-inject-16.3.3-android-arm64


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

@@ -305,23 +305,6 @@ object Global {
         }
     }
 
-    @JvmStatic
-    fun revealMessaging() {
-        try {
-            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()
-        }
-    }
-
     @JvmStatic
     fun resetAll() {
         val context = Utils.getContext()

+ 83 - 0
app/src/main/java/com/example/modifier/MsgAppModel.kt

@@ -0,0 +1,83 @@
+package com.example.modifier
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.liveData
+import com.example.modifier.enums.RcsConfigureState
+import com.example.modifier.service.ModifierService
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import org.apache.commons.collections4.queue.CircularFifoQueue
+
+class MsgAppModel(private val modifierService: ModifierService) {
+
+    private val rcsConfigureState = MutableLiveData(RcsConfigureState.CONFIGURED)
+    private val logcat = liveData(Dispatchers.IO) {
+        try {
+            val logs = CircularFifoQueue<String>(128)
+            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 time")
+                writer.newLine()
+                writer.flush()
+            }
+            p.inputStream
+                .bufferedReader()
+                .useLines { 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")) {
+                            rcsConfigureState.postValue(RcsConfigureState.VERIFYING_OTP)
+                        } else if (line.contains("destState=ConfiguredState")) {
+                            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(
+                            line
+                        )?.apply {
+                            val time = groups["time"]?.value?.dropLast(4)
+                            val log = groups["log"]?.value
+                                ?.replace(Regex("\\[\\w+-\\w+-\\w+-\\w+-\\w+]"), "")
+                                ?.replace(Regex("\\[CONTEXT.*]"), "")
+                                ?.trim()
+
+                            if (time != null && log != null) {
+                                if (log.contains("destState=")) {
+                                    logs.add("$time: $log")
+                                    emit(logs.joinToString("\n"))
+                                    delay(100)
+                                    emit(logs.joinToString("\n"))
+                                }
+                            }
+                        }
+                    }
+                }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
+    fun isRcsSwitchOn(): Boolean {
+        val nodes = modifierService.getNodes()
+        nodes.find {
+            it.text.contains("Turn on RCS chats") || it.text.contains("开启 RCS 聊天功能")
+        }?.let { node ->
+            modifierService.getNodes(node)
+                .find { "com.google.android.apps.messaging:id/switchWidget" == it.viewIdResourceName }
+                ?.let {
+                    return true
+                }
+        }
+        return false
+    }
+}

+ 4 - 1
app/src/main/java/com/example/modifier/serializer/LocalDateTimeSerializer.kt

@@ -5,6 +5,8 @@ import kotlinx.serialization.Serializer
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
 import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
 
 @Serializer(forClass = LocalDateTime::class)
@@ -17,6 +19,7 @@ class LocalDateTimeSerializer : KSerializer<LocalDateTime> {
     }
 
     override fun deserialize(decoder: Decoder): LocalDateTime {
-        return LocalDateTime.parse(decoder.decodeString(), formatter)
+        return ZonedDateTime.parse(decoder.decodeString(), formatter)
+            .withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
     }
 }

+ 214 - 179
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -23,7 +23,6 @@ import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.CompoundButton
 import android.widget.FrameLayout
-import android.widget.Toast
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.preferencesDataStore
@@ -34,7 +33,6 @@ import androidx.lifecycle.liveData
 import com.example.modifier.BuildConfig
 import com.example.modifier.CMD_BACK
 import com.example.modifier.CMD_CONVERSATION_LIST_ACTIVITY
-import com.example.modifier.CMD_HOME
 import com.example.modifier.CMD_MESSAGING_APP
 import com.example.modifier.CMD_RCS_SETTINGS_ACTIVITY
 import com.example.modifier.Global
@@ -82,7 +80,10 @@ import org.apache.commons.lang3.RandomStringUtils
 import org.json.JSONException
 import org.json.JSONObject
 import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
+import java.time.ZoneId
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
+import java.time.temporal.ChronoUnit
 import java.util.Optional
 import java.util.concurrent.atomic.AtomicReference
 import java.util.concurrent.locks.ReentrantLock
@@ -94,12 +95,13 @@ 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")
 
 @SuppressLint("SetTextI18n")
 class ModifierService : AccessibilityService(), Emitter.Listener {
     companion object {
-        private const val TAG = "ModifierService"
+        private const val TAG = "ModifierService1"
 
         const val NAME: String = BuildConfig.APPLICATION_ID + ".service.ModifierService"
 
@@ -491,6 +493,19 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         return false
     }
 
+    fun getNodes(node: AccessibilityNodeInfo? = null): List<AccessibilityNodeInfo> {
+        fun traverseChildren(node: AccessibilityNodeInfo): List<AccessibilityNodeInfo> {
+            val result = mutableListOf<AccessibilityNodeInfo>()
+            for (i in 0 until node.childCount) {
+                val child = node.getChild(i)
+                result.add(child)
+                result.addAll(traverseChildren(child))
+            }
+            return result
+        }
+        return traverseChildren(node ?: rootInActiveWindow)
+    }
+
     private fun traverseNode(node: AccessibilityNodeInfo?, result: TraverseResult) {
         if (node == null) {
             return
@@ -583,18 +598,26 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         val inflater = LayoutInflater.from(newContext)
         binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
         binding.swSend.text = Global.name
+        binding.tvVersion.text = "v${BuildConfig.VERSION_CODE}"
         windowManager.addView(mLayout, layoutParams)
 
-        val maxX = width - binding.root.measuredWidth
-        val maxY = height - binding.root.measuredHeight
+
+        var maxX = 0
+        var maxY = 0
+        binding.root.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY)
+        binding.root.post {
+            maxX = width - binding.root.measuredWidth
+            maxY = height - binding.root.measuredHeight
+            Log.i(TAG, "measured: $maxX, $maxY")
+            layoutParams.x = maxX
+            windowManager.updateViewLayout(mLayout, layoutParams)
+        }
+
         val downX = AtomicReference(0f)
         val downY = AtomicReference(0f)
         val downParamX = AtomicReference(0)
         val downParamY = AtomicReference(0)
 
-        layoutParams.x = maxX
-        windowManager.updateViewLayout(mLayout, layoutParams)
-
         val touchListener = OnTouchListener { v, event ->
             when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
@@ -624,7 +647,6 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             }
             false
         }
-
         binding.swSend.setOnTouchListener(touchListener)
 
         binding.swConnect.isChecked = true
@@ -730,199 +752,213 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
         requestNumberCount++
         requesting.postValue(true)
-        val result = withTimeoutOrNull(1.hours) {
+        var requestSuccess = false
+        withTimeoutOrNull(1.hours) {
             while (true) {
-                var rcsRes: RcsNumberResponse? = null
-                rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                try {
+                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
 
-                withTimeoutOrNull(10.minutes) {
-                    var retry = 0
-                    while (true) {
-                        withContext(Dispatchers.Main) {
-                            binding.tvLog.text = "requesting number...${++retry}"
-                        }
-                        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")
-                            break
-                        } catch (exception: Exception) {
-                            exception.printStackTrace()
-                            delay(2000)
-                        }
+                    withContext(Dispatchers.Main) {
+                        binding.tvLog.text = "requesting number..."
                     }
-                }
-                if (rcsRes == null) {
-                    Log.e(TAG, "requesting success, waiting for logs...")
-                    continue
-                }
 
-                withContext(Dispatchers.Main) {
-                    binding.tvLog.text = ""
-                }
+                    val response = KtorClient.put(
+                        RcsNumberApi()
+                    ) {
+                        contentType(ContentType.Application.Json)
+                        setBody(
+                            RcsNumberRequest(
+                                deviceId = Utils.getUniqueID(),
+                                taskId = currentTaskId
+                            )
+                        )
+                    }
+                    var rcsNumber = response.body<RcsNumberResponse>()
+                    Log.i(TAG, "requestNumber response: $rcsNumber")
 
-                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
+                    withContext(Dispatchers.Main) {
+                        binding.tvLog.text = "requesting success, waiting for logs..."
+                    }
+
+                    Global.save(
+                        TelephonyConfig(
+                            rcsNumber.number,
+                            rcsNumber.mcc,
+                            rcsNumber.mnc,
+                            RandomStringUtils.randomNumeric(20),
+                            rcsNumber.mcc + rcsNumber.mnc + RandomStringUtils.randomNumeric(
+                                15 - rcsNumber.mcc.length - rcsNumber.mnc.length
+                            ),
+                            Utils.generateIMEI(),
+                            rcsNumber.country
+                        )
                     )
-                )
 
-                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
+                    if (requestNumberCount > 999999999) {
+                        val resetSuccess = withTimeoutOrNull(5.minutes) {
+                            while (true) {
+                                withContext(Dispatchers.Main) {
+                                    binding.tvLog.text = "Waiting for RCS switch on..."
+                                }
+                                rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
+                                resetAll()
+                                val switchAppear = withTimeoutOrNull(2.minutes) {
+                                    while (true) {
+                                        if (rcsConfigureState.value == RcsConfigureState.WAITING_FOR_DEFAULT_ON) {
+                                            break
+                                        }
+                                        delay(1.seconds)
                                     }
-                                    delay(1.seconds)
+                                    true
+                                } ?: false
+                                if (!switchAppear) {
+                                    Log.e(TAG, "RCS not entered default on state, retrying...")
+                                    continue
                                 }
-                                true
-                            } ?: false
-                            if (!switchAppear) {
-                                Log.e(TAG, "RCS not entered default on state, retrying...")
-                                continue
-                            }
-                            Utils.runAsRoot(
-                                CMD_RCS_SETTINGS_ACTIVITY,
-                                "sleep 1"
-                            )
-                            val res = TraverseResult()
-                            traverseNode(rootInActiveWindow, res)
-                            if (res.rcsSwitch == null) {
-                                Log.e(TAG, "RCS switch not found, retrying...")
-                                continue
+                                Utils.runAsRoot(
+                                    CMD_RCS_SETTINGS_ACTIVITY,
+                                    "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",
+                                    CMD_BACK,
+                                    CMD_RCS_SETTINGS_ACTIVITY,
+                                )
+                                Log.i(TAG, "RCS switch turned on, waiting for state change...")
+                                val resetSuccess = waitForRcsState(
+                                    arrayOf(
+                                        RcsConfigureState.READY,
+                                        RcsConfigureState.RETRY
+                                    ), 2.minutes
+                                )
+                                Log.i(TAG, "waitForRcsState: $resetSuccess")
+                                Global.suspend(gms = true, sms = true)
+                                delay(1000)
+                                Global.unsuspend(gms = true, sms = true)
+                                delay(1000)
+                                requestNumberCount = 0
+                                break
                             }
-                            val rect = Rect()
-                            res.rcsSwitch!!.getBoundsInScreen(rect)
-                            Utils.runAsRoot(
-                                "input tap ${rect.centerX()} ${rect.centerY()}",
-                                "sleep 1",
-                                CMD_BACK,
-                                CMD_RCS_SETTINGS_ACTIVITY,
-                            )
-                            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
+                            true
+                        } ?: false
+                        if (!resetSuccess) {
+                            Log.e(TAG, "RCS reset failed, retrying...")
+                            continue
                         }
-                        true
-                    } ?: false
-                    if (!resetSuccess) {
-                        Log.e(TAG, "RCS reset failed, retrying...")
-                        continue
                     }
-                } else {
-                    Global.revealMessaging()
-                }
+                    Utils.runAsRoot(CMD_MESSAGING_APP)
+                    withContext(Dispatchers.Main) {
+                        binding.tvLog.text = "Waiting for logs..."
+                    }
 
-                if (waitForRcsState(
-                        arrayOf(RcsConfigureState.WAITING_FOR_OTP),
-                        otpTimeout
-                    ) != RcsConfigureState.WAITING_FOR_OTP
-                ) {
-                    Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
-                    continue
-                }
+                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
+                        Log.e(TAG, "RCS number expired, retrying...")
+                        continue
+                    }
+                    val sendOtpTimeout = ChronoUnit.SECONDS.between(
+                        LocalDateTime.now(),
+                        rcsNumber.expiryTime
+                    ).seconds
+                    if (sendOtpTimeout < 60.seconds) {
+                        Log.e(TAG, "OTP timeout too short, retrying...")
+                        continue
+                    }
+                    if (waitForRcsState(
+                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                            sendOtpTimeout
+                        ) != RcsConfigureState.WAITING_FOR_OTP
+                    ) {
+                        Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
+                        continue
+                    }
+                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
+                        Log.e(TAG, "RCS number expired, retrying...")
+                        continue
+                    }
 
-                withTimeoutOrNull(60.seconds) {
-                    while (true) {
-                        try {
-                            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
+                    withTimeoutOrNull(60.seconds) {
+                        while (true) {
+                            try {
+                                rcsNumber = KtorClient.get(RcsNumberApi.Id(id = rcsNumber.id))
+                                    .body<RcsNumberResponse>()
+                                Log.i(TAG, "wait for otp response: $rcsNumber")
+                                if (rcsNumber.status == RcsNumberResponse.STATUS_SUCCESS || rcsNumber.status == RcsNumberResponse.STATUS_EXPIRED) {
+                                    break
+                                }
+                            } catch (exception: Exception) {
+                                Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
                             }
-                        } catch (exception: Exception) {
-                            Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
+                            delay(2.seconds)
                         }
-                        delay(2.seconds)
                     }
-                }
 
-                if (rcsRes!!.status != RcsNumberResponse.STATUS_SUCCESS) {
-                    Log.e(TAG, "OTP not received, retrying...")
-                    continue
-                }
+                    if (rcsNumber.status != RcsNumberResponse.STATUS_SUCCESS) {
+                        Log.e(TAG, "OTP not received, retrying...")
+                        continue
+                    }
 
-                val match =
-                    Regex("Your Messenger verification code is G-(\\d{6})")
-                        .matchEntire(rcsRes!!.message!!)
-                if (match != null) {
-                    val otp = match.groupValues[1]
-                    Log.i(TAG, "OTP: $otp")
-                    val sender = "3538"
-                    val msg = "Your Messenger verification code is G-$otp"
-
-                    val configured = run a@{
-                        repeat(2) {
-                            Global.sendSmsIntent(sender, msg)
-                            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...")
+                    val match =
+                        Regex("Your Messenger verification code is G-(\\d{6})")
+                            .matchEntire(rcsNumber.message!!)
+                    if (match != null) {
+                        val otp = match.groupValues[1]
+                        Log.i(TAG, "OTP: $otp")
+                        val sender = "3538"
+                        val msg = "Your Messenger verification code is G-$otp"
+
+                        val configured = run configuring@{
+                            repeat(2) {
+                                Global.sendSmsIntent(sender, msg)
+                                val state =
+                                    waitForRcsState(
+                                        arrayOf(
+                                            RcsConfigureState.CONFIGURED,
+                                            RcsConfigureState.RETRY
+                                        ), 60.seconds
+                                    )
+                                when (state) {
+                                    RcsConfigureState.CONFIGURED -> {
+                                        return@configuring true
+                                    }
+
+                                    RcsConfigureState.RETRY -> {
+                                        waitForRcsState(
+                                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
+                                            60.seconds
+                                        )
+                                    }
+
+                                    else -> {
+                                        Log.e(TAG, "verifyOtp fail, retrying...")
+                                    }
+                                }
                             }
+                            false
+                        }
+                        if (!configured) {
+                            Log.e(TAG, "RCS not configured, retrying...")
+                            continue
+                        } else {
+                            requestSuccess = true
+                            break
                         }
-                        false
-                    }
-                    if (!configured) {
-                        Log.e(TAG, "RCS not configured, retrying...")
-                        continue
-                    } else {
-                        break
                     }
+                } catch (e: Exception) {
+                    Log.e(TAG, "requestNumberError: ${e.message}", e)
                 }
             }
-            true
-        } ?: false
+        }
         requesting.postValue(false)
-        if (result) {
+        if (requestSuccess) {
             sendCount = 0
             counter = 0
             Log.i(TAG, "requestNumber success")
@@ -930,7 +966,6 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             canSend = false
             Log.e(TAG, "requestNumber failed")
         }
-        Utils.runAsRoot(CMD_BACK)
     }
 
     suspend fun checkRcsAvailability(): Boolean {

+ 18 - 6
app/src/main/res/layout/floating_window.xml

@@ -9,11 +9,12 @@
         android:orientation="vertical">
 
         <com.google.android.material.card.MaterialCardView
-            style="?attr/materialCardViewElevatedStyle"
+            style="?attr/materialCardViewOutlinedStyle"
             android:layout_width="200dp"
             android:layout_height="200dp"
             android:layout_gravity="center"
-            android:alpha="0.8">
+            android:alpha="0.8"
+            android:elevation="0dp">
 
             <androidx.constraintlayout.widget.ConstraintLayout
                 android:layout_width="match_parent"
@@ -32,18 +33,31 @@
                     app:layout_constraintStart_toStartOf="parent"
                     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="match_parent"
                     android:layout_height="36dp"
+                    android:layout_weight="1"
                     android:checked="true"
                     android:gravity="center"
-                    android:text="Device Label"
+                    android:text="NJ-01-01"
                     android:textSize="18sp"
                     app:layout_constraintStart_toStartOf="@id/sw_connect"
                     app:layout_constraintTop_toBottomOf="@id/sw_connect" />
 
+                <TextView
+                    android:id="@+id/tv_version"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:padding="4dp"
+                    android:text="v1000"
+                    android:textSize="12sp"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+
                 <ScrollView
                     android:id="@+id/scroll"
                     android:layout_width="match_parent"
@@ -85,6 +99,7 @@
                         android:layout_height="34dp"
                         android:layout_marginLeft="8dp"
                         android:padding="0dp"
+                        android:visibility="gone"
                         app:icon="@drawable/ic_manage_search"
                         app:iconGravity="textStart"
                         app:iconPadding="0dp" />
@@ -100,10 +115,7 @@
                         app:iconGravity="textStart"
                         app:iconPadding="0dp" />
                 </LinearLayout>
-
             </androidx.constraintlayout.widget.ConstraintLayout>
-
-
         </com.google.android.material.card.MaterialCardView>
     </FrameLayout>
 </layout>

+ 13 - 12
app/src/test/java/com/example/modifier/ExampleUnitTest.kt

@@ -10,6 +10,10 @@ import kotlinx.coroutines.withTimeout
 import org.junit.Test
 
 import org.junit.Assert.*
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
 
 /**
  * Example local unit test, which will execute on the development machine (host).
@@ -18,19 +22,16 @@ import org.junit.Assert.*
  */
 class ExampleUnitTest {
     @Test
-    fun addition_isCorrect() {  println("Hello, World!")
-        CoroutineScope(Dispatchers.Default).launch {
-            runBlocking {
-                test()
-            }
-        }
+    fun test1() {
+        val time = ZonedDateTime.parse(
+            "2024-07-03T10:07:17.000Z",
+            DateTimeFormatter.ISO_ZONED_DATE_TIME
+        ).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
+        val now = LocalDateTime.now()
+
+        println("=====> $time")
+        println("=====> $now")
     }
 
-    suspend fun test() {
-        withTimeout(1000) {
 
-            println("Hello, World!")
-            delay(2000)
-        }
-    }
 }