Update: 2026-06-30 04:42:17
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,6 +10,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
|
||||
enum class BotState {
|
||||
@@ -34,9 +35,11 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
private var currentState = BotState.IDLE
|
||||
private var currentTask: JSONObject? = null
|
||||
private var currentAppName: String = ""
|
||||
private var appLaunchTime = 0L
|
||||
private var taxiFHomeClickTime = 0L
|
||||
private var taxiFLocationClickTime = 0L
|
||||
private var taxiFSuggestionClickTime = 0L
|
||||
private var taxiFSearchTypingTime = 0L
|
||||
private var taxiFPickupDone = false
|
||||
private var taxiFDestinationDone = false
|
||||
private val taxiFCollectedPrices = mutableListOf<Pair<Double, String>>()
|
||||
@@ -75,9 +78,20 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
} else {
|
||||
Log.d(TAG, "No tasks available.")
|
||||
}
|
||||
// Poll every 5 seconds
|
||||
delay(5000)
|
||||
} else {
|
||||
// We have an active task. Just to be safe, process screen periodically
|
||||
// in case accessibility events are missed (e.g. app launched but screen static)
|
||||
withContext(Dispatchers.Main) {
|
||||
val rootNode = rootInActiveWindow
|
||||
if (rootNode != null) {
|
||||
val pkg = rootNode.packageName?.toString() ?: currentAppName
|
||||
dispatchAutomation(pkg, rootNode)
|
||||
}
|
||||
}
|
||||
delay(1000) // Poll UI state every 1s when active
|
||||
}
|
||||
// Poll every 5 seconds
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,11 +105,13 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
|
||||
taxiFPickupDone = false
|
||||
taxiFDestinationDone = false
|
||||
taxiFSearchTypingTime = 0L
|
||||
currentState = BotState.LAUNCHING_APP
|
||||
|
||||
// Launch the App
|
||||
val success = AppLauncher.launchApp(this, currentAppName)
|
||||
if (success) {
|
||||
appLaunchTime = System.currentTimeMillis()
|
||||
currentState = if (currentAppName == "taxif" || currentAppName == "com.taxif.passenger") BotState.NAVIGATING_HOME else BotState.SEARCHING_START
|
||||
Log.i(TAG, "State -> ${currentState}")
|
||||
} else {
|
||||
@@ -120,14 +136,18 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
Log.d("HierarchyDump", "--- END SCREEN HIERARCHY DUMP FOR $packageName ---")
|
||||
}
|
||||
|
||||
when (packageName) {
|
||||
"ae.com.yalla.go.dubai.client" -> handleYallaGoAutomation(rootNode)
|
||||
"com.zakinn.app" -> handleZakinnAutomation(rootNode)
|
||||
"com.bis.taxi" -> handleTfadalAutomation(rootNode)
|
||||
"com.careem.acma" -> handleCareemAutomation(rootNode)
|
||||
"com.ubercab" -> handleUberAutomation(rootNode)
|
||||
"com.taxif.passenger" -> handleTaxiFAutomation(rootNode)
|
||||
"me.com.easytaxi" -> handleJeenyAutomation(rootNode)
|
||||
dispatchAutomation(packageName, rootNode)
|
||||
}
|
||||
|
||||
private fun dispatchAutomation(packageName: String, rootNode: android.view.accessibility.AccessibilityNodeInfo) {
|
||||
when {
|
||||
packageName.contains("yalla") -> handleYallaGoAutomation(rootNode)
|
||||
packageName.contains("zakinn") -> handleZakinnAutomation(rootNode)
|
||||
packageName.contains("bis.taxi") -> handleTfadalAutomation(rootNode)
|
||||
packageName.contains("careem") -> handleCareemAutomation(rootNode)
|
||||
packageName.contains("ubercab") -> handleUberAutomation(rootNode)
|
||||
packageName.contains("taxif") -> handleTaxiFAutomation(rootNode)
|
||||
packageName.contains("easytaxi") -> handleJeenyAutomation(rootNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +425,9 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
if (nextResult != null && nextResult.optBoolean("has_task", false)) {
|
||||
val nextTask = nextResult.getJSONObject("task")
|
||||
currentState = BotState.IDLE
|
||||
handleTask(nextTask)
|
||||
withContext(Dispatchers.Main) {
|
||||
handleTask(nextTask)
|
||||
}
|
||||
Log.i(TAG, "Multi-trip: Immediately started next task ${nextTask.optString("task_id")}")
|
||||
} else {
|
||||
Log.i(TAG, "No more tasks, returning to IDLE.")
|
||||
@@ -501,8 +523,9 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
Log.d(TAG, "TaxiF Automation event. State: $currentState")
|
||||
when (currentState) {
|
||||
BotState.NAVIGATING_HOME -> {
|
||||
if (System.currentTimeMillis() - appLaunchTime < 2000) return
|
||||
if (hasJustClickedHome()) return
|
||||
val rihlaNode = findNodeByText(rootNode, "رحلة")
|
||||
val rihlaNode = findNodeByText(rootNode, "رحلة") ?: findNodeByText(rootNode, "طلب")
|
||||
if (rihlaNode != null) {
|
||||
var clickable: android.view.accessibility.AccessibilityNodeInfo? = rihlaNode
|
||||
while (clickable != null && !clickable.isClickable) {
|
||||
@@ -510,12 +533,13 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
}
|
||||
if (clickable != null) {
|
||||
clickable.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
Log.i(TAG, "TaxiF: Clicked 'رحلة' tab, entering trip flow")
|
||||
Log.i(TAG, "TaxiF: Clicked 'رحلة' or 'طلب' tab, entering trip flow")
|
||||
} else {
|
||||
rihlaNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
Log.i(TAG, "TaxiF: Clicked 'رحلة' node directly")
|
||||
Log.i(TAG, "TaxiF: Clicked 'رحلة' or 'طلب' node directly")
|
||||
}
|
||||
taxiFHomeClickTime = System.currentTimeMillis()
|
||||
currentState = BotState.SEARCHING_START
|
||||
return
|
||||
}
|
||||
currentState = BotState.SEARCHING_START
|
||||
@@ -661,17 +685,65 @@ class ScraperAccessibilityService : AccessibilityService() {
|
||||
}
|
||||
editField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
||||
Log.i(TAG, "TaxiF: Typed '$location' in search field")
|
||||
taxiFSearchTypingTime = System.currentTimeMillis()
|
||||
} else {
|
||||
if (taxiFSearchTypingTime == 0L) {
|
||||
taxiFSearchTypingTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
if (hasJustClickedSuggestion()) return false
|
||||
|
||||
val firstWord = location.split(" ").firstOrNull() ?: location
|
||||
if (clickFirstSuggestion(rootNode, firstWord)) {
|
||||
taxiFSuggestionClickTime = System.currentTimeMillis()
|
||||
Log.i(TAG, "TaxiF: Clicked suggestion matching '$firstWord'")
|
||||
taxiFSearchTypingTime = 0L
|
||||
return true
|
||||
} else {
|
||||
if (System.currentTimeMillis() - taxiFSearchTypingTime > 3000) {
|
||||
if (clickFirstGenericSuggestion(rootNode)) {
|
||||
taxiFSuggestionClickTime = System.currentTimeMillis()
|
||||
Log.i(TAG, "TaxiF: Clicked first generic suggestion as fallback")
|
||||
taxiFSearchTypingTime = 0L
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun clickFirstGenericSuggestion(rootNode: android.view.accessibility.AccessibilityNodeInfo?): Boolean {
|
||||
if (rootNode == null) return false
|
||||
val recycler = findRecyclerView(rootNode) ?: return false
|
||||
for (i in 0 until recycler.childCount) {
|
||||
val child = recycler.getChild(i) ?: continue
|
||||
if (child.isClickable) {
|
||||
child.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
return true
|
||||
}
|
||||
}
|
||||
for (i in 0 until recycler.childCount) {
|
||||
val child = recycler.getChild(i) ?: continue
|
||||
val clickableDescendant = findClickableDescendant(child)
|
||||
if (clickableDescendant != null) {
|
||||
clickableDescendant.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun findClickableDescendant(node: android.view.accessibility.AccessibilityNodeInfo?): android.view.accessibility.AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
if (node.isClickable) return node
|
||||
for (i in 0 until node.childCount) {
|
||||
val res = findClickableDescendant(node.getChild(i))
|
||||
if (res != null) return res
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun clickFirstSuggestion(rootNode: android.view.accessibility.AccessibilityNodeInfo?, target: String): Boolean {
|
||||
if (rootNode == null) return false
|
||||
val recycler = findRecyclerView(rootNode) ?: return false
|
||||
|
||||
Reference in New Issue
Block a user