This commit is contained in:
Hamza-Ayed
2026-05-14 18:24:32 +03:00
commit 8272065938
646 changed files with 50360 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,118 @@
package com.jordanbot.autoride
import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.provider.Settings
import android.view.View
import android.view.accessibility.AccessibilityManager
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat
import com.jordanbot.autoride.service.BotForegroundService
import com.jordanbot.autoride.service.OverlayService
class MainActivity : AppCompatActivity() {
private lateinit var tvStatus: TextView
private lateinit var btnStart: Button
private lateinit var btnStop: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvStatus = findViewById(R.id.tv_status)
btnStart = findViewById(R.id.btn_start)
btnStop = findViewById(R.id.btn_stop)
findViewById<Button>(R.id.btn_notification).setOnClickListener {
startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
}
findViewById<Button>(R.id.btn_accessibility).setOnClickListener {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
findViewById<Button>(R.id.btn_overlay).setOnClickListener {
if (!Settings.canDrawOverlays(this)) {
startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))
} else {
Toast.makeText(this, "هذه الصلاحية مفعلة بالفعل", Toast.LENGTH_SHORT).show()
}
}
btnStart.setOnClickListener {
if (checkAllPermissions()) {
startBot()
} else {
Toast.makeText(this, "الرجاء تفعيل جميع الصلاحيات أولاً", Toast.LENGTH_LONG).show()
}
}
btnStop.setOnClickListener {
stopBot()
}
}
override fun onResume() {
super.onResume()
updateStatusUI()
}
private fun checkAllPermissions(): Boolean {
val notificationEnabled = NotificationManagerCompat.getEnabledListenerPackages(this).contains(packageName)
val overlayEnabled = Settings.canDrawOverlays(this)
// Check Accessibility
var accessibilityEnabled = false
val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
for (service in enabledServices) {
if (service.id.contains(packageName)) {
accessibilityEnabled = true
break
}
}
return notificationEnabled && overlayEnabled && accessibilityEnabled
}
private fun updateStatusUI() {
if (checkAllPermissions()) {
tvStatus.text = "✅ جاهز للتشغيل"
tvStatus.setTextColor(Color.parseColor("#00D4AA"))
} else {
tvStatus.text = "⚠️ بانتظار الصلاحيات"
tvStatus.setTextColor(Color.parseColor("#FFCA28"))
}
}
private fun startBot() {
startService(Intent(this, OverlayService::class.java))
startService(Intent(this, BotForegroundService::class.java))
tvStatus.text = "⚡ البوت يعمل حالياً"
tvStatus.setTextColor(Color.parseColor("#00D4AA"))
btnStart.visibility = View.GONE
btnStop.visibility = View.VISIBLE
Toast.makeText(this, "تم التشغيل بنجاح", Toast.LENGTH_SHORT).show()
}
private fun stopBot() {
stopService(Intent(this, OverlayService::class.java))
stopService(Intent(this, BotForegroundService::class.java))
tvStatus.text = "⏸ البوت متوقف"
tvStatus.setTextColor(Color.parseColor("#FF5252"))
btnStart.visibility = View.VISIBLE
btnStop.visibility = View.GONE
}
}

View File

@@ -0,0 +1,56 @@
package com.jordanbot.autoride.api
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonGsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST
// Data models for the API
data class RideLogRequest(
val platform: String,
val price: Double,
val pickupDistance: String,
val dropoffDistance: String,
val timeToPickup: String,
val isAccepted: Boolean,
val rawText: String,
val fingerprint: String
)
data class LocationPoint(
val latitude: Double,
val longitude: Double,
val speed: Float,
val timestamp: Long
)
data class BulkLocationRequest(
val fingerprint: String,
val locations: List<LocationPoint>
)
data class ApiResponse(
val success: Boolean,
val message: String
)
interface BackendApiService {
@POST("api/rides.php")
suspend fun logRide(@Body request: RideLogRequest): ApiResponse
@POST("api/location.php")
suspend fun updateBulkLocation(@Body request: BulkLocationRequest): ApiResponse
}
object ApiClient {
// Replace with your actual server URL
private const val BASE_URL = "https://lawer.tripz-egypt.com/jordan_bot/"
val service: BackendApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(BackendApiService::class.java)
}
}

