diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 7ecf2af9..7ac9d00b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -1653,6 +1653,12 @@ class BaresipService: Service() { fun runCall(uap: Long, uri: String, conferenceCall: Boolean, onHoldCallp: Long) { + val ua = UserAgent.ofUap(uap) + if (ua != null && ua.account.isMobile && Utils.isUssd(uri)) { + sendUssd(Utils.uriUserPart(uri)) + return + } + val executeCall = { val handler = Handler(Looper.getMainLooper()) handler.postDelayed({ @@ -2312,6 +2318,47 @@ class BaresipService: Service() { updateMobileStatus(status) } + @SuppressLint("MissingPermission") + private fun sendUssd(ussdCode: String) { + val uap = uas.value.find { it.account.isMobile }?.uap ?: 0L + if (VERSION.SDK_INT >= 26) { + val tm = getSystemService(TELEPHONY_SERVICE) as TelephonyManager + try { + tm.sendUssdRequest(ussdCode, object : TelephonyManager.UssdResponseCallback() { + override fun onReceiveUssdResponse( + telephonyManager: TelephonyManager, + request: String, + response: CharSequence + ) { + super.onReceiveUssdResponse(telephonyManager, request, response) + Log.d(TAG, "USSD response for $request: $response") + postServiceEvent(ServiceEvent("ussd response", arrayListOf(uap, request, response.toString()), System.nanoTime())) + } + + override fun onReceiveUssdResponseFailed( + telephonyManager: TelephonyManager, + request: String, + failureCode: Int + ) { + super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode) + Log.d(TAG, "USSD response for $request failed with code $failureCode") + val error = when (failureCode) { + TelephonyManager.USSD_RETURN_FAILURE -> getString(R.string.ussd_failed) + TelephonyManager.USSD_ERROR_SERVICE_UNAVAIL -> getString(R.string.ussd_service_unavailable) + else -> getString(R.string.ussd_unknown_error) + } + postServiceEvent(ServiceEvent("ussd fail", arrayListOf(uap, request, error), System.nanoTime())) + } + }, Handler(Looper.getMainLooper())) + } catch (e: Exception) { + Log.e(TAG, "Exception sending USSD request: ${e.message}") + toast(getString(R.string.ussd_failed)) + } + } else { + toast(getString(R.string.ussd_not_supported)) + } + } + @SuppressLint("FullScreenIntentPolicy") fun handleIncomingCall(call: Call) { val ua = call.ua diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt index 9769efce..336be445 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt @@ -2057,6 +2057,14 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, else if (Utils.isAudioMode(ctx,AudioManager.MODE_IN_CALL) && !Call.calls().any { it.ua.account.aor == ua.account.aor }) Toast.makeText(ctx, R.string.call_already_active, Toast.LENGTH_SHORT).show() + else if (ua.account.isMobile && Utils.isUssd(uri)) { + viewModel.dialerState.callButtonsEnabled.value = false + val baresipService = Intent(ctx, BaresipService::class.java) + baresipService.action = "Start Call" + baresipService.putExtra("uap", ua.uap) + baresipService.putExtra("uri", uri) + ContextCompat.startForegroundService(ctx, baresipService) + } else { viewModel.dialerState.callButtonsEnabled.value = false var error = "" @@ -2330,6 +2338,20 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params val aor = ua.account.aor when (ev[0]) { + "ussd response" -> { + if (aor == viewModel.selectedAor.value) + viewModel.dialerState.callButtonsEnabled.value = true + handleDialog(ctx, ctx.getString(R.string.info), params[2] as String) + handleNextEvent() + return + } + "ussd fail" -> { + if (aor == viewModel.selectedAor.value) + viewModel.dialerState.callButtonsEnabled.value = true + handleDialog(ctx, ctx.getString(R.string.error), params[2] as String) + handleNextEvent() + return + } "call rejected" -> {} "call outgoing" -> { val callp = params[1] as Long diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index 5f190b39..b7adc48d 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -370,13 +370,18 @@ object Utils { } fun isTelNumber(no: String): Boolean { - return no.isNotEmpty() && Regex("^([+][1-9])?[0-9- (),*#]{0,24}$").matches(no) + return no.isNotEmpty() && Regex("^[0-9- (),*#+]{1,32}$").matches(no) } fun isTelUri(uri: String): Boolean { return uri.startsWith("tel:") && isTelNumber(uri.substring(4)) } + fun isUssd(uri: String): Boolean { + val user = uriUserPart(uri) + return user.startsWith("*") && user.endsWith("#") + } + fun checkUri(uri: String): Boolean { return checkSipUri(uri) || isTelUri(uri) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 717aa2d5..14f4083e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -633,6 +633,10 @@ No network connection! No hardware Acoustic Echo Cancellation! Audio focus denied! + USSD failed + USSD service unavailable + USSD unknown error + USSD not supported Permissions rationale baresip needs \"Microphone\" permission for voice calls, \"Nearby devices\" permission for Bluetooth microphone/speaker detection,