xiongzhu há 10 meses atrás
pai
commit
bf38cabf50

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

@@ -19,4 +19,6 @@ class TraverseResult {
     var tosAgreeBtn: AccessibilityNodeInfo? = null
 
     var closeBtn: AccessibilityNodeInfo? = null
+
+    var inputField: AccessibilityNodeInfo? = null
 }

+ 27 - 11
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -29,6 +29,7 @@ import com.example.modifier.baseTag
 import com.example.modifier.data.AppDatabase
 import com.example.modifier.databinding.FloatingWindowBinding
 import com.example.modifier.enums.ReqState
+import com.example.modifier.http.downloadImage
 import com.example.modifier.repo.AppPrefsRepo
 import com.example.modifier.repo.AppStateRepo
 import com.example.modifier.repo.BackupRepository
@@ -276,7 +277,7 @@ class ModifierService : AccessibilityService() {
         layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
         layoutParams.x = 0
         layoutParams.y = 800
-        layoutParams.gravity = Gravity.START or Gravity.TOP 
+        layoutParams.gravity = Gravity.START or Gravity.TOP
 
         uiContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
         val inflater = LayoutInflater.from(uiContext)
@@ -382,7 +383,12 @@ class ModifierService : AccessibilityService() {
                     R.id.inspect -> {
                         delay(1500)
                         screenInspector.traverseNode(TraverseResult(), true)
-                        rootInActiveWindow.findFirstByDesc("wireless debugging")
+                        rootInActiveWindow.findFirstByDesc(
+                            Regex(
+                                "wireless debugging",
+                                RegexOption.IGNORE_CASE
+                            )
+                        )
                     }
 
                     R.id.check_availability -> {
@@ -432,19 +438,29 @@ class ModifierService : AccessibilityService() {
                         delay(1500)
                         screenController.createGroup(
                             arrayOf(
-                                "+17812178092",
-                                "+13206309083",
-                                "+12723059522",
-                                "+17799708239",
-                                "+19852084968",
-                                "+15084960960"
-                            )
+                                "+160127494771",
+                                "+150797845191",
+                            ), 5000
                         )
                     }
 
-                    R.id.leave_group -> {
+                    R.id.group_send -> {
                         delay(1500)
-                        screenController.leaveGroup()
+                        if (screenController.groupSend(
+                                "Hello World!",
+                                downloadImage("https://nebuai.oss-cn-hangzhou.aliyuncs.com/image/20250313/iai4ntok.png")
+                            )
+                        ) {
+                            Log.i(TAG, "showMenu: group send success")
+                            delay(5000)
+                            if (screenController.leaveGroup()) {
+                                Log.i(TAG, "showMenu: leave group success")
+                            } else {
+                                Log.i(TAG, "showMenu: leave group failed")
+                            }
+                        } else {
+                            Log.e(TAG, "showMenu: group send failed")
+                        }
                     }
                 }
             }

+ 110 - 10
app/src/main/java/com/example/modifier/service/ScreenController.kt

@@ -2,10 +2,13 @@ package com.example.modifier.service
 
 import android.accessibilityservice.AccessibilityService
 import android.accessibilityservice.GestureDescription
+import android.content.ClipData
+import android.content.Context
 import android.content.Intent
 import android.graphics.Path
 import android.graphics.Rect
 import android.net.Uri
+import android.os.Bundle
 import android.provider.Settings
 import android.util.Log
 import android.view.accessibility.AccessibilityNodeInfo
@@ -28,6 +31,7 @@ import com.example.modifier.utils.shellRun
 import com.example.modifier.utils.smsIntent
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.withTimeoutOrNull
+import java.io.File
 
 class ScreenController(val context: AccessibilityService, private val inspector: ScreenInspector) {
 
@@ -222,7 +226,7 @@ class ScreenController(val context: AccessibilityService, private val inspector:
         node.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
     }
 
-    suspend fun createGroup(numbers: Array<String>):Boolean {
+    suspend fun createGroup(numbers: Array<String>, timeout: Long): Boolean {
         val intent = Intent(Intent.ACTION_SENDTO)
         intent.data = Uri.parse("sms:")
         intent.putExtra("exit_on_sent", true)
@@ -230,7 +234,7 @@ class ScreenController(val context: AccessibilityService, private val inspector:
         intent.setPackage(PACKAGE_MESSAGING)
         context.startActivity(intent)
         delay(1000)
-        val createGroup = context.rootInActiveWindow.findByText("Create group")
+        val createGroup = context.rootInActiveWindow.findByText(Regex("^Create group$"))
         if (createGroup == null) {
             Log.e(TAG, "not found Create group")
             return false
@@ -240,40 +244,136 @@ class ScreenController(val context: AccessibilityService, private val inspector:
         shellRun(*(numbers.flatMap {
             listOf("input text $it", "input keyevent 66")
         }).toTypedArray())
-        val next = context.rootInActiveWindow.findByText("Next")
+        val next = context.rootInActiveWindow.findByText(Regex("^Next$"))
         if (next == null) {
             Log.e(TAG, "not found Next")
             return false
         }
         next.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
         delay(500)
-        val done = context.rootInActiveWindow.findByText("Done")
+        val done = context.rootInActiveWindow.findByText(Regex("^Done$"))
         if (done == null) {
             Log.e(TAG, "not found Done")
             return false
         }
         done.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
 
+        val traverseResult = TraverseResult()
+        withTimeoutOrNull(timeout) {
+            repeat(999) {
+                inspector.traverseNode(traverseResult)
+                if (traverseResult.isRcsCapable && traverseResult.sendBtn != null) {
+                    return@withTimeoutOrNull
+                }
+                delay(200)
+            }
+        }
+        if (!traverseResult.isRcsCapable) {
+            Log.e(TAG, "RCS not capable")
+        }
+        return traverseResult.isRcsCapable
+    }
+
+    suspend fun groupSend(message: String?, img: File?): Boolean {
+        var res = TraverseResult()
+        run r1@{
+            repeat(10) {
+                if (it > 0) delay(200)
+                inspector.traverseNode(res)
+                if (res.inputField != null) {
+                    return@r1
+                }
+            }
+        }
+        if (res.inputField == null) {
+            Log.e(TAG, "not found input field")
+            return false
+        }
+        res.inputField!!.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
+        if (message != null) {
+            res.inputField!!.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, Bundle().apply {
+                putCharSequence(
+                    AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+                    message
+                )
+            })
+        }
+        if (img != null) {
+            val uri = getUriForFile(
+                context,
+                "${BuildConfig.APPLICATION_ID}.fileprovider",
+                img
+            )
+            context.grantUriPermission(
+                PACKAGE_MESSAGING,
+                uri,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION
+            )
+            val clipboardManager =
+                context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
+            val clipData = ClipData.newUri(context.contentResolver, "Image", uri)
+            clipboardManager.setPrimaryClip(clipData)
+            delay(500)
+            res.inputField!!.performAction(AccessibilityNodeInfo.ACTION_PASTE)
+        }
+        delay(500)
+        run r2@{
+            repeat(10) {
+                if (it > 0) delay(200)
+                inspector.traverseNode(res)
+                if (res.sendBtn != null) {
+                    return@r2
+                }
+            }
+        }
+        if (res.sendBtn == null) {
+            Log.e(TAG, "not found send button")
+            return false
+        }
+        res.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+        Log.i(TAG, "groupSend: click send button")
         return true
     }
 
     suspend fun leaveGroup(): Boolean {
-        val more = context.rootInActiveWindow.findFirstByDesc("More")
+        val actionBar = run r1@{
+            repeat(10) {
+                if (it > 0) delay(200)
+                context.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+                    "com.google.android.apps.messaging:id/action_bar_root"
+                ).firstOrNull().let {
+                    if (it != null) {
+                        return@r1 it
+                    }
+                }
+            }
+            return@r1 null
+        }
+        if (actionBar == null) {
+            Log.e(TAG, "leaveGroup: not found action_bar_root")
+            return false
+        }
+
+        val more = actionBar.findFirstByDesc(Regex("^More$", RegexOption.IGNORE_CASE))
         if (more == null) {
             Log.e(TAG, "leaveGroup: not found More")
             return false
         }
         more.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
         delay(500)
-        val groupDetails = context.rootInActiveWindow.findByText("Group details")
+        val groupDetails =
+            context.rootInActiveWindow.findByText(Regex("^Group details$", RegexOption.IGNORE_CASE))
         if (groupDetails == null) {
-            inspector.traverseNode(TraverseResult(), true)
             Log.e(TAG, "leaveGroup: not found Group details")
             return false
         }
-        groupDetails.performAction(AccessibilityNodeInfo.ACTION_CLICK)
-        groupDetails.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+        groupDetails!!.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
         delay(500)
+
+        context.rootInActiveWindow.findAllScrollable().forEach {
+            it.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
+        }
+
         val leave = context.rootInActiveWindow.findById("Leave group")
         if (leave == null) {
             Log.e(TAG, "leaveGroup: not found Leave group")
@@ -281,7 +381,7 @@ class ScreenController(val context: AccessibilityService, private val inspector:
         }
         leave.performAction(AccessibilityNodeInfo.ACTION_CLICK)
         delay(500)
-        val ok = context.rootInActiveWindow.findByText("OK")
+        val ok = context.rootInActiveWindow.findByText(Regex("^Cancel$"))
         if (ok == null) {
             Log.e(TAG, "not found OK")
             return false

+ 4 - 0
app/src/main/java/com/example/modifier/service/ScreenInspector.kt

@@ -121,6 +121,10 @@ class ScreenInspector(val context: AccessibilityService) {
             result.closeBtn = node
         }
 
+        if("com.google.android.apps.messaging:id/compose_message_text" == id) {
+            result.inputField = node
+        }
+
         if (node.childCount != 0) {
             for (i in 0 until node.childCount) {
                 traverse(node.getChild(i), result, log)

+ 56 - 12
app/src/main/java/com/example/modifier/service/TaskRunner.kt

@@ -1,12 +1,17 @@
 package com.example.modifier.service
 
+import android.content.ClipData
 import android.content.Context
+import android.content.Intent
+import android.net.Uri
 import android.os.Environment
 import android.util.Log
 import android.view.accessibility.AccessibilityNodeInfo
 import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider.getUriForFile
 import coil3.ImageLoader
 import coil3.request.crossfade
+import com.example.modifier.BuildConfig
 import com.example.modifier.baseTag
 import com.example.modifier.TraverseResult
 import com.example.modifier.constants.CMD_BACK
@@ -93,7 +98,8 @@ class TaskRunner(
         private const val TAG = "$baseTag/TaskRunner"
     }
 
-    private var lastSend = 0L
+    private var lastSingleSend = 0L
+    private var lastGroupSend = 0L
     private var currentTaskId = 0
     private var requestMode = 1
     private var storeNumberJob: Job? = null
@@ -150,13 +156,13 @@ class TaskRunner(
                 } else {
                     Log.i(TAG, "Clicking send button")
 
-                    val dt = System.currentTimeMillis() - lastSend
+                    val dt = System.currentTimeMillis() - lastSingleSend
                     if (taskConfig.singleDelay > 0 && dt < taskConfig.singleDelay) {
                         Log.i(TAG, "Waiting for RCS interval")
                         delay(taskConfig.singleDelay - dt)
                     }
                     traverseResult.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
-                    lastSend = System.currentTimeMillis()
+                    lastSingleSend = System.currentTimeMillis()
                     success = true
                 }
             } else {
@@ -185,14 +191,29 @@ class TaskRunner(
         img: String?,
         taskConfig: TaskConfig
     ): Boolean {
-        if (img?.isNotBlank() == true) return false
-        if (!screenController.createGroup(to)) {
-            appStateRepo.incrementGroupExecutedNum(1, false)
-            return false
+        var imgFile: File? = null
+        if (img?.isNotBlank() == true) {
+            imgFile = downloadImage(img)
         }
-        val res = TraverseResult()
-
-        return false
+        val success = run {
+            if (!screenController.createGroup(to, taskConfig.groupTimeout)) {
+                return@run false
+            }
+            val dt = System.currentTimeMillis() - lastGroupSend
+            if (taskConfig.groupDelay > 0 && dt < taskConfig.groupDelay) {
+                Log.i(TAG, "Waiting for group delay")
+                delay(taskConfig.groupDelay - dt)
+            }
+            if (!screenController.groupSend(body, imgFile)) {
+                return@run false
+            }
+            lastGroupSend = System.currentTimeMillis()
+            delay(5000)
+            screenController.leaveGroup()
+            return@run true
+        }
+        appStateRepo.incrementGroupExecutedNum(1, success)
+        return true
     }
 
     suspend fun runTask(
@@ -228,9 +249,32 @@ class TaskRunner(
             }
 
             appStateRepo.updateRuntimeFlags(running = true, checkingConnection = false)
+
+            val tasks = mutableListOf(*taskAction.data.tasks)
             val results = mutableListOf<TaskExecutionResult>()
-            for (i in 0 until taskAction.data.tasks.size) {
-                val taskItem = taskAction.data.tasks[i]
+
+            while (tasks.isNotEmpty() && taskConfig.groupMode && tasks.size > 2 && appStateRepo.appState.value.groupExecuted < taskConfig.groupQty) {
+                val items = tasks.take(taskConfig.groupQty)
+                tasks.removeAll(items)
+                val groupSent = groupSend(
+                    to = items.map { it.number }.toTypedArray(),
+                    body = items[0].message,
+                    img = items[0].img,
+                    taskConfig = taskConfig
+                )
+                results.addAll(items.map {
+                    TaskExecutionResult(
+                        id = it.id,
+                        numberId = spoofedInfoRepo.spoofedInfo.value.numberId,
+                        sent = true,
+                        delivery = if (groupSent) 1 else 0
+                    )
+                })
+            }
+
+            while (tasks.isNotEmpty()) {
+                val taskItem = tasks[0]
+                tasks.removeAt(0)
                 val result = TaskExecutionResult(
                     id = taskItem.id,
                     numberId = spoofedInfoRepo.spoofedInfo.value.numberId

+ 13 - 13
app/src/main/java/com/example/modifier/utils/AccessibilityNodeInfo.kt

@@ -2,16 +2,16 @@ package com.example.modifier.utils
 
 import android.view.accessibility.AccessibilityNodeInfo
 
-fun AccessibilityNodeInfo?.findFirstByDesc(desc: String): AccessibilityNodeInfo? {
+fun AccessibilityNodeInfo?.findFirstByDesc(pattern: Regex): AccessibilityNodeInfo? {
     if (this == null) {
         return null
     }
-    if (this.contentDescription?.toString()?.lowercase()?.contains(desc.lowercase()) == true) {
+    if (this.contentDescription?.toString()?.matches(pattern) == true) {
         return this
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
-        val result = child.findFirstByDesc(desc)
+        val child = this.getChild(i) ?: continue
+        val result = child.findFirstByDesc(pattern)
         if (result != null) {
             return result
         }
@@ -32,7 +32,7 @@ fun AccessibilityNodeInfo?.findFirstByIdAndText(
         return this
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
+        val child = this.getChild(i) ?: continue
         val result = child.findFirstByIdAndText(id, *text)
         if (result != null) {
             return result
@@ -46,7 +46,7 @@ fun AccessibilityNodeInfo.findFirstScrollable(): AccessibilityNodeInfo? {
         return this
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
+        val child = this.getChild(i) ?: continue
         val result = child.findFirstScrollable()
         if (result != null) {
             return result
@@ -61,7 +61,7 @@ fun AccessibilityNodeInfo.findAllScrollable(): List<AccessibilityNodeInfo> {
         result.add(this)
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
+        val child = this.getChild(i) ?: continue
         result.addAll(child.findAllScrollable())
     }
     return result
@@ -72,7 +72,7 @@ fun AccessibilityNodeInfo.findFirstCheckable(): AccessibilityNodeInfo? {
         return this
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
+        val child = this.getChild(i) ?: continue
         val result = child.findFirstCheckable()
         if (result != null) {
             return result
@@ -81,13 +81,13 @@ fun AccessibilityNodeInfo.findFirstCheckable(): AccessibilityNodeInfo? {
     return null
 }
 
-fun AccessibilityNodeInfo.findByText(text: String): AccessibilityNodeInfo? {
-    if (this.text?.toString()?.lowercase()?.contains(text.lowercase()) == true) {
+fun AccessibilityNodeInfo.findByText(pattern: Regex): AccessibilityNodeInfo? {
+    if (this.text?.toString()?.matches(pattern) == true) {
         return this
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
-        val result = child.findByText(text)
+        val child = this.getChild(i) ?: continue
+        val result = child.findByText(pattern)
         if (result != null) {
             return result
         }
@@ -100,7 +100,7 @@ fun AccessibilityNodeInfo.findById(id: String): AccessibilityNodeInfo? {
         return this
     }
     for (i in 0 until this.childCount) {
-        val child = this.getChild(i)
+        val child = this.getChild(i) ?: continue
         val result = child.findById(id)
         if (result != null) {
             return result

+ 29 - 25
app/src/main/java/com/example/modifier/utils/Package.kt

@@ -64,31 +64,35 @@ suspend fun resetAll() {
             "sleep 10",
             PACKAGE_GMS.clear(),
             "sleep 2",
-            "pm revoke com.google.android.gms android.permission.NEARBY_WIFI_DEVICES",
-            "pm revoke com.google.android.gms android.permission.BLUETOOTH_CONNECT",
-            "pm revoke com.google.android.gms android.permission.BLUETOOTH_SCAN",
-            "pm revoke com.google.android.gms android.permission.BLUETOOTH_ADVERTISE",
-            "pm revoke com.google.android.gms android.permission.UWB_RANGING",
-            "pm revoke com.google.android.gms android.permission.GET_ACCOUNTS",
-            "pm revoke com.google.android.gms android.permission.SYSTEM_ALERT_WINDOW",
-            "pm revoke com.google.android.gms android.permission.POST_NOTIFICATIONS",
-            "pm revoke com.google.android.gms android.permission.CAMERA",
-            "pm revoke com.google.android.gms android.permission.RECEIVE_MMS",
-            "pm revoke com.google.android.gms android.permission.GET_APP_OPS_STATS",
-            "pm revoke com.google.android.gms android.permission.PROCESS_OUTGOING_CALLS",
-            "pm revoke com.google.android.gms android.permission.READ_CALL_LOG",
-            "pm revoke com.google.android.gms android.permission.WRITE_CONTACTS",
-            "pm revoke com.google.android.gms android.permission.CALL_PHONE",
-            "pm revoke com.google.android.gms android.permission.RECORD_AUDIO",
-            "pm revoke com.google.android.gms android.permission.READ_LOGS",
-            "pm revoke com.google.android.gms android.permission.READ_MEDIA_AUDIO",
-            "pm revoke com.google.android.gms android.permission.READ_MEDIA_IMAGES",
-            "pm revoke com.google.android.gms android.permission.READ_MEDIA_VIDEO",
-            "pm revoke com.google.android.gms android.permission.ACCESS_MEDIA_LOCATION",
-            "pm revoke com.google.android.gms android.permission.ACCESS_BROADCAST_RESPONSE_STATS",
-            "pm revoke com.google.android.gms android.permission.WRITE_CALL_LOG",
-            "pm revoke com.google.android.gms android.permission.BODY_SENSORS",
-            "pm revoke com.google.android.gms android.permission.DUMP",
+            "pm revoke $PACKAGE_GMS android.permission.NEARBY_WIFI_DEVICES",
+            "pm revoke $PACKAGE_GMS android.permission.BLUETOOTH_CONNECT",
+            "pm revoke $PACKAGE_GMS android.permission.BLUETOOTH_SCAN",
+            "pm revoke $PACKAGE_GMS android.permission.BLUETOOTH_ADVERTISE",
+            "pm revoke $PACKAGE_GMS android.permission.UWB_RANGING",
+            "pm revoke $PACKAGE_GMS android.permission.GET_ACCOUNTS",
+            "pm revoke $PACKAGE_GMS android.permission.SYSTEM_ALERT_WINDOW",
+            "pm revoke $PACKAGE_GMS android.permission.POST_NOTIFICATIONS",
+            "pm revoke $PACKAGE_GMS android.permission.CAMERA",
+            "pm revoke $PACKAGE_GMS android.permission.RECEIVE_MMS",
+            "pm revoke $PACKAGE_GMS android.permission.GET_APP_OPS_STATS",
+            "pm revoke $PACKAGE_GMS android.permission.PROCESS_OUTGOING_CALLS",
+            "pm revoke $PACKAGE_GMS android.permission.READ_CALL_LOG",
+            "pm revoke $PACKAGE_GMS android.permission.WRITE_CONTACTS",
+            "pm revoke $PACKAGE_GMS android.permission.CALL_PHONE",
+            "pm revoke $PACKAGE_GMS android.permission.RECORD_AUDIO",
+            "pm revoke $PACKAGE_GMS android.permission.READ_LOGS",
+            "pm revoke $PACKAGE_GMS android.permission.READ_MEDIA_AUDIO",
+            "pm revoke $PACKAGE_GMS android.permission.READ_MEDIA_IMAGES",
+            "pm revoke $PACKAGE_GMS android.permission.READ_MEDIA_VIDEO",
+            "pm revoke $PACKAGE_GMS android.permission.ACCESS_MEDIA_LOCATION",
+            "pm revoke $PACKAGE_GMS android.permission.ACCESS_BROADCAST_RESPONSE_STATS",
+            "pm revoke $PACKAGE_GMS android.permission.WRITE_CALL_LOG",
+            "pm revoke $PACKAGE_GMS android.permission.BODY_SENSORS",
+            "pm revoke $PACKAGE_GMS android.permission.DUMP",
+            "appops set $PACKAGE_GMS WIFI_SCAN ignore",
+            "appops set $PACKAGE_GMS BLUETOOTH_SCAN ignore",
+            "appops set $PACKAGE_GMS FINE_LOCATION_SOURCE ignore",
+            "appops set $PACKAGE_GMS ACCESS_RESTRICTED_SETTINGS ignore",
             "sleep 10",
             "cmd role add-role-holder android.app.role.SMS $PACKAGE_MESSAGING",
             PACKAGE_MESSAGING.clear(),

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

@@ -20,6 +20,7 @@ import com.example.modifier.MainActivity
 import com.example.modifier.Utils
 import com.example.modifier.baseTag
 import com.example.modifier.constants.PACKAGE_GMS
+import com.example.modifier.constants.PACKAGE_MESSAGING
 import com.example.modifier.extension.kill
 import com.example.modifier.http.api.SysConfigApi
 import com.example.modifier.http.ktorClient
@@ -180,6 +181,8 @@ suspend fun optimize() {
         "dumpsys deviceidle whitelist +${BuildConfig.APPLICATION_ID}",
         "cmd netpolicy add restrict-background-blacklist ${info.uid}",
         "pm grant ${BuildConfig.APPLICATION_ID} android.permission.POST_NOTIFICATIONS",
+        "cmd locale set-app-locales $PACKAGE_MESSAGING --locales en-US",
+        "appops set com.android.systemui READ_CLIPBOARD deny"
     )
     if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
         shellRun(

+ 3 - 3
app/src/main/res/menu/more.xml

@@ -36,8 +36,8 @@
         android:title="计数清零" />
     <item
         android:id="@+id/create_group"
-        android:title="群" />
+        android:title="群" />
     <item
-        android:id="@+id/leave_group"
-        android:title="退群" />
+        android:id="@+id/group_send"
+        android:title="群" />
 </menu>