View File

@@ -0,0 +1,28 @@
package com.jordanbot.autoride.filter
import com.jordanbot.autoride.model.RideRequest
class FilterEngine {
// These will eventually come from SharedPreferences / UI
var minPriceJod: Double = 1.0
var maxMinutesAway: Int = 10
var isEnabled: Boolean = true
fun evaluate(request: RideRequest): Boolean {
if (!isEnabled) return false
// Check Price
if (request.priceJod != null && request.priceJod < minPriceJod) {
return false
}
// Check Distance/Time
if (request.minutesAway != null && request.minutesAway > maxMinutesAway) {
return false
}
// Passes all filters
return true
}
}

View File

@@ -0,0 +1,10 @@
package com.jordanbot.autoride.model
data class RideRequest(
val appPackage: String,
val priceJod: Double?,
val minutesAway: Int?,
val distanceKm: Double?,
val title: String,
val text: String
)

View File

@@ -0,0 +1,35 @@
package com.jordanbot.autoride.parser
import com.jordanbot.autoride.model.RideRequest
class CareemParser : NotificationParser {
override val packageName = "com.careem.adma"
override fun parse(title: String, text: String): RideRequest? {
var price: Double? = null
var minutes: Int? = null
// Careem format examples: "رحلة جديدة - 2.5 د.أ", "New ride - 2.5 JOD"
val priceRegex = """(\d+\.?\d*)\s*(JOD|د\.أ|دينار)""".toRegex()
val minutesRegex = """(\d+)\s*(min|دقيقة|دقائق)""".toRegex()
val fullText = "$title $text"
priceRegex.find(fullText)?.let {
price = it.groupValues[1].toDoubleOrNull()
}
minutesRegex.find(fullText)?.let {
minutes = it.groupValues[1].toIntOrNull()
}
return RideRequest(
appPackage = packageName,
priceJod = price,
minutesAway = minutes,
distanceKm = null,
title = title,
text = text
)
}
}

View File

@@ -0,0 +1,35 @@
package com.jordanbot.autoride.parser
import com.jordanbot.autoride.model.RideRequest
class JeenyParser : NotificationParser {
override val packageName = "me.com.easytaxista"
override fun parse(title: String, text: String): RideRequest? {
var price: Double? = null
var distance: Double? = null
val priceRegex = """(\d+\.?\d*)\s*(JOD|د\.أ)""".toRegex()
// Jeeny often shows distance in km instead of minutes
val distanceRegex = """(\d+\.?\d*)\s*(km|كم)""".toRegex()
val fullText = "$title $text"
priceRegex.find(fullText)?.let {
price = it.groupValues[1].toDoubleOrNull()
}
distanceRegex.find(fullText)?.let {
distance = it.groupValues[1].toDoubleOrNull()
}
return RideRequest(
appPackage = packageName,
priceJod = price,
minutesAway = null,
distanceKm = distance,
title = title,
text = text
)
}
}

View File

@@ -0,0 +1,8 @@
package com.jordanbot.autoride.parser
import com.jordanbot.autoride.model.RideRequest
interface NotificationParser {
val packageName: String
fun parse(title: String, text: String): RideRequest?
}

View File

@@ -0,0 +1,29 @@
package com.jordanbot.autoride.parser
import com.jordanbot.autoride.model.RideRequest
class PetraRideParser : NotificationParser {
// Note: Package name might vary (e.g. com.PetraRide_Captain), updating to generic if needed
override val packageName = "com.petraride.captain"
override fun parse(title: String, text: String): RideRequest? {
var price: Double? = null
val priceRegex = """(\d+\.?\d*)\s*(JOD|د\.أ)""".toRegex()
val fullText = "$title $text"
priceRegex.find(fullText)?.let {
price = it.groupValues[1].toDoubleOrNull()
}
return RideRequest(
appPackage = packageName,
priceJod = price,
minutesAway = null,
distanceKm = null,
title = title,
text = text
)
}
}

View File

