first
This commit is contained in:
80
app/src/main/AndroidManifest.xml
Normal file
80
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Core permissions -->
|
||||
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<!-- Keep alive -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
|
||||
<!-- Network (for future activation API) -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.JordanBot"
|
||||
tools:targetApi="34">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".service.RideNotificationListener"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.RideAccessibilityService"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_config" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.OverlayService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".service.BotForegroundService"
|
||||
android:foregroundServiceType="specialUse|location"
|
||||
android:exported="false" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
app/src/main/java/com/jordanbot/.DS_Store
vendored
Normal file
BIN
app/src/main/java/com/jordanbot/.DS_Store
vendored
Normal file
Binary file not shown.
118
app/src/main/java/com/jordanbot/autoride/MainActivity.kt
Normal file
118
app/src/main/java/com/jordanbot/autoride/MainActivity.kt
Normal 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
|
||||
}
|
||||
}
|
||||
56
app/src/main/java/com/jordanbot/autoride/api/BackendApi.kt
Normal file
56
app/src/main/java/com/jordanbot/autoride/api/BackendApi.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
6
app/src/main/res/drawable/bg_bubble.xml
Normal file
6
app/src/main/res/drawable/bg_bubble.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#4CAF50" />
|
||||
<stroke android:width="2dp" android:color="#FFFFFF"/>
|
||||
</shape>
|
||||
6
app/src/main/res/drawable/bg_button.xml
Normal file
6
app/src/main/res/drawable/bg_button.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#353555" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
6
app/src/main/res/drawable/bg_button_danger.xml
Normal file
6
app/src/main/res/drawable/bg_button_danger.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#D32F2F" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
6
app/src/main/res/drawable/bg_button_primary.xml
Normal file
6
app/src/main/res/drawable/bg_button_primary.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#00D4AA" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
7
app/src/main/res/drawable/bg_card.xml
Normal file
7
app/src/main/res/drawable/bg_card.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#24243E" />
|
||||
<corners android:radius="12dp" />
|
||||
<stroke android:width="1dp" android:color="#33334C" />
|
||||
</shape>
|
||||
13
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
13
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M54,30l-20,20h40z M34,55h40v5h-40z" />
|
||||
</vector>
|
||||
211
app/src/main/res/layout/activity_main.xml
Normal file
211
app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,211 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1A1A2E"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- App Title -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="⚡ Jordan Bot"
|
||||
android:textColor="#00D4AA"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="بوت قبول الرحلات التلقائي"
|
||||
android:textColor="#AAAAAA"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<!-- Status Card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⏸ البوت متوقف"
|
||||
android:textColor="#FF5252"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="فعّل الصلاحيات أدناه ثم اضغط تشغيل"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Step 1: Notification Access -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="① قراءة الإشعارات"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="مطلوب لقراءة إشعارات تطبيقات النقل (Uber, Careem, Jeeny...)"
|
||||
android:textColor="#999999"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_notification"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_button"
|
||||
android:text="تفعيل قراءة الإشعارات"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Step 2: Accessibility -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="② الضغط التلقائي (Accessibility)"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="مطلوب لكي يضغط البوت على زر القبول تلقائياً نيابة عنك"
|
||||
android:textColor="#999999"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_accessibility"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_button"
|
||||
android:text="تفعيل خدمة الوصول"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Step 3: Overlay -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="③ الظهور فوق التطبيقات"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="مطلوب لإظهار لوحة التحكم العائمة أثناء عملك على التطبيقات الأخرى"
|
||||
android:textColor="#999999"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bg_button"
|
||||
android:text="تفعيل الظهور فوق التطبيقات"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Launch Button -->
|
||||
<Button
|
||||
android:id="@+id/btn_start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@drawable/bg_button_primary"
|
||||
android:text="🚀 تشغيل Jordan Bot"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_stop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/bg_button_danger"
|
||||
android:text="⏹ إيقاف البوت"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Supported apps info -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:text="التطبيقات المدعومة:\nUber • Careem • Jeeny • Petra Ride • TaxiF"
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="v1.0.0 — Jordan Bot © 2026"
|
||||
android:textColor="#444444"
|
||||
android:textSize="11sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
59
app/src/main/res/layout/overlay_expanded.xml
Normal file
59
app/src/main/res/layout/overlay_expanded.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#FFFFFF"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:elevation="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="إعدادات Jordan Bot ⚡"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switch_bot_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="حالة البوت (ON/OFF)"
|
||||
android:checked="true"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="أقل سعر للرحلة (دينار):" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_min_price"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberDecimal"
|
||||
android:text="1.50"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="أقصى وقت للوصول (دقائق):" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_max_minutes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:text="10"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_save_close"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="حفظ وإغلاق" />
|
||||
|
||||
</LinearLayout>
|
||||
17
app/src/main/res/layout/overlay_floating.xml
Normal file
17
app/src/main/res/layout/overlay_floating.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_bubble"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:background="@drawable/bg_bubble"
|
||||
android:gravity="center"
|
||||
android:text="Bot\nON"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:elevation="8dp" />
|
||||
</FrameLayout>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/bg_bubble"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/bg_bubble"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
4
app/src/main/res/values/strings.xml
Normal file
4
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Jordan Bot</string>
|
||||
<string name="accessibility_service_description">هذه الخدمة ضرورية لـ Jordan Bot لكي يقوم بقراءة تفاصيل الرحلة من الشاشة والضغط على زر قبول الرحلة تلقائياً عندما تتطابق مع شروطك.</string>
|
||||
</resources>
|
||||
14
app/src/main/res/values/themes.xml
Normal file
14
app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="Theme.JordanBot" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">#6200EE</item>
|
||||
<item name="colorPrimaryVariant">#3700B3</item>
|
||||
<item name="colorOnPrimary">#FFFFFF</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">#03DAC5</item>
|
||||
<item name="colorSecondaryVariant">#018786</item>
|
||||
<item name="colorOnSecondary">#000000</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
</style>
|
||||
</resources>
|
||||
9
app/src/main/res/xml/accessibility_config.xml
Normal file
9
app/src/main/res/xml/accessibility_config.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagDefault|flagReportViewIds|flagRetrieveInteractiveWindows"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:description="@string/accessibility_service_description"
|
||||
android:notificationTimeout="100"
|
||||
android:settingsActivity="com.jordanbot.autoride.MainActivity" />
|
||||
4
app/src/main/res/xml/backup_rules.xml
Normal file
4
app/src/main/res/xml/backup_rules.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<include domain="sharedpref" path="."/>
|
||||
</full-backup-content>
|
||||
6
app/src/main/res/xml/data_extraction_rules.xml
Normal file
6
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-network>
|
||||
<allow>true</allow>
|
||||
</cloud-network>
|
||||
</data-extraction-rules>
|
||||
Reference in New Issue
Block a user