package com.dmc.callcrm.ui import android.Manifest import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment import android.provider.Settings import android.widget.ArrayAdapter import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.dmc.callcrm.ApiConfig import com.dmc.callcrm.databinding.ActivityMainBinding import com.dmc.callcrm.data.Prefs import com.dmc.callcrm.data.SimHelper import com.dmc.callcrm.data.SimInfo import com.dmc.callcrm.network.ApiClient import com.dmc.callcrm.network.RegisterRequest import com.dmc.callcrm.service.CallMonitorService import com.dmc.callcrm.sync.RecordingWorker import com.dmc.callcrm.sync.SyncWorker import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { private lateinit var b: ActivityMainBinding private lateinit var prefs: Prefs private var sims: List = emptyList() private val permLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { result -> if (result[Manifest.permission.READ_PHONE_STATE] == true) loadSims() else b.tvStatus.text = "Phone permission is needed to detect SIMs." } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) b = ActivityMainBinding.inflate(layoutInflater) setContentView(b.root) prefs = Prefs(this) if (prefs.isRegistered) { showConnected(); return } requestPermissions() b.btnConnect.setOnClickListener { register() } } // -------- permissions -------- private fun requestPermissions() { val perms = mutableListOf( Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_CONTACTS, Manifest.permission.READ_PHONE_STATE ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { perms += Manifest.permission.POST_NOTIFICATIONS perms += Manifest.permission.READ_PHONE_NUMBERS perms += Manifest.permission.READ_MEDIA_AUDIO } else { perms += Manifest.permission.READ_EXTERNAL_STORAGE } permLauncher.launch(perms.toTypedArray()) } /** All-files access is needed to read recorder folders across OEMs. */ private fun ensureAllFilesAccess() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { try { startActivity( Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:$packageName")) ) Toast.makeText(this, "Enable 'Allow access to manage all files' so call recordings can be uploaded.", Toast.LENGTH_LONG).show() } catch (_: Exception) { /* ignore on OEMs that block it */ } } } // -------- SIM list -------- private fun loadSims() { sims = SimHelper.listSims(this) val labels = if (sims.isEmpty()) { listOf("Default SIM") } else { sims.map { "Slot ${it.slotIndex + 1} · ${it.carrier}" + (it.number?.let { n -> " ($n)" } ?: "") } } b.spinnerSim.adapter = ArrayAdapter( this, android.R.layout.simple_spinner_dropdown_item, labels ) // Pre-fill mobile from SIM if the carrier exposed it sims.firstOrNull()?.number?.let { if (b.etMobile.text.isNullOrBlank()) b.etMobile.setText(it) } } // -------- registration -------- @SuppressLint("HardwareIds") private fun register() { val connect = b.etConnect.text.toString().trim().uppercase() val name = b.etName.text.toString().trim() val mobile = b.etMobile.text.toString().trim() val company = b.etCompany.text.toString().trim() if (connect.isEmpty() || name.isEmpty() || mobile.isEmpty()) { Toast.makeText(this, "Enter connect code, name and mobile", Toast.LENGTH_SHORT).show() return } val slot = b.spinnerSim.selectedItemPosition val simSlot = sims.getOrNull(slot)?.slotIndex ?: slot val deviceId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) val model = "${Build.MANUFACTURER} ${Build.MODEL}" val appVer = try { packageManager.getPackageInfo(packageName, 0).versionName } catch (e: Exception) { null } b.btnConnect.isEnabled = false b.tvStatus.text = "Connecting…" lifecycleScope.launch { try { val resp = ApiClient.service.register( RegisterRequest( connectCode = connect, name = name, mobile = mobile, company = company, employeeCode = null, modelName = model, appVersion = appVer, deviceId = deviceId, simSlot = simSlot, fcmToken = null, appSecret = ApiConfig.APP_SECRET ) ) val body = resp.body() if (resp.isSuccessful && body?.ok == true) { prefs.apiToken = body.apiToken prefs.employeeId = body.employeeId ?: 0L prefs.name = name prefs.mobile = mobile prefs.simSlot = simSlot startBackgroundWork() showConnected() } else { b.tvStatus.text = "Failed: ${body?.error ?: resp.code()}" b.btnConnect.isEnabled = true } } catch (e: Exception) { b.tvStatus.text = "Network error: ${e.message}" b.btnConnect.isEnabled = true } } } private fun startBackgroundWork() { SyncWorker.schedule(this) RecordingWorker.schedule(this) CallMonitorService.start(this) askIgnoreBatteryOptimizations() ensureAllFilesAccess() } private fun showConnected() { b.tvStatus.text = "✓ Connected as ${prefs.name ?: ""} (${prefs.mobile ?: ""}).\n" + "Call activity is syncing. For call recordings: turn ON auto call-recording in this " + "phone's Phone/Dialer app, and grant 'all files access' when prompted." b.btnConnect.isEnabled = false b.btnConnect.text = "Connected" } // Ask the user to exempt the app from Doze so background sync survives. private fun askIgnoreBatteryOptimizations() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { startActivity( Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:$packageName")) ) } catch (_: Exception) { /* some OEMs block this; ignore */ } } } }