@@ -0,0 +1,28 @@
package com.jordanbot.autoride.parser
import com.jordanbot.autoride.model.RideRequest
class TaxiFParser : NotificationParser {
override val packageName = "com.taxif.driver"
override fun parse(title: String, text: String): RideRequest? {
var price: Double? = null
val priceRegex = """(\d+\.?\d*)\s*(JOD|د\.أ)""".toRegex()
val fullText = "$title $text"
priceRegex.find(fullText)?.let {
price = it.groupValues[1].toDoubleOrNull()
}
return RideRequest(
appPackage = packageName,
priceJod = price,
minutesAway = null,
distanceKm = null,
title = title,
text = text
)
}
}

View File

@@ -0,0 +1,38 @@
package com.jordanbot.autoride.parser
import com.jordanbot.autoride.model.RideRequest
class UberParser : NotificationParser {
override val packageName = "com.ubercab.driver"
override fun parse(title: String, text: String): RideRequest? {
// Example: "New trip request • 2.50 JOD • 5 min away"
// This is a basic regex, needs adjustment based on actual Uber notification in Jordan
var price: Double? = null
var minutes: Int? = null
val priceRegex = """(\d+\.?\d*)\s*(JOD|د\.أ)""".toRegex()
val minutesRegex = """(\d+)\s*(min|دقيقة)""".toRegex()
val fullText = "$title $text"
priceRegex.find(fullText)?.let {
price = it.groupValues[1].toDoubleOrNull()
}
minutesRegex.find(fullText)?.let {
minutes = it.groupValues[1].toIntOrNull()
}
// We return the request even if price is null, we can filter it later
return RideRequest(
appPackage = packageName,
priceJod = price,
minutesAway = minutes,
distanceKm = null, // Uber usually gives mins, not always distance
title = title,
text = text
)
}
}

View File

