From 6c5ce1beab9e097d4d0cbf0ef932cdb8310df69f Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Sun, 27 Apr 2025 17:27:27 +0300 Subject: [PATCH] feat: can mute mic in call --- .../icing/dialer/activities/MainActivity.kt | 13 ++++++++ .../icing/dialer/services/MyInCallService.kt | 28 +++++++++++++++-- dialer/lib/features/call/call_page.dart | 31 ++++++++++++++++--- dialer/lib/services/call_service.dart | 21 +++++++++++++ 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt index eb362de..0c492b5 100644 --- a/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt +++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt @@ -165,6 +165,19 @@ class MainActivity : FlutterActivity() { Log.d(TAG, "getCallState called, returning: $stateStr") result.success(stateStr) } + "muteCall" -> { + val mute = call.argument("mute") ?: false + val success = MyInCallService.currentCall?.let { + MyInCallService.toggleMute(mute) + } ?: false + if (success) { + Log.d(TAG, "Mute call set to $mute") + result.success(mapOf("status" to "success")) + } else { + Log.w(TAG, "No active call or failed to mute") + result.error("MUTE_FAILED", "No active call or failed to mute", null) + } + } else -> result.notImplemented() } } diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt index b5a4c8a..d79abd1 100644 --- a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt +++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt @@ -6,6 +6,7 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.media.AudioManager import android.os.Build import android.telecom.Call import android.telecom.InCallService @@ -22,6 +23,24 @@ class MyInCallService : InCallService() { private const val NOTIFICATION_CHANNEL_ID = "incoming_call_channel" private const val NOTIFICATION_ID = 1 var wasPhoneLocked: Boolean = false + private var instance: MyInCallService? = null + + fun toggleMute(mute: Boolean): Boolean { + return instance?.let { service -> + try { + val audioManager = service.getSystemService(Context.AUDIO_SERVICE) as AudioManager + audioManager.isMicrophoneMute = mute + Log.d(TAG, "Set microphone mute state to $mute") + channel?.invokeMethod("audioStateChanged", mapOf( + "muted" to mute + )) + true + } catch (e: Exception) { + Log.e(TAG, "Failed to set mute state: $e") + false + } + } ?: false + } } private val callCallback = object : Call.Callback() { @@ -60,6 +79,7 @@ class MyInCallService : InCallService() { override fun onCallAdded(call: Call) { super.onCallAdded(call) + instance = this currentCall = call val stateStr = when (call.state) { Call.STATE_DIALING -> "dialing" @@ -90,13 +110,17 @@ class MyInCallService : InCallService() { "wasPhoneLocked" to wasPhoneLocked )) currentCall = null + instance = null cancelNotification() } override fun onCallAudioStateChanged(state: android.telecom.CallAudioState) { super.onCallAudioStateChanged(state) - Log.d(TAG, "Audio state changed: route=${state.route}") - channel?.invokeMethod("audioStateChanged", mapOf("route" to state.route)) + Log.d(TAG, "Audio state changed: route=${state.route}, muted=${state.isMuted}") + channel?.invokeMethod("audioStateChanged", mapOf( + "route" to state.route, + "muted" to state.isMuted + )) } private fun showIncomingCallScreen(phoneNumber: String) { diff --git a/dialer/lib/features/call/call_page.dart b/dialer/lib/features/call/call_page.dart index dbbea12..ab42055 100644 --- a/dialer/lib/features/call/call_page.dart +++ b/dialer/lib/features/call/call_page.dart @@ -102,10 +102,31 @@ class _CallPageState extends State { }); } - void _toggleMute() { - setState(() { - isMuted = !isMuted; - }); + void _toggleMute() async { + try { + print('CallPage: Toggling mute, current state: $isMuted'); + final result = await _callService.muteCall(context, mute: !isMuted); + print('CallPage: Mute call result: $result'); + if (mounted && result['status'] == 'success') { + setState(() { + isMuted = !isMuted; + }); + } else { + print('CallPage: Failed to toggle mute: ${result['message']}'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to toggle mute: ${result['message']}')), + ); + } + } + } catch (e) { + print('CallPage: Error toggling mute: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error toggling mute: $e')), + ); + } + } } void _toggleSpeaker() { @@ -298,7 +319,7 @@ class _CallPageState extends State { onPressed: _toggleMute, icon: Icon( isMuted ? Icons.mic_off : Icons.mic, - color: Colors.white, + color: isMuted ? Colors.amber : Colors.white, size: 32, ), ), diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 821608f..33e9310 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -144,6 +144,27 @@ class CallService { } } + Future> muteCall(BuildContext context, {required bool mute}) async { + try { + print('CallService: Toggling mute to $mute'); + final result = await _channel.invokeMethod('muteCall', {'mute': mute}); + print('CallService: muteCall result: $result'); + final resultMap = Map.from(result as Map); + if (resultMap['status'] != 'success') { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to toggle mute')), + ); + } + return resultMap; + } catch (e) { + print('CallService: Error toggling mute: $e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error toggling mute: $e')), + ); + return {'status': 'error', 'message': e.toString()}; + } + } + void dispose() { _callStateController.close(); }