first commit

This commit is contained in:
Hamza-Ayed
2026-05-23 16:17:20 +03:00
commit 2bbaa1ee16
195 changed files with 11126 additions and 0 deletions

View File

@@ -0,0 +1,327 @@
package com.intaleq.flashcall
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.content.pm.ServiceInfo
import android.telephony.SmsManager
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
class CallerService : Service() {
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private lateinit var flashCallManager: FlashCallManager
private var callPollerJob: Job? = null
private var smsPollerJob: Job? = null
companion object {
var isRunning = false
private set
// Callback for MainActivity to receive log messages
var logListener: ((String) -> Unit)? = null
private const val CHANNEL_ID = "flash_call_service_channel"
private const val NOTIFICATION_ID = 1001
}
private val notificationManager by lazy {
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
override fun onCreate() {
super.onCreate()
isRunning = true
flashCallManager = FlashCallManager(this)
createNotificationChannel()
// startForeground(NOTIFICATION_ID, buildNotification("Flash OTP Caller — Active"))
// ... inside onCreate() ...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(
NOTIFICATION_ID,
buildNotification("Flash OTP Caller — Active"),
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
} else {
startForeground(NOTIFICATION_ID, buildNotification("Flash OTP Caller — Active"))
}
// Register SMS hardware status receiver
val filter = android.content.IntentFilter("SMS_SENT")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(smsReceiver, filter, Context.RECEIVER_EXPORTED)
} else {
registerReceiver(smsReceiver, filter)
}
startPolling()
addLog("Service started")
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
callPollerJob?.cancel()
smsPollerJob?.cancel()
serviceScope.cancel()
try {
unregisterReceiver(smsReceiver)
} catch (e: Exception) {
// Ignore if not registered
}
isRunning = false
addLog("Service stopped")
}
private fun startPolling() {
callPollerJob = serviceScope.launch {
while (isActive) {
try {
pollCallTask()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
addLog("Call poll error: ${e.message}")
}
delay(3000)
}
}
smsPollerJob = serviceScope.launch {
while (isActive) {
try {
pollSmsTask()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
addLog("SMS poll error: ${e.message}")
}
delay(3000)
}
}
}
private suspend fun pollCallTask() {
val prefs = getSharedPreferences("flash_call_prefs", Context.MODE_PRIVATE)
val deviceId = prefs.getString("device_id", null) ?: return
val appKey = prefs.getString("app_key", null) ?: return
RetrofitClient.setAppKey(appKey)
val api = RetrofitClient.apiService
val task = try {
val result = api.pendingCall(deviceId, appKey)
addLog("Call poll OK: taskId=${result.taskId}")
result
} catch (e: retrofit2.HttpException) {
addLog("Call poll HTTP ${e.code()}: ${e.message()}")
return
} catch (e: Exception) {
addLog("Call poll net error: ${e.javaClass.simpleName}: ${e.message}")
return
}
val taskId = task.taskId ?: return
val phone = task.phone ?: return
addLog("Call task #$taskId$phone")
val result = flashCallManager.makeFlashCall(phone)
addLog("Call #$taskId result: $result")
// Report result back to server
try {
api.callDone(
CallDoneRequest(
taskId = taskId,
deviceId = deviceId,
appKey = appKey,
result = result
)
)
addLog("Call #$taskId reported: $result")
} catch (e: Exception) {
addLog("Call #$taskId report failed: ${e.message}")
}
updateNotification("Last call: #$taskId$result")
}
private suspend fun pollSmsTask() {
val prefs = getSharedPreferences("flash_call_prefs", Context.MODE_PRIVATE)
val deviceId = prefs.getString("device_id", null) ?: return
val appKey = prefs.getString("app_key", null) ?: return
RetrofitClient.setAppKey(appKey)
val api = RetrofitClient.apiService
val task = try {
val result = api.pendingSms(deviceId, appKey)
addLog("SMS poll OK: taskId=${result.taskId}")
result
} catch (e: retrofit2.HttpException) {
addLog("SMS poll HTTP ${e.code()}: ${e.message()}")
return
} catch (e: Exception) {
addLog("SMS poll net error: ${e.javaClass.simpleName}: ${e.message}")
return
}
val taskId = task.taskId ?: return
val phone = task.phone ?: return
val otp = task.otp ?: return
addLog("SMS task #$taskId$phone (OTP: $otp)")
val result = sendSms(phone, otp)
addLog("SMS #$taskId result: $result")
// Report result back to server
try {
api.smsDone(
CallDoneRequest(
taskId = taskId,
deviceId = deviceId,
appKey = appKey,
result = result
)
)
addLog("SMS #$taskId reported: $result")
} catch (e: Exception) {
addLog("SMS #$taskId report failed: ${e.message}")
}
updateNotification("Last SMS: #$taskId$result")
}
private fun sendSms(phone: String, otp: String): String {
return try {
val message = "رمز التحقق في تطبيق انطلق هو: $otp"
// Format phone to local Jordan format if needed (+962 -> 0)
val formattedPhone = if (phone.startsWith("+962")) {
val suffix = phone.substring(4)
if (suffix.startsWith("0")) suffix else "0$suffix"
} else {
phone
}
addLog("SMS sending to $formattedPhone...")
// Create a PendingIntent to track if SMS was sent (Explicit intent for Android 14+)
val sentIntent = android.app.PendingIntent.getBroadcast(
this, 0,
Intent("SMS_SENT").apply { setPackage(packageName) },
android.app.PendingIntent.FLAG_IMMUTABLE
)
val smsManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
getSystemService(SmsManager::class.java)
} else {
@Suppress("DEPRECATION")
SmsManager.getDefault()
}
smsManager.sendTextMessage(formattedPhone, null, message, sentIntent, null)
addLog("SMS handed to OS for $formattedPhone")
"success"
} catch (e: SecurityException) {
addLog("SMS SecurityException: ${e.message}")
"failed"
} catch (e: Exception) {
addLog("SMS Exception: ${e.javaClass.simpleName}: ${e.message}")
"failed"
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Flash OTP Caller Service",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Keeps the flash call service running"
setShowBadge(false)
}
notificationManager.createNotificationChannel(channel)
}
}
private fun buildNotification(text: String): Notification {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Flash OTP Caller")
.setContentText(text)
.setSmallIcon(android.R.drawable.ic_menu_call)
.setOngoing(true)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
}
private fun updateNotification(text: String) {
try {
val notification = buildNotification(text)
notificationManager.notify(NOTIFICATION_ID, notification)
} catch (e: Exception) {
// Ignore notification update errors
}
}
private fun addLog(message: String) {
val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
val logLine = "[$timestamp] $message"
// Also log to system Logcat for Android Studio visibility
android.util.Log.d("CallerService", logLine)
// Save to SharedPreferences for MainActivity to read
val prefs = getSharedPreferences("flash_call_prefs", Context.MODE_PRIVATE)
val existingLogs = prefs.getString("service_logs", "") ?: ""
val logs = (existingLogs.split("\n") + logLine).takeLast(20)
prefs.edit().putString("service_logs", logs.joinToString("\n")).apply()
// Notify listener if attached
logListener?.invoke(logLine)
}
private val smsReceiver = object : android.content.BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "SMS_SENT") {
val result = when (resultCode) {
android.app.Activity.RESULT_OK -> "SUCCESS (Hardware confirmed)"
android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE -> "FAILED (Generic/No Credit)"
android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE -> "FAILED (No Cell Service)"
android.telephony.SmsManager.RESULT_ERROR_NULL_PDU -> "FAILED (Null PDU)"
android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF -> "FAILED (Radio Off/Airplane Mode)"
else -> "FAILED (Unknown code: $resultCode)"
}
addLog("SMS Hardware Result: $result")
}
}
}
}