@@ -0,0 +1,141 @@
package com.jordanbot.autoride.service
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.*
import com.jordanbot.autoride.R
import com.jordanbot.autoride.api.ApiClient
import com.jordanbot.autoride.api.BulkLocationRequest
import com.jordanbot.autoride.api.LocationPoint
import com.jordanbot.autoride.utils.DeviceUtils
import kotlinx.coroutines.*
import java.util.concurrent.TimeUnit
class BotForegroundService : Service() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val locationBuffer = mutableListOf<LocationPoint>()
private val UPLOAD_INTERVAL_MS = TimeUnit.MINUTES.toMillis(5)
private val TRACK_INTERVAL_MS = TimeUnit.SECONDS.toMillis(3)
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
locationResult.lastLocation?.let { location ->
val point = LocationPoint(
latitude = location.latitude,
longitude = location.longitude,
speed = location.speed,
timestamp = System.currentTimeMillis()
)
synchronized(locationBuffer) {
locationBuffer.add(point)
}
Log.d("JordanBot", "📍 Location tracked: ${location.latitude}, ${location.longitude}")
}
}
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
createNotificationChannel()
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
startTracking()
startUploadTimer()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Jordan Bot يعمل حالياً")
.setContentText("نحن نراقب الإشعارات ونحدث الخرائط.")
.setSmallIcon(R.drawable.bg_bubble)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
startForeground(1, notification)
return START_STICKY
}
@SuppressLint("MissingPermission")
private fun startTracking() {
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, TRACK_INTERVAL_MS)
.setMinUpdateIntervalMillis(TRACK_INTERVAL_MS)
.build()
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
private fun startUploadTimer() {
serviceScope.launch {
while (isActive) {
delay(UPLOAD_INTERVAL_MS)
uploadLocations()
}
}
}
private fun uploadLocations() {
val pointsToUpload = synchronized(locationBuffer) {
val copy = locationBuffer.toList()
locationBuffer.clear()
copy
}
if (pointsToUpload.isEmpty()) return
val fingerprint = DeviceUtils.getDeviceFingerprint(this)
val request = BulkLocationRequest(fingerprint, pointsToUpload)
serviceScope.launch {
try {
val response = ApiClient.service.updateBulkLocation(request)
if (response.success) {
Log.d("JordanBot", "✅ Successfully uploaded ${pointsToUpload.size} locations")
} else {
Log.e("JordanBot", "❌ Failed to upload locations: ${response.message}")
}
} catch (e: Exception) {
Log.e("JordanBot", "❌ Error uploading locations", e)
// If failed, we might want to add them back to the buffer,
// but for now we just log to keep it simple.
}
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Jordan Bot Service Channel",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
override fun onDestroy() {
super.onDestroy()
fusedLocationClient.removeLocationUpdates(locationCallback)
serviceScope.cancel()
}
companion object {
const val CHANNEL_ID = "JordanBotForegroundChannel"
}
}

View File

@@ -0,0 +1,160 @@
package com.jordanbot.autoride.service
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.Button
import android.widget.EditText
import android.widget.Switch
import android.widget.TextView
import com.jordanbot.autoride.R
class OverlayService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var floatingView: View
private lateinit var expandedView: View
private lateinit var params: WindowManager.LayoutParams
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
floatingView = inflater.inflate(R.layout.overlay_floating, null)
expandedView = inflater.inflate(R.layout.overlay_expanded, null)
val layoutFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
layoutFlag,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.TOP or Gravity.START
params.x = 0
params.y = 100
windowManager.addView(floatingView, params)
setupFloatingView()
setupExpandedView()
}
private fun setupFloatingView() {
val tvBubble = floatingView.findViewById<TextView>(R.id.tv_bubble)
floatingView.setOnTouchListener(object : View.OnTouchListener {
private var initialX: Int = 0
private var initialY: Int = 0
private var initialTouchX: Float = 0f
private var initialTouchY: Float = 0f
private var isClick = false
override fun onTouch(v: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
initialX = params.x
initialY = params.y
initialTouchX = event.rawX
initialTouchY = event.rawY
isClick = true
return true
}
MotionEvent.ACTION_MOVE -> {
val diffX = (event.rawX - initialTouchX).toInt()
val diffY = (event.rawY - initialTouchY).toInt()
if (Math.abs(diffX) > 10 || Math.abs(diffY) > 10) {
isClick = false
}
params.x = initialX + diffX
params.y = initialY + diffY
windowManager.updateViewLayout(floatingView, params)
return true
}
MotionEvent.ACTION_UP -> {
if (isClick) {
showExpandedView()
}
return true
}
}
return false
}
})
}
private fun setupExpandedView() {
val btnClose = expandedView.findViewById<Button>(R.id.btn_save_close)
val etMinPrice = expandedView.findViewById<EditText>(R.id.et_min_price)
val switchStatus = expandedView.findViewById<Switch>(R.id.switch_bot_status)
btnClose.setOnClickListener {
// TODO: Save to SharedPreferences/FilterEngine
val minPriceStr = etMinPrice.text.toString()
val isEnabled = switchStatus.isChecked
// Update bubble UI
val tvBubble = floatingView.findViewById<TextView>(R.id.tv_bubble)
if (isEnabled) {
tvBubble.text = "Bot\nON"
tvBubble.setBackgroundResource(R.drawable.bg_bubble)
} else {
tvBubble.text = "Bot\nOFF"
// Would need a red bubble background here in a real app
}
hideExpandedView()
}
}
private fun showExpandedView() {
windowManager.removeView(floatingView)
// Change params to focusable so user can type in EditText
val expandedParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT
)
expandedParams.gravity = Gravity.CENTER
windowManager.addView(expandedView, expandedParams)
}
private fun hideExpandedView() {
windowManager.removeView(expandedView)
// Reset params to non-focusable
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
windowManager.addView(floatingView, params)
}
override fun onDestroy() {
super.onDestroy()
if (::floatingView.isInitialized) windowManager.removeView(floatingView)
if (::expandedView.isInitialized && expandedView.parent != null) windowManager.removeView(expandedView)
}
}

View File

