diff --git a/android_bot/.gradle/8.13/executionHistory/executionHistory.bin b/android_bot/.gradle/8.13/executionHistory/executionHistory.bin index a91e8dbe..edb57f8b 100644 Binary files a/android_bot/.gradle/8.13/executionHistory/executionHistory.bin and b/android_bot/.gradle/8.13/executionHistory/executionHistory.bin differ diff --git a/android_bot/.gradle/8.13/executionHistory/executionHistory.lock b/android_bot/.gradle/8.13/executionHistory/executionHistory.lock index 2a675dd8..dc5a5379 100644 Binary files a/android_bot/.gradle/8.13/executionHistory/executionHistory.lock and b/android_bot/.gradle/8.13/executionHistory/executionHistory.lock differ diff --git a/android_bot/.gradle/8.13/fileHashes/fileHashes.bin b/android_bot/.gradle/8.13/fileHashes/fileHashes.bin index bd198982..b42a3d3d 100644 Binary files a/android_bot/.gradle/8.13/fileHashes/fileHashes.bin and b/android_bot/.gradle/8.13/fileHashes/fileHashes.bin differ diff --git a/android_bot/.gradle/8.13/fileHashes/fileHashes.lock b/android_bot/.gradle/8.13/fileHashes/fileHashes.lock index 7c18cc16..7d30270e 100644 Binary files a/android_bot/.gradle/8.13/fileHashes/fileHashes.lock and b/android_bot/.gradle/8.13/fileHashes/fileHashes.lock differ diff --git a/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin b/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin index 3ee7fcd4..c2d40fdd 100644 Binary files a/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin and b/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin differ diff --git a/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 6b52f9cc..9a0b5620 100644 Binary files a/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt b/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt index 3f4ad6c9..40db392a 100644 --- a/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt +++ b/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt @@ -41,6 +41,7 @@ class ScraperAccessibilityService : AccessibilityService() { private var taxiFDestinationDone = false private val taxiFCollectedPrices = mutableListOf>() private var taxiFPriceScrollAttempts = 0 + private var taxiFReadingPriceStartTime = 0L companion object { private val TAXIF_EXCLUDED_TEXTS = setOf( @@ -397,21 +398,19 @@ class ScraperAccessibilityService : AccessibilityService() { Log.e(TAG, "Failed to submit price.") } - // Wait 3 seconds, then fetch next trip directly (no relaunch) + // Wait 3 seconds for price to settle, then fetch next task directly delay(3000) + val nextResult = workerClient.fetchTask() if (nextResult != null && nextResult.optBoolean("has_task", false)) { val nextTask = nextResult.getJSONObject("task") - val nextApp = nextTask.optString("app") - Log.i(TAG, "Multi-trip: Fetched next task for $nextApp, going directly to SEARCHING_START") - currentTask = nextTask - currentAppName = nextApp taxiFPickupDone = false taxiFDestinationDone = false currentState = BotState.SEARCHING_START + Log.i(TAG, "TaxiF: Multi-trip fetched next task ${nextTask.optString("task_id")}, state -> SEARCHING_START") } else { - Log.d(TAG, "Multi-trip: No more tasks, going IDLE.") + Log.i(TAG, "No more tasks, returning to IDLE.") currentState = BotState.IDLE currentTask = null } @@ -551,6 +550,7 @@ class ScraperAccessibilityService : AccessibilityService() { } taxiFDestinationDone = true currentState = BotState.READING_PRICE + taxiFReadingPriceStartTime = System.currentTimeMillis() taxiFCollectedPrices.clear() taxiFPriceScrollAttempts = 0 return @@ -561,7 +561,16 @@ class ScraperAccessibilityService : AccessibilityService() { } } BotState.READING_PRICE -> { - if (!hasJustClickedSuggestion()) { + if (hasJustClickedSuggestion()) return + if (System.currentTimeMillis() - taxiFReadingPriceStartTime < 2000) return + + val ecoPrice = getEcoPrice(rootNode) + if (ecoPrice != null && ecoPrice > 0) { + Log.i(TAG, "TaxiF: Found ECO price: $ecoPrice JOD") + submitPriceToServer("$ecoPrice JOD") + currentState = BotState.IDLE + } else if (System.currentTimeMillis() - taxiFReadingPriceStartTime > 15000) { + Log.w(TAG, "TaxiF: ECO price not found within 15s timeout, falling back to collectAndScrollPrices") collectAndScrollPrices(rootNode) } } @@ -570,44 +579,34 @@ class ScraperAccessibilityService : AccessibilityService() { } private fun hasJustClickedHome(): Boolean { - return (System.currentTimeMillis() - taxiFHomeClickTime) < 1500 + return (System.currentTimeMillis() - taxiFHomeClickTime) < 500 } private fun hasJustClickedLocation(): Boolean { - return (System.currentTimeMillis() - taxiFLocationClickTime) < 1500 + return (System.currentTimeMillis() - taxiFLocationClickTime) < 500 } private fun hasJustClickedSuggestion(): Boolean { - return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 1500 - } - - private fun getLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?): List { - val texts = mutableListOf() - collectLocationTexts(node, texts) - return texts - } - - private fun collectLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?, texts: MutableList) { - if (node == null) return - val text = node.text?.toString()?.trim() ?: "" - val className = node.className?.toString() ?: "" - val isEditText = className == "android.widget.EditText" || node.isEditable - if (text.isNotBlank() && text.length > 2 && !isEditText && TAXIF_EXCLUDED_TEXTS.none { text.contains(it) }) { - texts.add(text) - } - for (i in 0 until node.childCount) { - collectLocationTexts(node.getChild(i), texts) - } + return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 500 } private fun shouldEditLocation(rootNode: android.view.accessibility.AccessibilityNodeInfo, task: JSONObject, isPickup: Boolean): Boolean { - val locationTexts = getLocationTexts(rootNode) + val locationNodes = getLocationTextNodes(rootNode) val taskLoc = if (isPickup) task.optString("start_location", "") else task.optString("end_location", "") if (taskLoc.isEmpty()) return false - val matchFound = locationTexts.any { text -> - text.contains(taskLoc, ignoreCase = true) || taskLoc.contains(text, ignoreCase = true) + + val rowNode = if (isPickup) { + locationNodes.firstOrNull() + } else { + val destIdx = locationNodes.indexOfFirst { it.text?.toString() == "عنوان الإنزال" } + if (destIdx >= 0) locationNodes[destIdx] else locationNodes.getOrNull(1) } - Log.d(TAG, "TaxiF: Location texts=$locationTexts, taskLoc=$taskLoc, matchFound=$matchFound") + + val rowText = rowNode?.text?.toString()?.trim() ?: "" + val matchFound = rowText.contains(taskLoc, ignoreCase = true) || taskLoc.contains(rowText, ignoreCase = true) + + Log.d(TAG, "TaxiF: shouldEditLocation(isPickup=$isPickup): rowText='$rowText', taskLoc='$taskLoc', matchFound=$matchFound, locationNodes=[${locationNodes.joinToString { it.text?.toString() ?: "" }}]") + return !matchFound } @@ -656,7 +655,9 @@ class ScraperAccessibilityService : AccessibilityService() { if (editTexts.isEmpty()) return false val editField = editTexts[0] val currentText = editField.text?.toString() ?: "" - if (currentText != location) { + val normalizedCurrent = currentText.replace("-", " ").replace("\\s+".toRegex(), " ").trim() + val normalizedLocation = location.replace("-", " ").replace("\\s+".toRegex(), " ").trim() + if (!normalizedCurrent.contains(normalizedLocation, ignoreCase = true)) { val arguments = android.os.Bundle().apply { putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, location) } @@ -689,7 +690,9 @@ class ScraperAccessibilityService : AccessibilityService() { private fun suggestionContainsText(node: android.view.accessibility.AccessibilityNodeInfo?, target: String): Boolean { if (node == null) return false val text = node.text?.toString() ?: "" - if (text.contains(target, ignoreCase = true)) return true + val normalizedText = text.replace("-", " ").replace("\\s+".toRegex(), " ").trim() + val normalizedTarget = target.replace("-", " ").replace("\\s+".toRegex(), " ").trim() + if (normalizedText.contains(normalizedTarget, ignoreCase = true)) return true for (i in 0 until node.childCount) { if (suggestionContainsText(node.getChild(i), target)) return true } @@ -780,6 +783,25 @@ class ScraperAccessibilityService : AccessibilityService() { } } + private fun getEcoPrice(rootNode: android.view.accessibility.AccessibilityNodeInfo?): Double? { + if (rootNode == null) return null + val recycler = findHorizontalRecyclerView(rootNode) ?: return null + for (i in 0 until recycler.childCount) { + val card = recycler.getChild(i) ?: continue + if (findNodeByText(card, "ECO") != null) { + val jodNode = findNodeByText(card, "JOD") ?: continue + val text = jodNode.text?.toString() ?: continue + val converted = arabicToWestern(text) + val match = Regex("""(\d+\.?\d*)""").find(converted) + if (match != null) { + val price = match.value.toDoubleOrNull() + if (price != null && price > 0) return price + } + } + } + return null + } + private fun handleJeenyAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { val task = currentTask ?: return Log.d(TAG, "Jeeny Automation event. State: $currentState")