| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214 |
- 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
- import android.net.Uri
- import android.os.Build
- import android.os.Handler
- import android.os.Looper
- 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.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.core.content.ContextCompat
- 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.Observer
- 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_KILL_GMS
- import com.example.modifier.CMD_KILL_MESSAGING_APP
- import com.example.modifier.CMD_MESSAGING_APP
- import com.example.modifier.CMD_RCS_SETTINGS_ACTIVITY
- import com.example.modifier.CMD_RESUME_GMS
- import com.example.modifier.CMD_RESUME_MESSAGING_APP
- import com.example.modifier.CMD_SUSPEND_GMS
- import com.example.modifier.CMD_SUSPEND_MESSAGING_APP
- import com.example.modifier.Global
- import com.example.modifier.Global.load
- import com.example.modifier.Global.resetAll
- import com.example.modifier.R
- import com.example.modifier.TraverseResult
- import com.example.modifier.Utils
- import com.example.modifier.data.AppDatabase
- import com.example.modifier.data.BackupItemDao
- import com.example.modifier.databinding.FloatingWindowBinding
- import com.example.modifier.enums.RcsConfigureState
- import com.example.modifier.enums.RcsConnectionStatus
- import com.example.modifier.http.KtorClient
- import com.example.modifier.http.api.RcsNumberApi
- import com.example.modifier.http.api.SysConfigApi
- import com.example.modifier.http.request.RcsNumberRequest
- import com.example.modifier.http.response.RcsNumberResponse
- import com.example.modifier.http.response.SysConfigResponse
- import com.example.modifier.model.SocketCallback
- import com.example.modifier.model.TaskAction
- import com.example.modifier.model.TaskExecutionResult
- import com.example.modifier.model.TelephonyConfig
- import com.example.modifier.ui.shellRun
- 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
- import io.socket.client.Socket
- import io.socket.emitter.Emitter
- import kotlinx.coroutines.CoroutineScope
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.delay
- import kotlinx.coroutines.isActive
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.suspendCancellableCoroutine
- import kotlinx.coroutines.withContext
- import kotlinx.coroutines.withTimeout
- import kotlinx.coroutines.withTimeoutOrNull
- import kotlinx.serialization.encodeToString
- import kotlinx.serialization.json.Json
- import org.apache.commons.collections4.queue.CircularFifoQueue
- import org.apache.commons.lang3.RandomStringUtils
- import org.json.JSONException
- import org.json.JSONObject
- import java.time.LocalDateTime
- import java.time.temporal.ChronoUnit
- import java.util.Optional
- import java.util.Timer
- import java.util.concurrent.atomic.AtomicReference
- import java.util.concurrent.locks.ReentrantLock
- import kotlin.coroutines.resume
- import kotlin.math.max
- import kotlin.math.min
- import kotlin.time.Duration
- 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 = "ModifierService1"
- const val NAME: String = BuildConfig.APPLICATION_ID + ".service.ModifierService"
- @JvmStatic
- var instance: ModifierService? = null
- private set
- }
- private val handler = Handler(Looper.getMainLooper())
- private val mSocketOpts = IO.Options()
- private lateinit var mSocket: Socket
- private lateinit var binding: FloatingWindowBinding
- val lock = ReentrantLock()
- 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 cleanCount = 0
- private var lastSend = 0L
- private var rcsInterval = 0L
- private var requestNumberInterval = 0L
- private val running = MutableLiveData(false)
- private val requesting = MutableLiveData(false)
- private val preparing = MutableLiveData(false)
- private val checkingConnection = MutableLiveData(false)
- private var currentTaskId = 0
- private var busy = MediatorLiveData<Boolean>().apply {
- addSource(running) {
- value = it || requesting.value!!
- }
- addSource(requesting) {
- value = it || running.value!!
- }
- addSource(preparing) {
- value = it || running.value!!
- }
- addSource(checkingConnection) {
- value = it || running.value!!
- }
- value =
- requesting.value!! || running.value!! || preparing.value!! || checkingConnection.value!!
- }
- private val rcsConfigureState = MutableLiveData(RcsConfigureState.CONFIGURED)
- private var sendCount: Int
- get() {
- return getSharedPreferences(
- BuildConfig.APPLICATION_ID,
- MODE_PRIVATE
- ).getInt("sendCount", 0)
- }
- set(value) {
- getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
- .putInt("sendCount", value).apply()
- }
- private var requestNumberCount: Int
- get() {
- return getSharedPreferences(
- BuildConfig.APPLICATION_ID,
- MODE_PRIVATE
- ).getInt("requestNumberCount", 0)
- }
- set(value) {
- getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE).edit()
- .putInt("requestNumberCount", value).apply()
- }
- 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()
- }
- }
- private val backupItemDao: BackupItemDao by lazy {
- AppDatabase.getDatabase(this).itemDao()
- }
- fun connect() {
- 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_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")
- CoroutineScope(Dispatchers.IO).launch {
- preparing.postValue(true)
- if (Global.rebooted()) {
- delay(2.minutes)
- }
- if (Global.hasRoot()) {
- Global.syncTime()
- if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
- Global.killPhoneProcess(force = true)
- }
- }
- preparing.postValue(false)
- }
- connect()
- val timer = Timer()
- timer.schedule(object : java.util.TimerTask() {
- override fun run() {
- reportDeviceStatues()
- }
- }, 0, 3000)
- }
- 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")
- CoroutineScope(Dispatchers.IO).launch {
- send(to, body, 2000)
- }
- }
- } else if ("task" == action) {
- val taskAction = Json.decodeFromString<TaskAction>(json.toString())
- CoroutineScope(Dispatchers.IO).launch {
- runTask(taskAction)
- }
- }
- }
- }
- }
- private suspend fun runTask(taskAction: TaskAction) {
- if (checkingConnection.value!! || running.value!! || preparing.value!! || requesting.value!!) {
- mSocket.emit(
- "callback",
- JSONObject(
- Json.encodeToString(
- SocketCallback<String>(
- id = taskAction.id,
- status = -1,
- error = "another task is running"
- )
- )
- )
- )
- return
- }
- try {
- val rcsWait = taskAction.data.config.rcsWait
- cleanCount = taskAction.data.config.cleanCount
- rcsInterval = taskAction.data.config.rcsInterval
- requestNumberInterval = taskAction.data.config.requestNumberInterval
- currentTaskId = taskAction.data.taskId
- if (taskAction.data.config.checkConnection) {
- checkingConnection.postValue(true)
- if (!checkRcsAvailability()) {
- mSocket.emit(
- "callback",
- JSONObject(
- Json.encodeToString(
- SocketCallback<String>(
- id = taskAction.id,
- status = -1,
- error = "RCS not available"
- )
- )
- )
- )
- requestNumber()
- checkingConnection.postValue(false)
- return
- }
- checkingConnection.postValue(false)
- }
- running.postValue(true)
- val success = ArrayList<Int>()
- val fail = ArrayList<Int>()
- for (i in 0 until taskAction.data.tasks.size) {
- val taskItem = taskAction.data.tasks[i]
- try {
- if (send(taskItem.number, taskItem.message, rcsWait)) {
- success.add(taskItem.id)
- } else {
- fail.add(taskItem.id)
- }
- } catch (e: Exception) {
- Log.e(TAG, "runTaskError: ${e.message}", e)
- fail.add(taskItem.id)
- }
- }
- Utils.runAsRoot(CMD_BACK)
- mSocket.emit(
- "callback",
- JSONObject(
- Json.encodeToString(
- SocketCallback(
- id = taskAction.id,
- status = 0,
- data = TaskExecutionResult(success, fail)
- )
- )
- )
- )
- if (requestNumberInterval in 1..sendCount) {
- requestNumber()
- } else if (cleanCount in 1..counter) {
- delay(3000)
- Global.clearConv();
- Utils.runAsRoot(CMD_MESSAGING_APP)
- delay(3000)
- counter = 0
- } else {
- delay(2000)
- }
- running.postValue(false)
- } catch (e: Exception) {
- Log.e(TAG, "runTaskError: ${e.message}", e)
- mSocket.emit(
- "callback",
- JSONObject(
- Json.encodeToString(
- SocketCallback<String>(
- id = taskAction.id,
- status = -1,
- error = e.message
- )
- )
- )
- )
- running.postValue(false)
- }
- }
- private fun smsIntent(to: String, body: String): Intent {
- val intent = Intent(Intent.ACTION_SENDTO)
- intent.data = Uri.parse("sms:$to")
- intent.putExtra("sms_body", body)
- intent.putExtra("exit_on_sent", true)
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- intent.setPackage("com.google.android.apps.messaging")
- return intent
- }
- private suspend fun send(to: String, body: String, rcsWait: Long): Boolean {
- Log.i(TAG, "Sending SMS to $to: $body")
- startActivity(smsIntent(to, body))
- try {
- Log.i(TAG, "Command executed successfully, waiting for app to open...")
- delay(1000)
- var success = false
- withTimeoutOrNull(rcsWait) {
- while (true) {
- val root = rootInActiveWindow
- 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")
- val dt = System.currentTimeMillis() - lastSend
- if (rcsInterval > 0 && dt < rcsInterval) {
- Log.i(TAG, "Waiting for RCS interval")
- delay(rcsInterval - dt)
- }
- traverseResult.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
- lastSend = System.currentTimeMillis()
- success = true
- sendCount++
- break
- }
- } else {
- Log.i(TAG, "RCS not detected")
- }
- delay(200)
- }
- }
- counter++
- Log.i(
- TAG,
- "sendCount: $sendCount, Counter: $counter, cleanCount: $cleanCount, requestNumberInterval: $requestNumberInterval"
- )
- delay(1000)
- return success
- } catch (e: Exception) {
- e.printStackTrace()
- }
- 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
- }
- 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 (text != null && (text.contains("Turn on RCS chats") || text.contains("开启 RCS 聊天功能"))) {
- fun findSwitch(node: AccessibilityNodeInfo): Boolean {
- if ("com.google.android.apps.messaging:id/switchWidget" == node.viewIdResourceName) {
- result.rcsSwitch = node
- return true
- }
- for (i in 0 until node.childCount) {
- val child = node.getChild(i)
- if (findSwitch(child)) {
- return true
- }
- }
- return false
- }
- findSwitch(node.parent.parent)
- }
- if ("com.google.android.apps.messaging:id/rcs_sim_status_status_text" == id) {
- if (text.lowercase().contains("connected") || text.lowercase().contains("已连接")) {
- result.rcsConnectionStatus = RcsConnectionStatus.CONNECTED
- }
- }
- 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 or
- AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- 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 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.swSend.text = Global.name
- binding.tvVersion.text = "v${BuildConfig.VERSION_CODE}"
- windowManager.addView(mLayout, layoutParams)
- 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)
- val 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)
- }
- 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)
- }
- 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) {
- connect()
- } else {
- if (this::mSocket.isInitialized) {
- mSocket.disconnect()
- }
- }
- }
- binding.swSend.isChecked = canSend
- binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
- canSend = isChecked
- }
- logcat.observeForever {
- binding.tvLog.text = it
- binding.scroll.fullScroll(View.FOCUS_DOWN)
- }
- requesting.observeForever {
- binding.btnReq.isEnabled = !it
- }
- binding.btnReq.setOnClickListener {
- requestNumberCount = 6
- CoroutineScope(Dispatchers.IO).launch {
- requestNumber()
- }
- }
- binding.btnInspect.setOnClickListener {
- traverseNode(rootInActiveWindow, TraverseResult())
- }
- binding.btnReset.setOnClickListener {
- binding.btnReset.isEnabled = false
- CoroutineScope(Dispatchers.IO).launch {
- reset()
- withContext(Dispatchers.Main) {
- binding.btnReset.isEnabled = true
- }
- }
- }
- binding.btnCheck.setOnClickListener {
- CoroutineScope(Dispatchers.IO).launch {
- checkRcsAvailability()
- }
- }
- binding.btnStoreNumbers.setOnClickListener {
- CoroutineScope(Dispatchers.IO).launch {
- storeNumbers()
- }
- }
- binding.btnToggleOn.setOnClickListener {
- binding.btnToggleOn.isEnabled = false
- CoroutineScope(Dispatchers.IO).launch {
- toggleRcsSwitch(true)
- withContext(Dispatchers.Main) {
- binding.btnToggleOn.isEnabled = true
- }
- }
- }
- binding.btnToggleOff.setOnClickListener {
- binding.btnToggleOff.isEnabled = false
- CoroutineScope(Dispatchers.IO).launch {
- toggleRcsSwitch(false)
- withContext(Dispatchers.Main) {
- binding.btnToggleOff.isEnabled = true
- }
- }
- }
- busy.observeForever {
- reportDeviceStatues()
- }
- }
- private fun reportDeviceStatues() {
- if (this::mSocket.isInitialized) {
- val data = JSONObject()
- try {
- data.put("action", "updateDevice")
- val dataObj = JSONObject()
- dataObj.put("canSend", canSend)
- dataObj.put("busy", busy.value)
- data.put("data", dataObj)
- mSocket.emit("message", data)
- } catch (e: JSONException) {
- e.printStackTrace()
- }
- }
- }
- private suspend fun waitForRcsState(
- states: Array<RcsConfigureState>,
- timeout: Duration
- ): RcsConfigureState? {
- var state: RcsConfigureState? = null
- withTimeoutOrNull(timeout) {
- withContext(Dispatchers.Main) {
- suspendCancellableCoroutine { continuation ->
- val observer = Observer<RcsConfigureState> { value ->
- if (states.contains(value)) {
- state = value
- if (isActive)
- continuation.resume(Unit)
- }
- }
- rcsConfigureState.observeForever(observer)
- continuation.invokeOnCancellation {
- handler.post {
- Log.i(TAG, "removeObserver")
- rcsConfigureState.removeObserver(observer)
- }
- }
- }
- }
- false
- }
- return state
- }
- private suspend fun toggleRcsSwitch(on: Boolean, retry: Int = 3): Boolean {
- val res = TraverseResult()
- shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 0.5")
- val success = run repeatBlock@{
- repeat(retry) {
- res.rcsSwitch = null
- traverseNode(rootInActiveWindow, res)
- if (res.rcsSwitch == null) {
- shellRun(CMD_BACK, "sleep 0.5", CMD_RCS_SETTINGS_ACTIVITY, "sleep 0.5")
- } else {
- if (res.rcsSwitch!!.isChecked == on) {
- return@repeatBlock true
- }
- val rect = Rect()
- res.rcsSwitch!!.getBoundsInScreen(rect)
- if (on) {
- shellRun(
- "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
- CMD_BACK, "sleep 0.5",
- CMD_RCS_SETTINGS_ACTIVITY, "sleep 1",
- )
- } else {
- shellRun(
- "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
- )
- rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
- .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
- shellRun(
- "sleep 0.5", CMD_BACK, "sleep 0.5",
- CMD_RCS_SETTINGS_ACTIVITY, "sleep 0.5",
- )
- }
- res.rcsSwitch = null
- traverseNode(rootInActiveWindow, res)
- if (res.rcsSwitch?.isChecked == on) {
- return@repeatBlock true
- }
- }
- }
- false
- }
- shellRun(CMD_BACK, "sleep 0.5")
- return success
- }
- private suspend fun reset() {
- withTimeout(1.hours) {
- while (true) {
- delay(100)
- withContext(Dispatchers.Main) {
- binding.tvLog.text = "Waiting for RCS switch on..."
- }
- rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
- Global.saveMock()
- resetAll()
- var switchAppear = waitForRcsState(
- arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
- 1.minutes
- )?.let {
- it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
- }
- if (switchAppear != true) {
- shellRun(
- CMD_KILL_GMS, CMD_KILL_MESSAGING_APP, "sleep 1",
- CMD_MESSAGING_APP
- )
- switchAppear = waitForRcsState(
- arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
- 2.minutes
- )?.let {
- it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
- }
- if (switchAppear != true) {
- Log.e(TAG, "RCS not entered default on state, retrying...")
- continue
- }
- }
- val switchOn = toggleRcsSwitch(true)
- if (!switchOn) {
- Log.e(TAG, "RCS switch not turned on, retrying...")
- continue
- }
- val resetSuccess = waitForRcsState(
- arrayOf(
- RcsConfigureState.READY
- ), 3.minutes
- )
- Log.i(TAG, "waitForRcsState: $resetSuccess")
- requestNumberCount = 0
- break
- }
- }
- }
- private suspend fun requestNumber() {
- val color = ContextCompat.getColorStateList(binding.root.context, R.color.btn_color)
- binding.btnReq.backgroundTintList = color
- if (getSharedPreferences("settings", Context.MODE_PRIVATE)
- .getBoolean("do_not_request", false)
- ) {
- return
- }
- if (requesting.value!!) {
- return
- }
- requestNumberCount++
- requesting.postValue(true)
- var requestSuccess = false
- withTimeoutOrNull(1.hours) {
- var needRest = false
- while (true) {
- delay(200)
- try {
- rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
- withContext(Dispatchers.Main) {
- binding.tvLog.text = "Requesting number..."
- }
- 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")
- withContext(Dispatchers.Main) {
- binding.tvLog.text = "Requesting success, waiting for logs..."
- }
- Global.save(
- TelephonyConfig(
- rcsNumber.number,
- rcsNumber.mcc,
- rcsNumber.mnc,
- Global.genICCID(rcsNumber.mnc, rcsNumber.areaCode),
- rcsNumber.mcc + rcsNumber.mnc + RandomStringUtils.randomNumeric(
- 15 - rcsNumber.mcc.length - rcsNumber.mnc.length
- ),
- Utils.generateIMEI(),
- rcsNumber.country,
- rcsNumber.areaCode
- )
- )
- if (requestNumberCount > 5 &&
- !getSharedPreferences("settings", Context.MODE_PRIVATE)
- .getBoolean("do_not_reset", false)
- ) {
- val resetSuccess = withTimeoutOrNull(5.minutes) {
- while (true) {
- delay(200)
- withContext(Dispatchers.Main) {
- binding.tvLog.text = "Waiting for RCS switch on..."
- }
- rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
- resetAll()
- val switchAppear = waitForRcsState(
- arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
- 3.minutes
- )?.let {
- it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
- }
- if (switchAppear != true) {
- Log.e(TAG, "RCS not entered default on state, retrying...")
- continue
- }
- val switchOn = toggleRcsSwitch(true)
- if (!switchOn) {
- Log.e(TAG, "RCS switch not turned on, retrying...")
- continue
- }
- val resetSuccess = waitForRcsState(
- arrayOf(
- RcsConfigureState.READY
- ), 3.minutes
- )
- Log.i(TAG, "waitForRcsState: $resetSuccess")
- toggleRcsSwitch(false)
- delay(500)
- toggleRcsSwitch(true)
- requestNumberCount = 0
- break
- }
- true
- } ?: false
- if (resetSuccess) {
- needRest = false
- } else {
- Log.e(TAG, "RCS reset failed, retrying...")
- continue
- }
- }
- Utils.runAsRoot(CMD_MESSAGING_APP)
- withContext(Dispatchers.Main) {
- binding.tvLog.text = "Waiting for logs..."
- }
- if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
- Log.e(TAG, "RCS number expired, retrying...")
- continue
- }
- var sendOtpTimeout = ChronoUnit.SECONDS.between(
- LocalDateTime.now(),
- rcsNumber.expiryTime
- ).seconds
- if (sendOtpTimeout < 60.seconds) {
- Log.e(TAG, "OTP timeout too short, retrying...")
- continue
- }
- if (sendOtpTimeout > 2.minutes) {
- sendOtpTimeout = 2.minutes
- }
- 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 {
- 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}")
- }
- delay(2.seconds)
- }
- }
- 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(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
- }
- }
- } catch (e: Exception) {
- Log.e(TAG, "requestNumberError: ${e.message}", e)
- }
- }
- }
- requesting.postValue(false)
- if (requestSuccess) {
- sendCount = 0
- counter = 0
- Log.i(TAG, "requestNumber success")
- } else {
- Log.e(TAG, "requestNumber failed")
- canSend = false
- withContext(Dispatchers.Main) {
- binding.swSend.isChecked = false
- binding.btnReq.backgroundTintList =
- ContextCompat.getColorStateList(binding.root.context, R.color.btn_color_error)
- }
- }
- }
- private suspend fun checkRcsConnectivity(): Boolean = run checkRcsConnection@{
- repeat(3) {
- Log.i(TAG, "Checking RCS status...")
- shellRun(
- CMD_CONVERSATION_LIST_ACTIVITY,
- CMD_RCS_SETTINGS_ACTIVITY,
- "sleep 1",
- )
- val res = TraverseResult()
- traverseNode(rootInActiveWindow, res)
- if (res.rcsConnectionStatus == RcsConnectionStatus.CONNECTED) {
- Log.i(TAG, "RCS is connected")
- shellRun(CMD_BACK)
- return@checkRcsConnection true
- } else {
- Log.i(TAG, "RCS not connected, retrying...")
- }
- shellRun(CMD_BACK, "sleep ${it * 2}")
- }
- false
- }
- suspend fun checkRcsAvailability(): Boolean {
- checkingConnection.postValue(true)
- val availability = run checkAvailability@{
- val rcsConnected = checkRcsConnectivity()
- if (!rcsConnected) {
- return@checkAvailability false
- }
- var config: SysConfigResponse
- val checkRcsAvailabilityNumbers = mutableListOf<String>()
- withTimeoutOrNull(60.seconds) {
- while (true) {
- try {
- config = KtorClient.get(
- SysConfigApi.Id(
- SysConfigApi(),
- "check_availability_numbers"
- )
- )
- .body<SysConfigResponse>()
- Log.i(TAG, "sysConfig response: $config")
- checkRcsAvailabilityNumbers.addAll(
- config.value.split(",").map { it.trim() })
- break
- } catch (exception: Exception) {
- Log.e(TAG, "sysConfig Error: ${exception.message}", exception)
- }
- delay(1.seconds)
- }
- }
- checkRcsAvailabilityNumbers.forEach {
- startActivity(smsIntent(it, ""))
- val s = withTimeoutOrNull(3.seconds) {
- while (true) {
- val root = rootInActiveWindow
- val traverseResult = TraverseResult()
- traverseNode(root, traverseResult)
- if (traverseResult.isRcsCapable) {
- return@withTimeoutOrNull true
- } else {
- Log.i(TAG, "checkRcsAvailability: RCS not detected")
- }
- delay(200)
- }
- }
- if (s == true) {
- Log.i(TAG, "checkRcsAvailability: $it success")
- delay(1000)
- return@checkAvailability true
- }
- }
- false
- }
- checkingConnection.postValue(false)
- return availability
- }
- private suspend fun storeNumbers() {
- withContext(Dispatchers.Main) {
- binding.btnStoreNumbers.isEnabled = false
- }
- repeat(20) {
- requestNumber()
- delay(3000)
- Global.backup(backupItemDao, "auto", 0)
- }
- withContext(Dispatchers.Main) {
- binding.btnStoreNumbers.isEnabled = true
- }
- }
- }
|