@@ -0,0 +1,72 @@
package com.jordanbot.autoride.service
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
class RideAccessibilityService : AccessibilityService() {
private val handler = Handler(Looper.getMainLooper())
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (event.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return
val source = event.source ?: return
val packageName = event.packageName?.toString() ?: return
// Check if this package was recently flagged by NotificationListener to be accepted
if (RideNotificationListener.pendingAcceptPackage == packageName) {
val timeSinceRequest = System.currentTimeMillis() - RideNotificationListener.lastRequestTime
// Only accept if within 15 seconds of notification
if (timeSinceRequest < 15000) {
findAndClickAcceptButton(source)
} else {
RideNotificationListener.pendingAcceptPackage = null // Expired
}
}
}
private fun findAndClickAcceptButton(rootNode: AccessibilityNodeInfo) {
// Arabic and English accept terms
val acceptTerms = listOf("Accept", "قبول", "Tap to accept", "اضغط للقبول")
for (term in acceptTerms) {
val nodes = rootNode.findAccessibilityNodeInfosByText(term)
for (node in nodes) {
if (node.isClickable) {
// Simulating human delay (e.g., 500ms)
handler.postDelayed({
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
Log.d("JordanBot", "Clicked Accept on node: ${node.text}")
// Reset pending accept after clicking
RideNotificationListener.pendingAcceptPackage = null
}, 500)
return
}
}
}
}
override fun onInterrupt() {
Log.e("JordanBot", "Accessibility Service Interrupted")
}
override fun onServiceConnected() {
super.onServiceConnected()
Log.d("JordanBot", "Accessibility Service Connected")
val info = AccessibilityServiceInfo().apply {
eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED or AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC
notificationTimeout = 100
flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS or AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
}
this.serviceInfo = info
}
}

View File

@@ -0,0 +1,91 @@
package com.jordanbot.autoride.service
import android.app.Notification
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import com.jordanbot.autoride.api.ApiClient
import com.jordanbot.autoride.api.RideLogRequest
import com.jordanbot.autoride.filter.FilterEngine
import com.jordanbot.autoride.parser.*
import com.jordanbot.autoride.utils.DeviceUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class RideNotificationListener : NotificationListenerService() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val filterEngine = FilterEngine()
private val parsers = listOf(
UberParser(),
CareemParser(),
JeenyParser(),
PetraRideParser(),
TaxiFParser()
)
companion object {
var pendingAcceptPackage: String? = null
var lastRequestTime: Long = 0
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
val packageName = sbn.packageName
val parser = parsers.find { it.packageName == packageName } ?: return
val notification = sbn.notification
val extras = notification.extras
val title = extras.getString(Notification.EXTRA_TITLE) ?: ""
val text = extras.getCharSequence(Notification.EXTRA_TEXT)?.toString() ?: ""
Log.d("JordanBot", "Notification from $packageName: $title - $text")
val rideRequest = parser.parse(title, text)
if (rideRequest != null) {
val isAccepted = filterEngine.evaluate(rideRequest)
// Send to Backend for Data Mining
sendLogToBackend(rideRequest, isAccepted, "$title - $text")
if (isAccepted) {
Log.d("JordanBot", "Ride ACCEPTED by filter: $rideRequest")
pendingAcceptPackage = packageName
lastRequestTime = System.currentTimeMillis()
} else {
Log.d("JordanBot", "Ride REJECTED by filter: $rideRequest")
}
}
}
private fun sendLogToBackend(ride: com.jordanbot.autoride.model.RideRequest, isAccepted: Boolean, rawText: String) {
val logRequest = RideLogRequest(
platform = ride.platform,
price = ride.price,
pickupDistance = ride.pickupDistance,
dropoffDistance = ride.dropoffDistance,
timeToPickup = ride.timeToPickup,
isAccepted = isAccepted,
rawText = rawText,
fingerprint = DeviceUtils.getDeviceFingerprint(this)
)
serviceScope.launch {
try {
val response = ApiClient.service.logRide(logRequest)
if (response.success) {
Log.d("JordanBot", "✅ Ride log sent to server")
}
} catch (e: Exception) {
Log.e("JordanBot", "❌ Failed to send ride log", e)
}
}
}
override fun onListenerConnected() {
super.onListenerConnected()
Log.d("JordanBot", "Notification Listener Connected")
}
}

View File

@@ -0,0 +1,13 @@
package com.jordanbot.autoride.utils
import android.content.Context
import android.provider.Settings
object DeviceUtils {
/**
* Returns a unique fingerprint for the device based on Android ID.
*/
fun getDeviceFingerprint(context: Context): String {
return Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) ?: "UNKNOWN_DEVICE"
}
}