ModifierService.kt 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. package com.example.modifier
  2. import android.accessibilityservice.AccessibilityService
  3. import android.accessibilityservice.AccessibilityServiceInfo
  4. import android.annotation.SuppressLint
  5. import android.content.Intent
  6. import android.graphics.PixelFormat
  7. import android.net.Uri
  8. import android.os.Build
  9. import android.util.DisplayMetrics
  10. import android.util.Log
  11. import android.view.Gravity
  12. import android.view.LayoutInflater
  13. import android.view.MotionEvent
  14. import android.view.View
  15. import android.view.WindowManager
  16. import android.view.accessibility.AccessibilityEvent
  17. import android.view.accessibility.AccessibilityNodeInfo
  18. import android.widget.CompoundButton
  19. import android.widget.FrameLayout
  20. import com.example.modifier.Global.load
  21. import com.example.modifier.databinding.FloatingWindowBinding
  22. import com.google.android.material.color.DynamicColors
  23. import io.socket.client.IO
  24. import io.socket.client.Socket
  25. import io.socket.emitter.Emitter
  26. import kotlinx.coroutines.CoroutineScope
  27. import kotlinx.coroutines.Dispatchers
  28. import kotlinx.coroutines.GlobalScope
  29. import kotlinx.coroutines.MainScope
  30. import kotlinx.coroutines.launch
  31. import kotlinx.coroutines.withContext
  32. import org.apache.commons.lang3.StringUtils
  33. import org.json.JSONArray
  34. import org.json.JSONException
  35. import org.json.JSONObject
  36. import java.util.Optional
  37. import java.util.concurrent.ScheduledExecutorService
  38. import java.util.concurrent.ScheduledThreadPoolExecutor
  39. import java.util.concurrent.TimeUnit
  40. import java.util.concurrent.atomic.AtomicReference
  41. import kotlin.math.max
  42. import kotlin.math.min
  43. class ModifierService : AccessibilityService(), Emitter.Listener {
  44. private val mExecutor: ScheduledExecutorService = ScheduledThreadPoolExecutor(8)
  45. private lateinit var mSocket: Socket
  46. private val mSocketOpts = IO.Options()
  47. private var canSend = false
  48. private lateinit var binding: FloatingWindowBinding
  49. private var counter = 0
  50. private var _busy = false
  51. private var busy: Boolean
  52. get() {
  53. return _busy
  54. }
  55. set(value) {
  56. _busy = value
  57. updateDevice("busy", value)
  58. }
  59. fun connect() {
  60. CoroutineScope(Dispatchers.IO).launch {
  61. try {
  62. load()
  63. if (this@ModifierService::binding.isInitialized) {
  64. withContext(Dispatchers.Main) {
  65. binding.tvDeviceName.text = Global.name
  66. }
  67. }
  68. if (this@ModifierService::mSocket.isInitialized) {
  69. mSocket.disconnect()
  70. }
  71. canSend = getSharedPreferences(
  72. BuildConfig.APPLICATION_ID,
  73. MODE_PRIVATE
  74. ).getBoolean("canSend", false)
  75. mSocketOpts.query =
  76. "model=" + Build.MANUFACTURER + " " + Build.MODEL + "&name=" + Global.name + "&id=" + Utils.getUniqueID() + "&canSend=" + canSend
  77. mSocket = IO.socket(Global.serverUrl, mSocketOpts)
  78. mSocket.on("message", this@ModifierService)
  79. mSocket.on(Socket.EVENT_CONNECT) {
  80. Log.i(TAG, "Connected to server")
  81. }
  82. mSocket.on(Socket.EVENT_DISCONNECT) {
  83. Log.i(TAG, "Disconnected from server")
  84. }
  85. mSocket.on(Socket.EVENT_CONNECT_ERROR) { args ->
  86. Log.i(TAG, "Connection error: " + args[0])
  87. if (args[0] is Exception) {
  88. val e = args[0] as Exception
  89. e.printStackTrace()
  90. }
  91. }
  92. mSocket.connect()
  93. } catch (e: Exception) {
  94. e.printStackTrace()
  95. }
  96. }
  97. }
  98. override fun onCreate() {
  99. super.onCreate()
  100. Log.i(TAG, "Starting ModifierService")
  101. connect()
  102. }
  103. override fun onAccessibilityEvent(event: AccessibilityEvent) {
  104. // traverseNode(getRootInActiveWindow(), new TraverseResult());
  105. }
  106. override fun onInterrupt() {
  107. }
  108. override fun call(vararg args: Any) {
  109. if (args.isNotEmpty()) {
  110. Log.i(TAG, "Received message: " + args[0])
  111. if (args[0] is JSONObject) {
  112. val json = args[0] as JSONObject
  113. val action = json.optString("action")
  114. if ("send" == action) {
  115. val data = json.optJSONObject("data")
  116. if (data != null) {
  117. val to = data.optString("to")
  118. val body = data.optString("body")
  119. send(to, body, 2000)
  120. }
  121. } else if ("task" == action) {
  122. val data = json.optJSONObject("data")
  123. val id = json.optString("id")
  124. if (data != null && StringUtils.isNoneBlank(id)) {
  125. runTask(id, data)
  126. }
  127. } else if ("changeNumber" == action) {
  128. }
  129. }
  130. }
  131. }
  132. private fun runTask(id: String, data: JSONObject) {
  133. val config = data.optJSONObject("config")
  134. val rcsWait = config.optLong("rcsWait", 2000)
  135. val tasks = data.optJSONArray("tasks")
  136. mExecutor.submit {
  137. busy = true
  138. val success = JSONArray()
  139. val fail = JSONArray()
  140. for (i in 0 until tasks.length()) {
  141. val task = tasks.optJSONObject(i)
  142. val to = task.optString("number")
  143. val body = task.optString("message")
  144. val taskId = task.optInt("id")
  145. try {
  146. if (send(to, body, rcsWait)) {
  147. success.put(taskId)
  148. } else {
  149. fail.put(taskId)
  150. }
  151. } catch (e: Exception) {
  152. e.printStackTrace()
  153. fail.put(taskId)
  154. }
  155. }
  156. val res = JSONObject()
  157. try {
  158. res.put("id", id)
  159. res.put("status", 0)
  160. res.put(
  161. "data", JSONObject()
  162. .put("success", success)
  163. .put("fail", fail)
  164. )
  165. } catch (e: JSONException) {
  166. }
  167. mSocket.emit("callback", res)
  168. busy = false
  169. }
  170. }
  171. private fun send(to: String, body: String, rcsWait: Long): Boolean {
  172. Log.i(TAG, "Sending SMS to $to: $body")
  173. val intent = Intent(Intent.ACTION_SENDTO)
  174. intent.setData(Uri.parse("sms:$to"))
  175. intent.putExtra("sms_body", body)
  176. intent.putExtra("exit_on_sent", true)
  177. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
  178. startActivity(intent)
  179. try {
  180. Log.i(TAG, "Command executed successfully, waiting for app to open...")
  181. val f = mExecutor.schedule<Boolean>(
  182. {
  183. val ts = System.currentTimeMillis()
  184. while (System.currentTimeMillis() - ts < rcsWait) {
  185. val root = rootInActiveWindow
  186. val packageName = root.packageName.toString()
  187. val result = TraverseResult()
  188. traverseNode(root, result)
  189. if (result.isRcsCapable) {
  190. if (result.sendBtn == null) {
  191. Log.i(TAG, "Send button not found")
  192. } else {
  193. Log.i(TAG, "Clicking send button")
  194. result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
  195. counter++
  196. if (counter == 20) {
  197. Thread.sleep(2000)
  198. Global.clearConv();
  199. Thread.sleep(2000)
  200. }
  201. return@schedule true
  202. }
  203. } else {
  204. Log.i(TAG, "RCS not detected")
  205. }
  206. try {
  207. Thread.sleep(500)
  208. } catch (e: InterruptedException) {
  209. e.printStackTrace()
  210. }
  211. }
  212. false
  213. }, 1000, TimeUnit.MILLISECONDS
  214. )
  215. synchronized(f) {
  216. Log.i(TAG, "Waiting for task to complete...")
  217. return f.get()
  218. }
  219. } catch (e: Exception) {
  220. e.printStackTrace()
  221. }
  222. return false
  223. }
  224. private fun traverseNode(node: AccessibilityNodeInfo?, result: TraverseResult) {
  225. if (node == null) {
  226. return
  227. }
  228. val className = node.className.toString()
  229. val name = node.viewIdResourceName
  230. val text = Optional.ofNullable(node.text).map { obj: CharSequence -> obj.toString() }
  231. .orElse(null)
  232. val id = node.viewIdResourceName
  233. Log.d(TAG, "Node: class=$className, text=$text, name=$name, id=$id")
  234. if ("Compose:Draft:Send" == name) {
  235. result.sendBtn = node
  236. }
  237. if ("com.google.android.apps.messaging:id/send_message_button_icon" == id) {
  238. result.sendBtn = node
  239. }
  240. if (text != null && (text.contains("RCS 聊天") || text.contains("RCS chat"))) {
  241. result.isRcsCapable = true
  242. }
  243. if (node.childCount != 0) {
  244. for (i in 0 until node.childCount) {
  245. traverseNode(node.getChild(i), result)
  246. }
  247. }
  248. }
  249. @SuppressLint("ClickableViewAccessibility")
  250. override fun onServiceConnected() {
  251. super.onServiceConnected()
  252. instance = this
  253. val info = AccessibilityServiceInfo()
  254. info.flags =
  255. AccessibilityServiceInfo.DEFAULT or AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
  256. info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
  257. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN
  258. info.notificationTimeout = 100
  259. this.serviceInfo = info
  260. val displayMetrics = DisplayMetrics()
  261. val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
  262. windowManager.defaultDisplay.getMetrics(displayMetrics)
  263. val height = displayMetrics.heightPixels
  264. val width = displayMetrics.widthPixels
  265. val maxX = width - Utils.dp2px(this, 108)
  266. val maxY = height - Utils.dp2px(this, 108)
  267. val mLayout = FrameLayout(this)
  268. val layoutParams = WindowManager.LayoutParams()
  269. layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
  270. layoutParams.format = PixelFormat.TRANSLUCENT
  271. layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  272. layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  273. layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
  274. layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
  275. layoutParams.x = 0
  276. layoutParams.y = 800
  277. layoutParams.gravity = Gravity.START or Gravity.TOP
  278. val newContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
  279. val inflater = LayoutInflater.from(newContext)
  280. binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
  281. binding.tvDeviceName.text = Global.name
  282. windowManager.addView(mLayout, layoutParams)
  283. val downX = AtomicReference(0f)
  284. val downY = AtomicReference(0f)
  285. val downParamX = AtomicReference(0)
  286. val downParamY = AtomicReference(0)
  287. binding.floatingWindow.setOnTouchListener { v: View?, event: MotionEvent ->
  288. when (event.action) {
  289. MotionEvent.ACTION_DOWN -> {
  290. downX.set(event.rawX)
  291. downY.set(event.rawY)
  292. downParamX.set(layoutParams.x)
  293. downParamY.set(layoutParams.y)
  294. return@setOnTouchListener true
  295. }
  296. MotionEvent.ACTION_MOVE -> {
  297. layoutParams.x = min(
  298. max((downParamX.get() + (event.rawX - downX.get())).toDouble(), 0.0),
  299. maxX.toDouble()
  300. )
  301. .toInt()
  302. layoutParams.y = min(
  303. max((downParamY.get() + (event.rawY - downY.get())).toDouble(), 0.0),
  304. maxY.toDouble()
  305. )
  306. .toInt()
  307. windowManager.updateViewLayout(mLayout, layoutParams)
  308. return@setOnTouchListener true
  309. }
  310. }
  311. false
  312. }
  313. binding.swConnect.isChecked = true
  314. binding.swConnect.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
  315. if (isChecked) {
  316. connect()
  317. } else {
  318. if (this::mSocket.isInitialized) {
  319. mSocket.disconnect()
  320. }
  321. }
  322. }
  323. canSend = getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).getBoolean(
  324. "canSend",
  325. false
  326. )
  327. binding.swSend.isChecked = canSend
  328. binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
  329. getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
  330. .putBoolean("canSend", isChecked).apply()
  331. canSend = isChecked
  332. updateDevice("canSend", canSend)
  333. }
  334. // PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
  335. // PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "RCS:MyWakelockTag");
  336. // wakeLock.acquire();
  337. }
  338. private fun updateDevice(key: String, value: Any) {
  339. if (this::mSocket.isInitialized) {
  340. val data = JSONObject()
  341. try {
  342. data.put("action", "updateDevice")
  343. val dataObj = JSONObject()
  344. dataObj.put(key, value)
  345. data.put("data", dataObj)
  346. mSocket.emit("message", data)
  347. } catch (e: JSONException) {
  348. e.printStackTrace()
  349. }
  350. }
  351. }
  352. companion object {
  353. private const val TAG = "ModifierService"
  354. const val NAME: String = BuildConfig.APPLICATION_ID + ".ModifierService"
  355. @JvmStatic
  356. var instance: ModifierService? = null
  357. private set
  358. }
  359. }