| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- package com.example.modifier.service
- import android.accessibilityservice.AccessibilityService
- import android.annotation.SuppressLint
- import android.content.Context
- import android.graphics.PixelFormat
- 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.View.GONE
- import android.view.View.OnTouchListener
- import android.view.View.VISIBLE
- import android.view.WindowManager
- import android.view.accessibility.AccessibilityEvent
- import android.widget.CompoundButton
- import android.widget.FrameLayout
- import androidx.annotation.MenuRes
- import androidx.appcompat.widget.PopupMenu
- import androidx.core.content.ContextCompat
- import com.example.modifier.utils.ADB
- import com.example.modifier.BuildConfig
- import com.example.modifier.R
- import com.example.modifier.TraverseResult
- 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.repo.AppPrefsRepo
- import com.example.modifier.repo.AppStateRepo
- import com.example.modifier.repo.BackupRepository
- import com.example.modifier.repo.GmsgStateRepo
- import com.example.modifier.repo.SpoofedInfoRepo
- import com.example.modifier.utils.ROOT_ACCESS
- import com.example.modifier.utils.checkPif
- import com.example.modifier.utils.clearConv
- import com.example.modifier.utils.findFirstByDesc
- import com.example.modifier.utils.hasRootAccess
- import com.example.modifier.utils.killPhoneProcess
- import com.example.modifier.utils.optimize
- import com.example.modifier.utils.restartSelf
- import com.example.modifier.utils.setBatteryLevel
- import com.example.modifier.utils.syncTime
- import com.google.android.material.color.DynamicColors
- import kotlinx.coroutines.CoroutineScope
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.FlowPreview
- import kotlinx.coroutines.delay
- import kotlinx.coroutines.flow.debounce
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.withContext
- import java.util.Timer
- import java.util.TimerTask
- import java.util.concurrent.atomic.AtomicReference
- import kotlin.math.max
- import kotlin.math.min
- import kotlin.time.Duration.Companion.minutes
- import kotlin.time.Duration.Companion.seconds
- @SuppressLint("SetTextI18n")
- class ModifierService : AccessibilityService() {
- @SuppressLint("StaticFieldLeak")
- companion object {
- private const val TAG = "$baseTag/Service"
- const val NAME: String = BuildConfig.APPLICATION_ID + ".service.ModifierService"
- var instance: ModifierService? = null
- }
- private lateinit var binding: FloatingWindowBinding
- private val backupItemDao by lazy { AppDatabase.getDatabase(this).itemDao() }
- private val appPrefsRepo = AppPrefsRepo.instance
- private val appStateRepo = AppStateRepo.instance
- private val gmsgStateRepo = GmsgStateRepo.instance
- private val screenInspector by lazy { ScreenInspector(this) }
- private val screenController by lazy { ScreenController(this, screenInspector) }
- private val spoofedInfoRepo = SpoofedInfoRepo.instance
- private val backupRepository by lazy { BackupRepository(this, backupItemDao) }
- private lateinit var socketClient: SocketClient
- lateinit var taskRunner: TaskRunner
- private lateinit var uiContext: Context
- override fun onAccessibilityEvent(event: AccessibilityEvent) {
- // Log.w(TAG, "AccessibilityEvent: type=${event.eventType} text=${event.text}")
- if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- if (event.text.any { it.contains("允许 USB 调试吗?") || it.contains("Allow USB debugging?") }) {
- CoroutineScope(Dispatchers.IO).launch {
- screenController.allowDebugging()
- }
- }
- }
- }
- override fun onInterrupt() {
- Log.e(TAG, "onInterrupt")
- }
- @OptIn(FlowPreview::class)
- @SuppressLint("ClickableViewAccessibility")
- override fun onServiceConnected() {
- super.onServiceConnected()
- CoroutineScope(Dispatchers.IO).launch {
- taskRunner = TaskRunner(
- this@ModifierService,
- screenInspector,
- screenController,
- appStateRepo,
- appPrefsRepo,
- spoofedInfoRepo,
- gmsgStateRepo,
- backupRepository,
- )
- launch {
- appStateRepo.appState.debounce(200).collect {
- if (this@ModifierService::socketClient.isInitialized) {
- socketClient.reportDeviceStatues()
- }
- }
- }
- launch {
- appStateRepo.appState.collect {
- withContext(Dispatchers.Main) {
- binding.swSend.isChecked = it.send
- binding.btnReq.isEnabled = it.reqState == ReqState.NONE
- binding.tvCount.text = "${it.successNum} / ${it.executedNum}"
- if (it.suspended) {
- binding.btnReq.backgroundTintList = ContextCompat.getColorStateList(
- binding.root.context,
- R.color.btn_color_error
- )
- } else {
- binding.btnReq.backgroundTintList = ContextCompat.getColorStateList(
- binding.root.context,
- R.color.btn_color
- )
- }
- when (it.reqState) {
- ReqState.NONE -> {
- binding.tvStatus.visibility = GONE
- binding.tvStatus.text = ""
- }
- ReqState.CLEAN -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "cleaning messages"
- }
- ReqState.RESET -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "resetting GMS"
- }
- ReqState.BACKUP -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "backing up"
- }
- ReqState.RESTORE -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "restoring"
- }
- ReqState.REQUEST -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "requesting number"
- }
- ReqState.OTP_1 -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "waiting for OTP sent"
- }
- ReqState.OTP_2 -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "waiting for OTP received"
- }
- ReqState.CONFIG -> {
- binding.tvStatus.visibility = VISIBLE
- binding.tvStatus.text = "waiting for configuration"
- }
- }
- }
- }
- }
- launch {
- appPrefsRepo.appPrefs.collect {
- withContext(Dispatchers.Main) {
- binding.swSend.text = it.name
- }
- if (this@ModifierService::socketClient.isInitialized) {
- socketClient.disconnect()
- }
- socketClient = SocketClient(taskRunner)
- }
- }
- launch {
- spoofedInfoRepo.spoofedInfo.collect {
- withContext(Dispatchers.Main) {
- binding.tvCountry.text = it.country
- }
- }
- }
- launch {
- gmsgStateRepo.logs.debounce(200).collect {
- withContext(Dispatchers.Main) {
- binding.tvLog.text = it
- delay(100)
- binding.scroll.fullScroll(View.FOCUS_DOWN)
- }
- }
- }
- appStateRepo.updateRuntimeFlags(preparing = true)
- if (hasRootAccess()) {
- ROOT_ACCESS = true
- } else {
- if (!ADB.connect()) {
- binding.tvLog.text = "ADB connection failed"
- return@launch
- }
- }
- launch {
- gmsgStateRepo.startLogging()
- }
- delay(15.seconds)
- appPrefsRepo.updateId()
- optimize()
- syncTime()
- if (ROOT_ACCESS) {
- checkPif()
- if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
- killPhoneProcess(force = false)
- delay(15.seconds)
- }
- }
- appStateRepo.updateRuntimeFlags(preparing = false)
- val timer = Timer()
- timer.schedule(object : TimerTask() {
- override fun run() {
- CoroutineScope(Dispatchers.IO).launch {
- try {
- syncTime()
- if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
- setBatteryLevel(100)
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- }
- }, 0, 5.minutes.inWholeMilliseconds)
- }
- instance = this
- 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
- uiContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
- val inflater = LayoutInflater.from(uiContext)
- binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
- 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
- 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.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
- CoroutineScope(Dispatchers.IO).launch {
- appStateRepo.updateSend(isChecked)
- }
- }
- binding.btnReq.setOnClickListener {
- CoroutineScope(Dispatchers.IO).launch {
- taskRunner.requestNumberOnTask(reset = false, noBackup = true)
- }
- }
- binding.btnReset.setOnClickListener {
- if (appStateRepo.appState.value.reqState != ReqState.NONE) {
- return@setOnClickListener
- }
- binding.btnReset.isEnabled = false
- CoroutineScope(Dispatchers.IO).launch {
- taskRunner.reset()
- withContext(Dispatchers.Main) {
- binding.btnReset.isEnabled = true
- }
- }
- }
- binding.btnMore.setOnClickListener {
- showMenu(uiContext, binding.btnMore, R.menu.more)
- }
- }
- private fun showMenu(context: Context, v: View, @MenuRes menuRes: Int) {
- val popup = PopupMenu(context, v)
- popup.menuInflater.inflate(menuRes, popup.menu)
- popup.menu.findItem(R.id.store_numbers).isVisible =
- !appStateRepo.appState.value.storing
- popup.menu.findItem(R.id.cancel_store_numbers).isVisible =
- appStateRepo.appState.value.storing
- popup.menu.findItem(R.id.check_availability).isEnabled =
- !appStateRepo.appState.value.checkingConnection
- if (BuildConfig.DEBUG) {
- popup.menu.findItem(R.id.inspect).isVisible = true
- popup.menu.findItem(R.id.toggle_on).isVisible = true
- popup.menu.findItem(R.id.toggle_off).isVisible = true
- }
- popup.setOnMenuItemClickListener { item ->
- CoroutineScope(Dispatchers.IO).launch {
- when (item.itemId) {
- R.id.inspect -> {
- delay(1500)
- screenInspector.traverseNode(TraverseResult(), true)
- rootInActiveWindow.findFirstByDesc("wireless debugging")
- }
- R.id.check_availability -> {
- appStateRepo.updateRuntimeFlags(checkingConnection = true)
- taskRunner.checkRcsA10y()
- appStateRepo.updateRuntimeFlags(checkingConnection = false)
- }
- R.id.toggle_on -> {
- screenController.toggleRcsSwitch(true)
- }
- R.id.toggle_off -> {
- screenController.toggleRcsSwitch(false)
- }
- R.id.clear_conv -> {
- clearConv()
- appStateRepo.resetExecutedNum()
- }
- R.id.store_numbers -> {
- taskRunner.storeNumbers()
- }
- R.id.cancel_store_numbers -> {
- taskRunner.cancelStoreNumber()
- }
- R.id.change_profile -> {
- }
- R.id.restart_modifier -> {
- restartSelf()
- }
- R.id.reset_counter -> {
- CoroutineScope(Dispatchers.IO).launch {
- appStateRepo.resetExecutedNum()
- appStateRepo.resetSuccessNum()
- appStateRepo.resetRequestedNum()
- }
- }
- }
- }
- true
- }
- popup.setOnDismissListener {
- // Respond to popup being dismissed.
- }
- // Show the popup menu.
- popup.show()
- }
- }
|