| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- package com.example.modifier
- import android.accessibilityservice.AccessibilityService
- import android.accessibilityservice.AccessibilityServiceInfo
- import android.annotation.SuppressLint
- import android.content.Intent
- import android.graphics.PixelFormat
- import android.net.Uri
- import android.os.Build
- import android.util.DisplayMetrics
- import android.util.Log
- import android.view.Gravity
- import android.view.LayoutInflater
- import android.view.MotionEvent
- import android.view.View
- import android.view.WindowManager
- import android.view.accessibility.AccessibilityEvent
- import android.view.accessibility.AccessibilityNodeInfo
- import android.widget.CompoundButton
- import android.widget.FrameLayout
- import com.example.modifier.Global.load
- import com.example.modifier.databinding.FloatingWindowBinding
- import com.google.android.material.color.DynamicColors
- import io.socket.client.IO
- import io.socket.client.Socket
- import io.socket.emitter.Emitter
- import kotlinx.coroutines.CoroutineScope
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.GlobalScope
- import kotlinx.coroutines.MainScope
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.withContext
- import org.apache.commons.lang3.StringUtils
- import org.json.JSONArray
- import org.json.JSONException
- import org.json.JSONObject
- import java.util.Optional
- import java.util.concurrent.ScheduledExecutorService
- import java.util.concurrent.ScheduledThreadPoolExecutor
- import java.util.concurrent.TimeUnit
- import java.util.concurrent.atomic.AtomicReference
- import kotlin.math.max
- import kotlin.math.min
- 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 binding: FloatingWindowBinding
- private var counter = 0
- private var _busy = false
- 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")
- }
- 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()
- }
- }
- }
- override fun onCreate() {
- super.onCreate()
- Log.i(TAG, "Starting ModifierService")
- connect()
- }
- override fun onAccessibilityEvent(event: AccessibilityEvent) {
- // traverseNode(getRootInActiveWindow(), new TraverseResult());
- }
- override fun onInterrupt() {
- }
- override fun call(vararg args: Any) {
- if (args.isNotEmpty()) {
- Log.i(TAG, "Received message: " + args[0])
- if (args[0] is JSONObject) {
- val json = args[0] as JSONObject
- val action = json.optString("action")
- if ("send" == action) {
- val data = json.optJSONObject("data")
- if (data != null) {
- val to = data.optString("to")
- val body = data.optString("body")
- send(to, body, 2000)
- }
- } else if ("task" == action) {
- val data = json.optJSONObject("data")
- val id = json.optString("id")
- if (data != null && StringUtils.isNoneBlank(id)) {
- runTask(id, data)
- }
- } else if ("changeNumber" == action) {
- }
- }
- }
- }
- private fun runTask(id: String, data: JSONObject) {
- val config = data.optJSONObject("config")
- val rcsWait = config.optLong("rcsWait", 2000)
- val tasks = data.optJSONArray("tasks")
- mExecutor.submit {
- busy = true
- val success = JSONArray()
- val fail = JSONArray()
- for (i in 0 until tasks.length()) {
- val task = tasks.optJSONObject(i)
- val to = task.optString("number")
- val body = task.optString("message")
- val taskId = task.optInt("id")
- try {
- if (send(to, body, rcsWait)) {
- success.put(taskId)
- } else {
- fail.put(taskId)
- }
- } catch (e: Exception) {
- e.printStackTrace()
- fail.put(taskId)
- }
- }
- val res = JSONObject()
- try {
- res.put("id", id)
- res.put("status", 0)
- res.put(
- "data", JSONObject()
- .put("success", success)
- .put("fail", fail)
- )
- } catch (e: JSONException) {
- }
- mSocket.emit("callback", res)
- busy = false
- }
- }
- private fun send(to: String, body: String, rcsWait: Long): Boolean {
- Log.i(TAG, "Sending SMS to $to: $body")
- val intent = Intent(Intent.ACTION_SENDTO)
- intent.setData(Uri.parse("sms:$to"))
- intent.putExtra("sms_body", body)
- intent.putExtra("exit_on_sent", true)
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(intent)
- try {
- Log.i(TAG, "Command executed successfully, waiting for app to open...")
- val f = mExecutor.schedule<Boolean>(
- {
- val ts = System.currentTimeMillis()
- 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) {
- Log.i(TAG, "Send button not found")
- } else {
- Log.i(TAG, "Clicking send button")
- result.sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
- counter++
- if (counter == 20) {
- Thread.sleep(2000)
- Global.clearConv();
- Thread.sleep(2000)
- }
- return@schedule true
- }
- } else {
- Log.i(TAG, "RCS not detected")
- }
- try {
- Thread.sleep(500)
- } catch (e: InterruptedException) {
- e.printStackTrace()
- }
- }
- false
- }, 1000, TimeUnit.MILLISECONDS
- )
- synchronized(f) {
- Log.i(TAG, "Waiting for task to complete...")
- return f.get()
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- return false
- }
- private fun traverseNode(node: AccessibilityNodeInfo?, result: TraverseResult) {
- if (node == null) {
- return
- }
- val className = node.className.toString()
- val name = node.viewIdResourceName
- val text = Optional.ofNullable(node.text).map { obj: CharSequence -> obj.toString() }
- .orElse(null)
- val id = node.viewIdResourceName
- Log.d(TAG, "Node: class=$className, text=$text, name=$name, id=$id")
- if ("Compose:Draft:Send" == name) {
- result.sendBtn = node
- }
- if ("com.google.android.apps.messaging:id/send_message_button_icon" == id) {
- result.sendBtn = node
- }
- if (text != null && (text.contains("RCS 聊天") || text.contains("RCS chat"))) {
- result.isRcsCapable = true
- }
- if (node.childCount != 0) {
- for (i in 0 until node.childCount) {
- traverseNode(node.getChild(i), result)
- }
- }
- }
- @SuppressLint("ClickableViewAccessibility")
- override fun onServiceConnected() {
- super.onServiceConnected()
- instance = this
- val info = AccessibilityServiceInfo()
- info.flags =
- AccessibilityServiceInfo.DEFAULT or AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
- info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN
- info.notificationTimeout = 100
- this.serviceInfo = info
- val displayMetrics = DisplayMetrics()
- val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
- windowManager.defaultDisplay.getMetrics(displayMetrics)
- val height = displayMetrics.heightPixels
- val width = displayMetrics.widthPixels
- val maxX = width - Utils.dp2px(this, 108)
- val maxY = height - Utils.dp2px(this, 108)
- val mLayout = FrameLayout(this)
- val layoutParams = WindowManager.LayoutParams()
- layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
- layoutParams.format = PixelFormat.TRANSLUCENT
- layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
- layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
- layoutParams.x = 0
- layoutParams.y = 800
- layoutParams.gravity = Gravity.START or Gravity.TOP
- val newContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
- val inflater = LayoutInflater.from(newContext)
- binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
- binding.tvDeviceName.text = Global.name
- windowManager.addView(mLayout, layoutParams)
- val downX = AtomicReference(0f)
- val downY = AtomicReference(0f)
- val downParamX = AtomicReference(0)
- val downParamY = AtomicReference(0)
- binding.floatingWindow.setOnTouchListener { v: View?, event: MotionEvent ->
- 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 -> {
- layoutParams.x = min(
- max((downParamX.get() + (event.rawX - downX.get())).toDouble(), 0.0),
- maxX.toDouble()
- )
- .toInt()
- layoutParams.y = min(
- max((downParamY.get() + (event.rawY - downY.get())).toDouble(), 0.0),
- maxY.toDouble()
- )
- .toInt()
- windowManager.updateViewLayout(mLayout, layoutParams)
- return@setOnTouchListener true
- }
- }
- false
- }
- binding.swConnect.isChecked = true
- binding.swConnect.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
- if (isChecked) {
- connect()
- } else {
- if (this::mSocket.isInitialized) {
- mSocket.disconnect()
- }
- }
- }
- 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)
- }
- // PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- // PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "RCS:MyWakelockTag");
- // wakeLock.acquire();
- }
- private fun updateDevice(key: String, value: Any) {
- if (this::mSocket.isInitialized) {
- val data = JSONObject()
- try {
- data.put("action", "updateDevice")
- val dataObj = JSONObject()
- dataObj.put(key, value)
- data.put("data", dataObj)
- mSocket.emit("message", data)
- } catch (e: JSONException) {
- e.printStackTrace()
- }
- }
- }
- companion object {
- private const val TAG = "ModifierService"
- const val NAME: String = BuildConfig.APPLICATION_ID + ".ModifierService"
- @JvmStatic
- var instance: ModifierService? = null
- private set
- }
- }
|