ModifierService.kt 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. package com.example.modifier.service
  2. import android.accessibilityservice.AccessibilityService
  3. import android.annotation.SuppressLint
  4. import android.content.Context
  5. import android.graphics.PixelFormat
  6. import android.os.Build
  7. import android.util.DisplayMetrics
  8. import android.util.Log
  9. import android.view.Gravity
  10. import android.view.LayoutInflater
  11. import android.view.MotionEvent
  12. import android.view.View
  13. import android.view.View.GONE
  14. import android.view.View.OnTouchListener
  15. import android.view.View.VISIBLE
  16. import android.view.WindowManager
  17. import android.view.accessibility.AccessibilityEvent
  18. import android.widget.CompoundButton
  19. import android.widget.FrameLayout
  20. import androidx.annotation.MenuRes
  21. import androidx.appcompat.widget.PopupMenu
  22. import androidx.core.content.ContextCompat
  23. import com.example.modifier.utils.ADB
  24. import com.example.modifier.BuildConfig
  25. import com.example.modifier.R
  26. import com.example.modifier.TraverseResult
  27. import com.example.modifier.baseTag
  28. import com.example.modifier.data.AppDatabase
  29. import com.example.modifier.databinding.FloatingWindowBinding
  30. import com.example.modifier.enums.ReqState
  31. import com.example.modifier.repo.AppPrefsRepo
  32. import com.example.modifier.repo.AppStateRepo
  33. import com.example.modifier.repo.BackupRepository
  34. import com.example.modifier.repo.GmsgStateRepo
  35. import com.example.modifier.repo.SpoofedInfoRepo
  36. import com.example.modifier.utils.ROOT_ACCESS
  37. import com.example.modifier.utils.checkPif
  38. import com.example.modifier.utils.clearConv
  39. import com.example.modifier.utils.findFirstByDesc
  40. import com.example.modifier.utils.hasRootAccess
  41. import com.example.modifier.utils.killPhoneProcess
  42. import com.example.modifier.utils.optimize
  43. import com.example.modifier.utils.restartSelf
  44. import com.example.modifier.utils.setBatteryLevel
  45. import com.example.modifier.utils.syncTime
  46. import com.google.android.material.color.DynamicColors
  47. import kotlinx.coroutines.CoroutineScope
  48. import kotlinx.coroutines.Dispatchers
  49. import kotlinx.coroutines.FlowPreview
  50. import kotlinx.coroutines.delay
  51. import kotlinx.coroutines.flow.debounce
  52. import kotlinx.coroutines.launch
  53. import kotlinx.coroutines.withContext
  54. import java.util.Timer
  55. import java.util.TimerTask
  56. import java.util.concurrent.atomic.AtomicReference
  57. import kotlin.math.max
  58. import kotlin.math.min
  59. import kotlin.time.Duration.Companion.minutes
  60. import kotlin.time.Duration.Companion.seconds
  61. @SuppressLint("SetTextI18n")
  62. class ModifierService : AccessibilityService() {
  63. @SuppressLint("StaticFieldLeak")
  64. companion object {
  65. private const val TAG = "$baseTag/Service"
  66. const val NAME: String = BuildConfig.APPLICATION_ID + ".service.ModifierService"
  67. var instance: ModifierService? = null
  68. }
  69. private lateinit var binding: FloatingWindowBinding
  70. private val backupItemDao by lazy { AppDatabase.getDatabase(this).itemDao() }
  71. private val appPrefsRepo = AppPrefsRepo.instance
  72. private val appStateRepo = AppStateRepo.instance
  73. private val gmsgStateRepo = GmsgStateRepo.instance
  74. private val screenInspector by lazy { ScreenInspector(this) }
  75. private val screenController by lazy { ScreenController(this, screenInspector) }
  76. private val spoofedInfoRepo = SpoofedInfoRepo.instance
  77. private val backupRepository by lazy { BackupRepository(this, backupItemDao) }
  78. private lateinit var socketClient: SocketClient
  79. lateinit var taskRunner: TaskRunner
  80. private lateinit var uiContext: Context
  81. override fun onAccessibilityEvent(event: AccessibilityEvent) {
  82. // Log.w(TAG, "AccessibilityEvent: type=${event.eventType} text=${event.text}")
  83. if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
  84. if (event.text.any { it.contains("允许 USB 调试吗?") || it.contains("Allow USB debugging?") }) {
  85. CoroutineScope(Dispatchers.IO).launch {
  86. screenController.allowDebugging()
  87. }
  88. }
  89. }
  90. }
  91. override fun onInterrupt() {
  92. Log.e(TAG, "onInterrupt")
  93. }
  94. @OptIn(FlowPreview::class)
  95. @SuppressLint("ClickableViewAccessibility")
  96. override fun onServiceConnected() {
  97. super.onServiceConnected()
  98. CoroutineScope(Dispatchers.IO).launch {
  99. taskRunner = TaskRunner(
  100. this@ModifierService,
  101. screenInspector,
  102. screenController,
  103. appStateRepo,
  104. appPrefsRepo,
  105. spoofedInfoRepo,
  106. gmsgStateRepo,
  107. backupRepository,
  108. )
  109. launch {
  110. appStateRepo.appState.debounce(200).collect {
  111. if (this@ModifierService::socketClient.isInitialized) {
  112. socketClient.reportDeviceStatues()
  113. }
  114. }
  115. }
  116. launch {
  117. appStateRepo.appState.collect {
  118. withContext(Dispatchers.Main) {
  119. binding.swSend.isChecked = it.send
  120. binding.btnReq.isEnabled = it.reqState == ReqState.NONE
  121. binding.tvCount.text = "${it.successNum} / ${it.executedNum}"
  122. if (it.suspended) {
  123. binding.btnReq.backgroundTintList = ContextCompat.getColorStateList(
  124. binding.root.context,
  125. R.color.btn_color_error
  126. )
  127. } else {
  128. binding.btnReq.backgroundTintList = ContextCompat.getColorStateList(
  129. binding.root.context,
  130. R.color.btn_color
  131. )
  132. }
  133. when (it.reqState) {
  134. ReqState.NONE -> {
  135. binding.tvStatus.visibility = GONE
  136. binding.tvStatus.text = ""
  137. }
  138. ReqState.CLEAN -> {
  139. binding.tvStatus.visibility = VISIBLE
  140. binding.tvStatus.text = "cleaning messages"
  141. }
  142. ReqState.RESET -> {
  143. binding.tvStatus.visibility = VISIBLE
  144. binding.tvStatus.text = "resetting GMS"
  145. }
  146. ReqState.BACKUP -> {
  147. binding.tvStatus.visibility = VISIBLE
  148. binding.tvStatus.text = "backing up"
  149. }
  150. ReqState.RESTORE -> {
  151. binding.tvStatus.visibility = VISIBLE
  152. binding.tvStatus.text = "restoring"
  153. }
  154. ReqState.REQUEST -> {
  155. binding.tvStatus.visibility = VISIBLE
  156. binding.tvStatus.text = "requesting number"
  157. }
  158. ReqState.OTP_1 -> {
  159. binding.tvStatus.visibility = VISIBLE
  160. binding.tvStatus.text = "waiting for OTP sent"
  161. }
  162. ReqState.OTP_2 -> {
  163. binding.tvStatus.visibility = VISIBLE
  164. binding.tvStatus.text = "waiting for OTP received"
  165. }
  166. ReqState.CONFIG -> {
  167. binding.tvStatus.visibility = VISIBLE
  168. binding.tvStatus.text = "waiting for configuration"
  169. }
  170. }
  171. }
  172. }
  173. }
  174. launch {
  175. appPrefsRepo.appPrefs.collect {
  176. withContext(Dispatchers.Main) {
  177. binding.swSend.text = it.name
  178. }
  179. if (this@ModifierService::socketClient.isInitialized) {
  180. socketClient.disconnect()
  181. }
  182. socketClient = SocketClient(taskRunner)
  183. }
  184. }
  185. launch {
  186. spoofedInfoRepo.spoofedInfo.collect {
  187. withContext(Dispatchers.Main) {
  188. binding.tvCountry.text = it.country
  189. }
  190. }
  191. }
  192. launch {
  193. gmsgStateRepo.logs.debounce(200).collect {
  194. withContext(Dispatchers.Main) {
  195. binding.tvLog.text = it
  196. delay(100)
  197. binding.scroll.fullScroll(View.FOCUS_DOWN)
  198. }
  199. }
  200. }
  201. appStateRepo.updateRuntimeFlags(preparing = true)
  202. if (hasRootAccess()) {
  203. ROOT_ACCESS = true
  204. } else {
  205. if (!ADB.connect()) {
  206. binding.tvLog.text = "ADB connection failed"
  207. return@launch
  208. }
  209. }
  210. launch {
  211. gmsgStateRepo.startLogging()
  212. }
  213. delay(15.seconds)
  214. appPrefsRepo.updateId()
  215. optimize()
  216. syncTime()
  217. if (ROOT_ACCESS) {
  218. checkPif()
  219. if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
  220. killPhoneProcess(force = false)
  221. delay(15.seconds)
  222. }
  223. }
  224. appStateRepo.updateRuntimeFlags(preparing = false)
  225. val timer = Timer()
  226. timer.schedule(object : TimerTask() {
  227. override fun run() {
  228. CoroutineScope(Dispatchers.IO).launch {
  229. try {
  230. syncTime()
  231. if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
  232. setBatteryLevel(100)
  233. }
  234. } catch (e: Exception) {
  235. e.printStackTrace()
  236. }
  237. }
  238. }
  239. }, 0, 5.minutes.inWholeMilliseconds)
  240. }
  241. instance = this
  242. val displayMetrics = DisplayMetrics()
  243. val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
  244. windowManager.defaultDisplay.getMetrics(displayMetrics)
  245. val height = displayMetrics.heightPixels
  246. val width = displayMetrics.widthPixels
  247. val mLayout = FrameLayout(this)
  248. val layoutParams = WindowManager.LayoutParams()
  249. layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
  250. layoutParams.format = PixelFormat.TRANSLUCENT
  251. layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  252. layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  253. layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
  254. layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
  255. layoutParams.x = 0
  256. layoutParams.y = 800
  257. layoutParams.gravity = Gravity.START or Gravity.TOP
  258. uiContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
  259. val inflater = LayoutInflater.from(uiContext)
  260. binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
  261. binding.tvVersion.text = "v${BuildConfig.VERSION_CODE}"
  262. windowManager.addView(mLayout, layoutParams)
  263. var maxX = 0
  264. var maxY = 0
  265. binding.root.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY)
  266. binding.root.post {
  267. maxX = width - binding.root.measuredWidth
  268. maxY = height - binding.root.measuredHeight
  269. layoutParams.x = maxX
  270. windowManager.updateViewLayout(mLayout, layoutParams)
  271. }
  272. val downX = AtomicReference(0f)
  273. val downY = AtomicReference(0f)
  274. val downParamX = AtomicReference(0)
  275. val downParamY = AtomicReference(0)
  276. val touchListener = OnTouchListener { v, event ->
  277. when (event.action) {
  278. MotionEvent.ACTION_DOWN -> {
  279. downX.set(event.rawX)
  280. downY.set(event.rawY)
  281. downParamX.set(layoutParams.x)
  282. downParamY.set(layoutParams.y)
  283. }
  284. MotionEvent.ACTION_MOVE -> {
  285. layoutParams.x = min(
  286. max((downParamX.get() + (event.rawX - downX.get())).toDouble(), 0.0),
  287. maxX.toDouble()
  288. )
  289. .toInt()
  290. layoutParams.y = min(
  291. max((downParamY.get() + (event.rawY - downY.get())).toDouble(), 0.0),
  292. maxY.toDouble()
  293. )
  294. .toInt()
  295. windowManager.updateViewLayout(mLayout, layoutParams)
  296. }
  297. MotionEvent.ACTION_UP -> {
  298. return@OnTouchListener event.eventTime - event.downTime >= 200
  299. }
  300. }
  301. false
  302. }
  303. binding.swSend.setOnTouchListener(touchListener)
  304. binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
  305. CoroutineScope(Dispatchers.IO).launch {
  306. appStateRepo.updateSend(isChecked)
  307. }
  308. }
  309. binding.btnReq.setOnClickListener {
  310. CoroutineScope(Dispatchers.IO).launch {
  311. taskRunner.requestNumberOnTask(reset = true, noBackup = true)
  312. }
  313. }
  314. binding.btnReset.setOnClickListener {
  315. if (appStateRepo.appState.value.reqState != ReqState.NONE) {
  316. return@setOnClickListener
  317. }
  318. binding.btnReset.isEnabled = false
  319. CoroutineScope(Dispatchers.IO).launch {
  320. taskRunner.reset()
  321. withContext(Dispatchers.Main) {
  322. binding.btnReset.isEnabled = true
  323. }
  324. }
  325. }
  326. binding.btnMore.setOnClickListener {
  327. showMenu(uiContext, binding.btnMore, R.menu.more)
  328. }
  329. }
  330. private fun showMenu(context: Context, v: View, @MenuRes menuRes: Int) {
  331. val popup = PopupMenu(context, v)
  332. popup.menuInflater.inflate(menuRes, popup.menu)
  333. popup.menu.findItem(R.id.store_numbers).isVisible =
  334. !appStateRepo.appState.value.storing
  335. popup.menu.findItem(R.id.cancel_store_numbers).isVisible =
  336. appStateRepo.appState.value.storing
  337. popup.menu.findItem(R.id.check_availability).isEnabled =
  338. !appStateRepo.appState.value.checkingConnection
  339. if (BuildConfig.DEBUG) {
  340. popup.menu.findItem(R.id.inspect).isVisible = true
  341. popup.menu.findItem(R.id.toggle_on).isVisible = true
  342. popup.menu.findItem(R.id.toggle_off).isVisible = true
  343. }
  344. popup.setOnMenuItemClickListener { item ->
  345. CoroutineScope(Dispatchers.IO).launch {
  346. when (item.itemId) {
  347. R.id.inspect -> {
  348. delay(1500)
  349. screenInspector.traverseNode(TraverseResult(), true)
  350. rootInActiveWindow.findFirstByDesc("wireless debugging")
  351. }
  352. R.id.check_availability -> {
  353. appStateRepo.updateRuntimeFlags(checkingConnection = true)
  354. taskRunner.checkRcsA10y()
  355. appStateRepo.updateRuntimeFlags(checkingConnection = false)
  356. }
  357. R.id.toggle_on -> {
  358. screenController.toggleRcsSwitch(true)
  359. }
  360. R.id.toggle_off -> {
  361. screenController.toggleRcsSwitch(false)
  362. }
  363. R.id.clear_conv -> {
  364. clearConv()
  365. appStateRepo.resetExecutedNum()
  366. }
  367. R.id.store_numbers -> {
  368. taskRunner.storeNumbers()
  369. }
  370. R.id.cancel_store_numbers -> {
  371. taskRunner.cancelStoreNumber()
  372. }
  373. R.id.change_profile -> {
  374. }
  375. R.id.restart_modifier -> {
  376. restartSelf()
  377. }
  378. R.id.reset_counter -> {
  379. CoroutineScope(Dispatchers.IO).launch {
  380. appStateRepo.resetExecutedNum()
  381. appStateRepo.resetSuccessNum()
  382. appStateRepo.resetRequestedNum()
  383. }
  384. }
  385. }
  386. }
  387. true
  388. }
  389. popup.setOnDismissListener {
  390. // Respond to popup being dismissed.
  391. }
  392. // Show the popup menu.
  393. popup.show()
  394. }
  395. }