From 72dd30995e14cc72257e7bf594a6ec6511dafd88 Mon Sep 17 00:00:00 2001
From: Florian Griffon <florian.griffon@epitech.eu>
Date: Sun, 23 Mar 2025 15:33:03 +0200
Subject: [PATCH] feat: app is now default dialer app

---
 dialer/android/.gitignore                     |   1 +
 .../android/app/src/main/AndroidManifest.xml  |  47 ++++-
 .../icing/dialer/activities/MainActivity.kt   | 190 +++++++++++++-----
 .../dialer/services/CallConnectionService.kt  |  82 ++++++++
 .../com/icing/dialer/services/CallService.kt  |  55 +++--
 .../icing/dialer/services/MyInCallService.kt  |  69 +++++++
 dialer/lib/features/call/call_page.dart       |  62 +++---
 .../lib/features/call/incoming_call_page.dart | 181 +++++++++++++++++
 .../lib/features/composition/composition.dart |   6 +-
 .../contacts/widgets/contact_modal.dart       |  42 ++--
 dialer/lib/features/history/history_page.dart |   2 +-
 dialer/lib/main.dart                          |  31 ++-
 dialer/lib/services/call_service.dart         | 148 +++++++++++++-
 13 files changed, 767 insertions(+), 149 deletions(-)
 create mode 100644 dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallConnectionService.kt
 create mode 100644 dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt
 create mode 100644 dialer/lib/features/call/incoming_call_page.dart

diff --git a/dialer/android/.gitignore b/dialer/android/.gitignore
index e6d71b3..ebc61c7 100644
--- a/dialer/android/.gitignore
+++ b/dialer/android/.gitignore
@@ -7,6 +7,7 @@ gradle-wrapper.jar
 /gradle.properties
 GeneratedPluginRegistrant.java
 gradle.properties
+.cxx
 
 # Remember to never publicly share your keystore.
 # See https://flutter.dev/to/reference-keystore
diff --git a/dialer/android/app/src/main/AndroidManifest.xml b/dialer/android/app/src/main/AndroidManifest.xml
index e0de6a4..fcaf71f 100644
--- a/dialer/android/app/src/main/AndroidManifest.xml
+++ b/dialer/android/app/src/main/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.icing.dialer">
+    <uses-feature android:name="android.hardware.telephony" android:required="true" />
+
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.CALL_PHONE" />
@@ -7,7 +9,9 @@
     <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
 
     <application
         android:label="Icing Dialer"
@@ -35,7 +39,48 @@
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
+            <!-- Dialer intent filters (required for default dialer eligibility) -->
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="tel" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.CALL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="tel" />
+            </intent-filter>
         </activity>
+        <service
+            android:name=".services.MyInCallService"
+            android:permission="android.permission.BIND_INCALL_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.InCallService" />
+            </intent-filter>
+            <meta-data
+                android:name="android.telecom.IN_CALL_SERVICE_UI"
+                android:value="true" />
+        </service>
+        <!-- Custom ConnextionService, will be needed at some point when we implement our own protocol -->
+        <!-- <service
+            android:name=".services.CallConnectionService"
+            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService" />
+            </intent-filter>
+        </service> -->
+        
         <!-- Don't delete the meta-data below.
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
         <meta-data
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 a2397d6..2fe4eb9 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
@@ -1,54 +1,91 @@
 package com.icing.dialer.activities
 
+import android.Manifest
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
 import android.database.Cursor
+import android.net.Uri
+import android.os.Build
 import android.os.Bundle
 import android.provider.CallLog
-import io.flutter.embedding.android.FlutterActivity
-import io.flutter.embedding.engine.FlutterEngine
-import io.flutter.plugin.common.MethodCall
-import io.flutter.plugin.common.MethodChannel
+import android.telecom.TelecomManager
+import android.util.Log
+import androidx.core.content.ContextCompat
 import com.icing.dialer.KeystoreHelper
 import com.icing.dialer.services.CallService
+import com.icing.dialer.services.MyInCallService
+import io.flutter.embedding.android.FlutterActivity
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
 
-class MainActivity: FlutterActivity() {
-    // Existing channel for keystore operations.
+class MainActivity : FlutterActivity() {
     private val KEYSTORE_CHANNEL = "com.example.keystore"
-    // New channel for call log access.
     private val CALLLOG_CHANNEL = "com.example.calllog"
-    
     private val CALL_CHANNEL = "call_service"
+    private val TAG = "MainActivity"
+    private val REQUEST_CODE_SET_DEFAULT_DIALER = 1001
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        Log.d(TAG, "onCreate started")
+        Log.d(TAG, "Waiting for Flutter to signal permissions")
+    }
 
     override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
         super.configureFlutterEngine(flutterEngine)
+        Log.d(TAG, "Configuring Flutter engine")
 
-        // Call service channel
-        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL).setMethodCallHandler { call, result ->
-            when (call.method) {
-                "makeGsmCall" -> {
-                    val phoneNumber = call.argument<String>("phoneNumber")
-                    if (phoneNumber != null) {
-                        CallService.makeGsmCall(this, phoneNumber)
-                        result.success("Calling $phoneNumber")
-                    } else {
-                        result.error("INVALID_PHONE_NUMBER", "Phone number is required", null)
-                    }
-                }
-                "hangUpCall" -> {
-                    CallService.hangUpCall(this)
-                    result.success("Call ended")
-                }
-                else -> result.notImplemented()
-            }
-        }
-
-        // Set up the keystore channel.
-        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, KEYSTORE_CHANNEL)
+        MyInCallService.channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL)
+        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL)
             .setMethodCallHandler { call, result ->
-                // Delegate method calls to KeystoreHelper.
-                KeystoreHelper(call, result).handleMethodCall()
+                when (call.method) {
+                    "permissionsGranted" -> {
+                        Log.d(TAG, "Received permissionsGranted from Flutter")
+                        checkAndRequestDefaultDialer()
+                        result.success(true)
+                    }
+                    "makeGsmCall" -> {
+                        val phoneNumber = call.argument<String>("phoneNumber")
+                        if (phoneNumber != null) {
+                            val success = CallService.makeGsmCall(this, phoneNumber)
+                            if (success) {
+                                result.success(mapOf("status" to "calling", "phoneNumber" to phoneNumber))
+                            } else {
+                                result.error("CALL_FAILED", "Failed to initiate call", null)
+                            }
+                        } else {
+                            result.error("INVALID_PHONE_NUMBER", "Phone number is required", null)
+                        }
+                    }
+                    "hangUpCall" -> {
+                        val success = CallService.hangUpCall(this)
+                        if (success) {
+                            result.success(mapOf("status" to "ended"))
+                        } else {
+                            result.error("HANGUP_FAILED", "Failed to end call", null)
+                        }
+                    }
+                    "answerCall" -> {
+                        val success = MyInCallService.currentCall?.let {
+                            it.answer(0) // 0 for default video state (audio-only)
+                            Log.d(TAG, "Answered call")
+                            true
+                        } ?: false
+                        if (success) {
+                            result.success(mapOf("status" to "answered"))
+                        } else {
+                            result.error("ANSWER_FAILED", "No active call to answer", null)
+                        }
+                    }
+                    else -> result.notImplemented()
+                }
             }
 
-        // Set up the call log channel.
+        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, KEYSTORE_CHANNEL)
+            .setMethodCallHandler { call, result -> KeystoreHelper(call, result).handleMethodCall() }
+
         MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALLLOG_CHANNEL)
             .setMethodCallHandler { call, result ->
                 if (call.method == "getCallLogs") {
@@ -60,35 +97,78 @@ class MainActivity: FlutterActivity() {
             }
     }
 
-    /**
-     * Queries the Android call log and returns a list of maps.
-     * Each map contains keys: "number", "type", "date", and "duration".
-     */
+    private fun checkAndRequestDefaultDialer() {
+        val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
+        val currentDefault = telecomManager.defaultDialerPackage
+        Log.d(TAG, "Current default dialer: $currentDefault, My package: $packageName")
+
+        if (currentDefault != packageName) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
+                if (roleManager.isRoleAvailable(RoleManager.ROLE_DIALER) && !roleManager.isRoleHeld(RoleManager.ROLE_DIALER)) {
+                    val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER)
+                    startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER)
+                    Log.d(TAG, "Launched RoleManager intent for default dialer on API 29+")
+                } else {
+                    Log.d(TAG, "RoleManager: Available=${roleManager.isRoleAvailable(RoleManager.ROLE_DIALER)}, Held=${roleManager.isRoleHeld(RoleManager.ROLE_DIALER)}")
+                }
+            } else {
+                val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
+                    .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                try {
+                    startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER)
+                    Log.d(TAG, "Launched TelecomManager intent for default dialer")
+                } catch (e: Exception) {
+                    Log.e(TAG, "Failed to launch default dialer prompt: ${e.message}", e)
+                    launchDefaultAppsSettings()
+                }
+            }
+        } else {
+            Log.d(TAG, "Already the default dialer")
+        }
+    }
+
+    private fun launchDefaultAppsSettings() {
+        val settingsIntent = Intent(android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+        startActivity(settingsIntent)
+        Log.d(TAG, "Opened default apps settings as fallback")
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode, data=$data")
+        if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER) {
+            if (resultCode == RESULT_OK) {
+                Log.d(TAG, "User accepted default dialer change")
+            } else {
+                Log.w(TAG, "Default dialer prompt canceled or failed (resultCode=$resultCode)")
+                launchDefaultAppsSettings()
+            }
+        }
+    }
+
     private fun getCallLogs(): List<Map<String, Any?>> {
         val logsList = mutableListOf<Map<String, Any?>>()
         val cursor: Cursor? = contentResolver.query(
-            CallLog.Calls.CONTENT_URI,
-            null,
-            null,
-            null,
-            CallLog.Calls.DATE + " DESC"
+            CallLog.Calls.CONTENT_URI, null, null, null, CallLog.Calls.DATE + " DESC"
         )
-        if (cursor != null) {
-            while (cursor.moveToNext()) {
-                val number = cursor.getString(cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER))
-                val type = cursor.getInt(cursor.getColumnIndexOrThrow(CallLog.Calls.TYPE))
-                val date = cursor.getLong(cursor.getColumnIndexOrThrow(CallLog.Calls.DATE))
-                val duration = cursor.getLong(cursor.getColumnIndexOrThrow(CallLog.Calls.DURATION))
+        cursor?.use {
+            while (it.moveToNext()) {
+                val number = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.NUMBER))
+                val type = it.getInt(it.getColumnIndexOrThrow(CallLog.Calls.TYPE))
+                val date = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DATE))
+                val duration = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DURATION))
 
-                val map = HashMap<String, Any?>()
-                map["number"] = number
-                map["type"] = type  // Typically: 1 for incoming, 2 for outgoing, 3 for missed.
-                map["date"] = date
-                map["duration"] = duration
+                val map = mutableMapOf<String, Any?>(
+                    "number" to number,
+                    "type" to type,
+                    "date" to date,
+                    "duration" to duration
+                )
                 logsList.add(map)
             }
-            cursor.close()
         }
         return logsList
     }
-}
+}
\ No newline at end of file
diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallConnectionService.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallConnectionService.kt
new file mode 100644
index 0000000..c39d608
--- /dev/null
+++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallConnectionService.kt
@@ -0,0 +1,82 @@
+// package com.icing.dialer.services
+
+// import android.telecom.Connection
+// import android.telecom.ConnectionService
+// import android.telecom.PhoneAccountHandle
+// import android.telecom.TelecomManager
+// import android.telecom.DisconnectCause
+// import android.util.Log
+// import io.flutter.plugin.common.MethodChannel
+
+// class CallConnectionService : ConnectionService() {
+//     companion object {
+//         var channel: MethodChannel? = null
+//         private const val TAG = "CallConnectionService"
+//     }
+
+//     init {
+//         Log.d(TAG, "CallConnectionService initialized")
+//     }
+
+//     override fun onCreate() {
+//         super.onCreate()
+//         Log.d(TAG, "Service created")
+//     }
+
+//     override fun onDestroy() {
+//         super.onDestroy()
+//         Log.d(TAG, "Service destroyed")
+//     }
+
+//     override fun onCreateOutgoingConnection(
+//         connectionManagerPhoneAccount: PhoneAccountHandle?,
+//         request: android.telecom.ConnectionRequest
+//     ): Connection {
+//         Log.d(TAG, "Creating outgoing connection for ${request.address}, account: $connectionManagerPhoneAccount")
+//         val connection = object : Connection() {
+//             override fun onStateChanged(state: Int) {
+//                 super.onStateChanged(state)
+//                 Log.d(TAG, "Connection state changed: $state")
+//                 val stateStr = when (state) {
+//                     STATE_DIALING -> "dialing"
+//                     STATE_ACTIVE -> "active"
+//                     STATE_DISCONNECTED -> "disconnected"
+//                     else -> "unknown"
+//                 }
+//                 channel?.invokeMethod("callStateChanged", mapOf("state" to stateStr, "phoneNumber" to request.address.toString()))
+//             }
+
+//             override fun onDisconnect() {
+//                 Log.d(TAG, "Connection disconnected")
+//                 setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
+//                 destroy()
+//             }
+//         }
+//         connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED)
+//         connection.setInitialized()
+//         connection.setDialing()
+//         return connection
+//     }
+
+//     override fun onCreateIncomingConnection(
+//         connectionManagerPhoneAccount: PhoneAccountHandle?,
+//         request: android.telecom.ConnectionRequest
+//     ): Connection {
+//         Log.d(TAG, "Creating incoming connection for ${request.address}, account: $connectionManagerPhoneAccount")
+//         val connection = object : Connection() {
+//             override fun onAnswer() {
+//                 Log.d(TAG, "Connection answered")
+//                 setActive()
+//             }
+
+//             override fun onDisconnect() {
+//                 Log.d(TAG, "Connection disconnected")
+//                 setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
+//                 destroy()
+//             }
+//         }
+//         connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED)
+//         connection.setRinging()
+//         return connection
+//     }
+// }
\ No newline at end of file
diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt
index e0016dc..7958799 100644
--- a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt
+++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt
@@ -1,30 +1,55 @@
 package com.icing.dialer.services
 
 import android.content.Context
-import android.content.Intent
 import android.net.Uri
-import android.telecom.TelecomManager
 import android.os.Build
+import android.os.Bundle
+import android.telecom.TelecomManager
 import android.util.Log
+import androidx.core.content.ContextCompat
+import android.content.pm.PackageManager
+import android.Manifest
 
 object CallService {
-    
-    fun makeGsmCall(context: Context, phoneNumber: String) {
-        try {
-            val intent = Intent(Intent.ACTION_CALL)
-            intent.data = Uri.parse("tel:$phoneNumber")
-            context.startActivity(intent)
+    private val TAG = "CallService"
+
+    fun makeGsmCall(context: Context, phoneNumber: String): Boolean {
+        return try {
+            val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+            val uri = Uri.parse("tel:$phoneNumber")
+
+            if (ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
+                telecomManager.placeCall(uri, Bundle())
+                Log.d(TAG, "Initiated call to $phoneNumber")
+                true
+            } else {
+                Log.e(TAG, "CALL_PHONE permission not granted")
+                false
+            }
         } catch (e: Exception) {
-            Log.e("CallService", "Error making GSM call: ${e.message}")
+            Log.e(TAG, "Error making GSM call: ${e.message}", e)
+            false
         }
     }
 
-    fun hangUpCall(context: Context) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-            val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
-            telecomManager.endCall()
-        } else {
-            Log.e("CallService", "Hangup call is only supported on Android P or later.")
+    fun hangUpCall(context: Context): Boolean {
+        return try {
+            if (MyInCallService.currentCall != null) {
+                MyInCallService.currentCall?.disconnect()
+                Log.d(TAG, "Disconnected active call via MyInCallService")
+                true
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+                telecomManager.endCall()
+                Log.d(TAG, "Ended call via TelecomManager (no active call in MyInCallService)")
+                true
+            } else {
+                Log.e(TAG, "No active call and hangup not supported below Android P")
+                false
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Error hanging up call: ${e.message}", e)
+            false
         }
     }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..48b7edd
--- /dev/null
+++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt
@@ -0,0 +1,69 @@
+package com.icing.dialer.services
+
+import android.telecom.Call
+import android.telecom.InCallService
+import android.util.Log
+import io.flutter.plugin.common.MethodChannel
+
+class MyInCallService : InCallService() {
+    companion object {
+        var channel: MethodChannel? = null
+        var currentCall: Call? = null
+        private const val TAG = "MyInCallService"
+    }
+
+    private val callCallback = object : Call.Callback() {
+        override fun onStateChanged(call: Call, state: Int) {
+            super.onStateChanged(call, state)
+            val stateStr = when (state) {
+                Call.STATE_DIALING -> "dialing"
+                Call.STATE_ACTIVE -> "active"
+                Call.STATE_DISCONNECTED -> "disconnected"
+                Call.STATE_DISCONNECTING -> "disconnecting"
+                Call.STATE_RINGING -> "ringing"
+                else -> "unknown"
+            }
+            Log.d(TAG, "State changed: $stateStr for call ${call.details.handle}")
+            channel?.invokeMethod("callStateChanged", mapOf(
+                "callId" to call.details.handle.toString(),
+                "state" to stateStr
+            ))
+            if (state == Call.STATE_DISCONNECTED || state == Call.STATE_DISCONNECTING) {
+                Log.d(TAG, "Call ended: ${call.details.handle}")
+                channel?.invokeMethod("callEnded", mapOf("callId" to call.details.handle.toString()))
+                currentCall = null
+            }
+        }
+    }
+
+    override fun onCallAdded(call: Call) {
+        super.onCallAdded(call)
+        currentCall = call
+        val stateStr = when (call.state) {
+            Call.STATE_DIALING -> "dialing"
+            Call.STATE_ACTIVE -> "active"
+            Call.STATE_RINGING -> "ringing"
+            else -> "dialing" // Default for outgoing
+        }
+        Log.d(TAG, "Call added: ${call.details.handle}, state: $stateStr")
+        channel?.invokeMethod("callAdded", mapOf(
+            "callId" to call.details.handle.toString(),
+            "state" to stateStr
+        ))
+        call.registerCallback(callCallback)
+    }
+
+    override fun onCallRemoved(call: Call) {
+        super.onCallRemoved(call)
+        Log.d(TAG, "Call removed: ${call.details.handle}")
+        call.unregisterCallback(callCallback)
+        channel?.invokeMethod("callRemoved", mapOf("callId" to call.details.handle.toString()))
+        currentCall = null
+    }
+
+    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))
+    }
+}
\ No newline at end of file
diff --git a/dialer/lib/features/call/call_page.dart b/dialer/lib/features/call/call_page.dart
index e8edf82..1416a98 100644
--- a/dialer/lib/features/call/call_page.dart
+++ b/dialer/lib/features/call/call_page.dart
@@ -1,13 +1,20 @@
 import 'dart:typed_data';
 import 'package:flutter/material.dart';
+import 'package:dialer/services/call_service.dart';
 import 'package:dialer/services/obfuscate_service.dart';
 import 'package:dialer/widgets/username_color_generator.dart';
 
 class CallPage extends StatefulWidget {
   final String displayName;
+  final String phoneNumber;
   final Uint8List? thumbnail;
 
-  const CallPage({super.key, required this.displayName, this.thumbnail});
+  const CallPage({
+    super.key,
+    required this.displayName,
+    required this.phoneNumber,
+    this.thumbnail,
+  });
 
   @override
   _CallPageState createState() => _CallPageState();
@@ -15,11 +22,12 @@ class CallPage extends StatefulWidget {
 
 class _CallPageState extends State<CallPage> {
   final ObfuscateService _obfuscateService = ObfuscateService();
+  final CallService _callService = CallService();
   bool isMuted = false;
   bool isSpeakerOn = false;
   bool isKeypadVisible = false;
   bool icingProtocolOk = true;
-  String _typedDigits = ""; // New state variable for pressed digits
+  String _typedDigits = "";
 
   void _addDigit(String digit) {
     setState(() {
@@ -51,15 +59,19 @@ class _CallPageState extends State<CallPage> {
     });
   }
 
-  void _hangUp() {
-    Navigator.pop(context);
+  void _hangUp() async {
+    try {
+      await _callService.hangUpCall(context);
+    } catch (e) {
+      print("Error hanging up: $e");
+    }
   }
 
   @override
   Widget build(BuildContext context) {
-    final double avatarRadius = isKeypadVisible ? 45.0 : 45.0; // Smaller avatar
-    final double nameFontSize = isKeypadVisible ? 24.0 : 24.0; // Smaller font
-    final double statusFontSize = isKeypadVisible ? 16.0 : 16.0; // Smaller status
+    final double avatarRadius = isKeypadVisible ? 45.0 : 45.0;
+    final double nameFontSize = isKeypadVisible ? 24.0 : 24.0;
+    final double statusFontSize = isKeypadVisible ? 16.0 : 16.0;
 
     return Scaffold(
       body: Container(
@@ -67,7 +79,6 @@ class _CallPageState extends State<CallPage> {
         child: SafeArea(
           child: Column(
             children: [
-              // Top section - make it more compact
               Container(
                 padding: const EdgeInsets.symmetric(vertical: 8.0),
                 child: Column(
@@ -75,7 +86,7 @@ class _CallPageState extends State<CallPage> {
                   children: [
                     SizedBox(height: 35),
                     ObfuscatedAvatar(
-                      imageBytes: widget.thumbnail,
+                      imageBytes: widget.thumbnail, // Uses thumbnail if provided
                       radius: avatarRadius,
                       backgroundColor: generateColorFromName(widget.displayName),
                       fallbackInitial: widget.displayName,
@@ -109,6 +120,10 @@ class _CallPageState extends State<CallPage> {
                         fontWeight: FontWeight.bold,
                       ),
                     ),
+                    Text(
+                      widget.phoneNumber,
+                      style: TextStyle(fontSize: statusFontSize, color: Colors.white70),
+                    ),
                     Text(
                       'Calling...',
                       style: TextStyle(fontSize: statusFontSize, color: Colors.white70),
@@ -116,16 +131,11 @@ class _CallPageState extends State<CallPage> {
                   ],
                 ),
               ),
-
-              // Middle section - make it flexible and scrollable if needed
               Expanded(
                 child: Column(
                   children: [
                     if (isKeypadVisible) ...[
-                      // Add spacer to push keypad down
                       const Spacer(flex: 2),
-                      
-                      // Typed digits display
                       Padding(
                         padding: const EdgeInsets.symmetric(horizontal: 20.0),
                         child: Row(
@@ -152,8 +162,6 @@ class _CallPageState extends State<CallPage> {
                           ],
                         ),
                       ),
-                      
-                      // Keypad grid
                       Container(
                         height: MediaQuery.of(context).size.height * 0.35,
                         margin: const EdgeInsets.symmetric(horizontal: 20),
@@ -193,22 +201,17 @@ class _CallPageState extends State<CallPage> {
                           }),
                         ),
                       ),
-                      
-                      // Add spacer after keypad
                       const Spacer(flex: 1),
                     ] else ...[
                       const Spacer(),
-                      // Control buttons
                       Padding(
                         padding: const EdgeInsets.symmetric(horizontal: 32.0),
                         child: Column(
                           mainAxisSize: MainAxisSize.min,
                           children: [
-                            // Main control buttons
                             Row(
                               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                               children: [
-                                // Mute
                                 Column(
                                   mainAxisSize: MainAxisSize.min,
                                   children: [
@@ -226,7 +229,6 @@ class _CallPageState extends State<CallPage> {
                                     ),
                                   ],
                                 ),
-                                // Keypad
                                 Column(
                                   mainAxisSize: MainAxisSize.min,
                                   children: [
@@ -240,7 +242,6 @@ class _CallPageState extends State<CallPage> {
                                     ),
                                   ],
                                 ),
-                                // Speaker
                                 Column(
                                   mainAxisSize: MainAxisSize.min,
                                   children: [
@@ -261,32 +262,25 @@ class _CallPageState extends State<CallPage> {
                               ],
                             ),
                             const SizedBox(height: 20),
-                            // Additional buttons
                             Row(
                               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                               children: [
-                                // Add Contact
                                 Column(
                                   mainAxisSize: MainAxisSize.min,
                                   children: [
                                     IconButton(
-                                      onPressed: () {
-                                        // ...existing code...
-                                      },
+                                      onPressed: () {},
                                       icon: const Icon(Icons.person_add, color: Colors.white, size: 32),
                                     ),
                                     const Text('Add Contact',
                                         style: TextStyle(color: Colors.white, fontSize: 14)),
                                   ],
                                 ),
-                                // Change SIM
                                 Column(
                                   mainAxisSize: MainAxisSize.min,
                                   children: [
                                     IconButton(
-                                      onPressed: () {
-                                        // ...existing code...
-                                      },
+                                      onPressed: () {},
                                       icon: const Icon(Icons.sim_card, color: Colors.white, size: 32),
                                     ),
                                     const Text('Change SIM',
@@ -303,8 +297,6 @@ class _CallPageState extends State<CallPage> {
                   ],
                 ),
               ),
-
-              // Bottom section - hang up button
               Padding(
                 padding: const EdgeInsets.only(bottom: 16.0),
                 child: GestureDetector(
@@ -329,4 +321,4 @@ class _CallPageState extends State<CallPage> {
       ),
     );
   }
-}
+}
\ No newline at end of file
diff --git a/dialer/lib/features/call/incoming_call_page.dart b/dialer/lib/features/call/incoming_call_page.dart
new file mode 100644
index 0000000..2bad2eb
--- /dev/null
+++ b/dialer/lib/features/call/incoming_call_page.dart
@@ -0,0 +1,181 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:dialer/services/call_service.dart';
+import 'package:dialer/services/obfuscate_service.dart';
+import 'package:dialer/widgets/username_color_generator.dart';
+import 'package:dialer/features/call/call_page.dart';
+
+class IncomingCallPage extends StatefulWidget {
+  final String displayName;
+  final String phoneNumber;
+  final Uint8List? thumbnail;
+
+  const IncomingCallPage({
+    super.key,
+    required this.displayName,
+    required this.phoneNumber,
+    this.thumbnail,
+  });
+
+  @override
+  _IncomingCallPageState createState() => _IncomingCallPageState();
+}
+
+class _IncomingCallPageState extends State<IncomingCallPage> {
+  static const MethodChannel _channel = MethodChannel('call_service');
+  final ObfuscateService _obfuscateService = ObfuscateService();
+  final CallService _callService = CallService();
+  bool icingProtocolOk = true;
+
+  void _toggleIcingProtocol() {
+    setState(() {
+      icingProtocolOk = !icingProtocolOk;
+    });
+  }
+
+  void _answerCall() async {
+    try {
+      final result = await _channel.invokeMethod('answerCall');
+      print('IncomingCallPage: Answer call result: $result');
+      if (result["status"] == "answered") {
+        Navigator.pushReplacement(
+          context,
+          MaterialPageRoute(
+            builder: (context) => CallPage(
+              displayName: widget.displayName,
+              phoneNumber: widget.phoneNumber,
+              thumbnail: widget.thumbnail,
+            ),
+          ),
+        );
+      }
+    } catch (e) {
+      print("IncomingCallPage: Error answering call: $e");
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(content: Text("Error answering call: $e")),
+      );
+    }
+  }
+
+  void _declineCall() async {
+    try {
+      await _callService.hangUpCall(context);
+    } catch (e) {
+      print("IncomingCallPage: Error declining call: $e");
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(content: Text("Error declining call: $e")),
+      );
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    const double avatarRadius = 45.0;
+    const double nameFontSize = 24.0;
+    const double statusFontSize = 16.0;
+
+    return Scaffold(
+      body: Container(
+        color: Colors.black,
+        child: SafeArea(
+          child: Column(
+            children: [
+              Container(
+                padding: const EdgeInsets.symmetric(vertical: 8.0),
+                child: Column(
+                  mainAxisSize: MainAxisSize.min,
+                  children: [
+                    const SizedBox(height: 35),
+                    ObfuscatedAvatar(
+                      imageBytes: widget.thumbnail,
+                      radius: avatarRadius,
+                      backgroundColor: generateColorFromName(widget.displayName),
+                      fallbackInitial: widget.displayName,
+                    ),
+                    const SizedBox(height: 4),
+                    Row(
+                      mainAxisAlignment: MainAxisAlignment.center,
+                      children: [
+                        Icon(
+                          icingProtocolOk ? Icons.lock : Icons.lock_open,
+                          color: icingProtocolOk ? Colors.green : Colors.red,
+                          size: 16,
+                        ),
+                        const SizedBox(width: 4),
+                        Text(
+                          'Icing protocol: ${icingProtocolOk ? "ok" : "ko"}',
+                          style: TextStyle(
+                            color: icingProtocolOk ? Colors.green : Colors.red,
+                            fontSize: 12,
+                            fontWeight: FontWeight.bold,
+                          ),
+                        ),
+                      ],
+                    ),
+                    const SizedBox(height: 4),
+                    Text(
+                      _obfuscateService.obfuscateData(widget.displayName),
+                      style: const TextStyle(
+                        fontSize: nameFontSize,
+                        color: Colors.white,
+                        fontWeight: FontWeight.bold,
+                      ),
+                    ),
+                    Text(
+                      widget.phoneNumber,
+                      style: const TextStyle(fontSize: statusFontSize, color: Colors.white70),
+                    ),
+                    const Text(
+                      'Incoming Call...',
+                      style: TextStyle(fontSize: statusFontSize, color: Colors.white70),
+                    ),
+                  ],
+                ),
+              ),
+              const Spacer(),
+              Padding(
+                padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0),
+                child: Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                  children: [
+                    GestureDetector(
+                      onTap: _declineCall,
+                      child: Container(
+                        padding: const EdgeInsets.all(12),
+                        decoration: const BoxDecoration(
+                          color: Colors.red,
+                          shape: BoxShape.circle,
+                        ),
+                        child: const Icon(
+                          Icons.call_end,
+                          color: Colors.white,
+                          size: 32,
+                        ),
+                      ),
+                    ),
+                    GestureDetector(
+                      onTap: _answerCall,
+                      child: Container(
+                        padding: const EdgeInsets.all(12),
+                        decoration: const BoxDecoration(
+                          color: Colors.green,
+                          shape: BoxShape.circle,
+                        ),
+                        child: const Icon(
+                          Icons.call,
+                          color: Colors.white,
+                          size: 32,
+                        ),
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+              const SizedBox(height: 16),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
\ No newline at end of file
diff --git a/dialer/lib/features/composition/composition.dart b/dialer/lib/features/composition/composition.dart
index 9bde112..b807ce4 100644
--- a/dialer/lib/features/composition/composition.dart
+++ b/dialer/lib/features/composition/composition.dart
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter_contacts/flutter_contacts.dart';
 import 'package:url_launcher/url_launcher.dart';
 import '../../services/contact_service.dart';
-import '../../services/obfuscate_service.dart'; // Import ObfuscateService
-import '../../services/call_service.dart'; // Import the CallService
+import '../../services/obfuscate_service.dart';
+import '../../services/call_service.dart';
 import '../contacts/widgets/add_contact_button.dart';
 
 class CompositionPage extends StatefulWidget {
@@ -76,7 +76,7 @@ class _CompositionPageState extends State<CompositionPage> {
   // Function to call a contact's number using the CallService
   void _makeCall(String phoneNumber) async {
     try {
-      await _callService.makeGsmCall(phoneNumber);
+      await _callService.makeGsmCall(context, phoneNumber: phoneNumber);
       setState(() {
         dialedNumber = phoneNumber;
       });
diff --git a/dialer/lib/features/contacts/widgets/contact_modal.dart b/dialer/lib/features/contacts/widgets/contact_modal.dart
index 0a6efad..198f614 100644
--- a/dialer/lib/features/contacts/widgets/contact_modal.dart
+++ b/dialer/lib/features/contacts/widgets/contact_modal.dart
@@ -5,8 +5,7 @@ import 'package:url_launcher/url_launcher.dart';
 import 'package:dialer/widgets/username_color_generator.dart';
 import '../../../services/block_service.dart';
 import '../../../services/contact_service.dart';
-import '../../../features/call/call_page.dart';
-import '../../../services/call_service.dart'; // Import CallService
+import '../../../services/call_service.dart';
 
 class ContactModal extends StatefulWidget {
   final Contact contact;
@@ -30,7 +29,7 @@ class _ContactModalState extends State<ContactModal> {
   late String phoneNumber;
   bool isBlocked = false;
   final ObfuscateService _obfuscateService = ObfuscateService();
-  final CallService _callService = CallService(); // Instantiate CallService
+  final CallService _callService = CallService();
 
   @override
   void initState() {
@@ -127,7 +126,9 @@ class _ContactModalState extends State<ContactModal> {
 
         // Show success message
         ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text('${_obfuscateService.obfuscateData(widget.contact.displayName)} deleted')),
+          SnackBar(
+              content: Text(
+                  '${_obfuscateService.obfuscateData(widget.contact.displayName)} deleted')),
         );
 
         // Close the modal
@@ -135,7 +136,9 @@ class _ContactModalState extends State<ContactModal> {
       } catch (e) {
         // Handle errors and show a failure message
         ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text('Failed to delete ${widget.contact.displayName}: $e')),
+          SnackBar(
+              content:
+                  Text('Failed to delete ${widget.contact.displayName}: $e')),
         );
       }
     }
@@ -163,7 +166,7 @@ class _ContactModalState extends State<ContactModal> {
               decoration: BoxDecoration(
                 color: Colors.grey[900],
                 borderRadius:
-                const BorderRadius.vertical(top: Radius.circular(20)),
+                    const BorderRadius.vertical(top: Radius.circular(20)),
               ),
               child: Column(
                 mainAxisSize: MainAxisSize.min,
@@ -217,7 +220,7 @@ class _ContactModalState extends State<ContactModal> {
                                 const PopupMenuItem<String>(
                                   value: 'create_shortcut',
                                   child:
-                                  Text('Create shortcut (to home screen)'),
+                                      Text('Create shortcut (to home screen)'),
                                 ),
                                 const PopupMenuItem<String>(
                                   value: 'set_ringtone',
@@ -239,12 +242,13 @@ class _ContactModalState extends State<ContactModal> {
                           imageBytes: widget.contact.thumbnail,
                           radius: 50,
                           backgroundColor:
-                          generateColorFromName(widget.contact.displayName),
+                              generateColorFromName(widget.contact.displayName),
                           fallbackInitial: widget.contact.displayName,
                         ),
                         const SizedBox(height: 10),
                         Text(
-                          _obfuscateService.obfuscateData(widget.contact.displayName),
+                          _obfuscateService
+                              .obfuscateData(widget.contact.displayName),
                           style: const TextStyle(
                               fontSize: 24,
                               fontWeight: FontWeight.bold,
@@ -263,21 +267,10 @@ class _ContactModalState extends State<ContactModal> {
                     ),
                     onTap: () async {
                       if (widget.contact.phones.isNotEmpty) {
-                        await _callService.makeGsmCall(phoneNumber);
+                        await _callService.makeGsmCall(context,
+                            phoneNumber: phoneNumber);
                       }
                     },
-                    onLongPress: () {
-                      // Navigate to the beautiful calling page demo
-                      Navigator.push(
-                        context,
-                        MaterialPageRoute(
-                          builder: (_) => CallPage(
-                            displayName: widget.contact.displayName,
-                            thumbnail: widget.contact.thumbnail,
-                          ),
-                        ),
-                      );
-                    },
                   ),
                   ListTile(
                     leading: const Icon(Icons.message, color: Colors.blue),
@@ -320,9 +313,8 @@ class _ContactModalState extends State<ContactModal> {
                             icon: Icon(widget.isFavorite
                                 ? Icons.star
                                 : Icons.star_border),
-                            label: Text(widget.isFavorite
-                                ? 'Unfavorite'
-                                : 'Favorite'),
+                            label: Text(
+                                widget.isFavorite ? 'Unfavorite' : 'Favorite'),
                           ),
                         ),
                         const SizedBox(height: 10),
diff --git a/dialer/lib/features/history/history_page.dart b/dialer/lib/features/history/history_page.dart
index 117d1e8..2ce20b8 100644
--- a/dialer/lib/features/history/history_page.dart
+++ b/dialer/lib/features/history/history_page.dart
@@ -419,7 +419,7 @@ class _HistoryPageState extends State<HistoryPage>
                       icon: const Icon(Icons.phone, color: Colors.green),
                       onPressed: () async {
                         if (contact.phones.isNotEmpty) {
-                          _callService.makeGsmCall(contact.phones.first.number);
+                          _callService.makeGsmCall(context, phoneNumber: contact.phones.first.number);
                         } else {
                           ScaffoldMessenger.of(context).showSnackBar(
                             const SnackBar(
diff --git a/dialer/lib/main.dart b/dialer/lib/main.dart
index afde3b9..d3513cf 100644
--- a/dialer/lib/main.dart
+++ b/dialer/lib/main.dart
@@ -1,8 +1,11 @@
 import 'package:dialer/features/home/home_page.dart';
 import 'package:flutter/material.dart';
 import 'package:dialer/features/contacts/contact_state.dart';
+import 'package:dialer/services/call_service.dart';
+import 'package:flutter/services.dart';
 import 'globals.dart' as globals;
 import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart';
+import 'package:permission_handler/permission_handler.dart';
 import 'package:provider/provider.dart';
 
 void main() async {
@@ -13,19 +16,38 @@ void main() async {
   final AsymmetricCryptoService cryptoService = AsymmetricCryptoService();
   await cryptoService.initializeDefaultKeyPair();
 
+  // Request permissions before running the app
+  await _requestPermissions();
+
+  CallService();
+
   runApp(
     MultiProvider(
       providers: [
         Provider<AsymmetricCryptoService>(
           create: (_) => cryptoService,
         ),
-        // Add other providers here
       ],
       child: Dialer(),
     ),
   );
 }
 
+Future<void> _requestPermissions() async {
+  Map<Permission, PermissionStatus> statuses = await [
+    Permission.phone,
+    Permission.contacts,
+    Permission.microphone,
+  ].request();
+  if (statuses.values.every((status) => status.isGranted)) {
+    print("All required permissions granted");
+    const channel = MethodChannel('call_service');
+    await channel.invokeMethod('permissionsGranted');
+  } else {
+    print("Permissions denied: ${statuses.entries.where((e) => !e.value.isGranted).map((e) => e.key).join(', ')}");
+  }
+}
+
 class Dialer extends StatelessWidget {
   const Dialer({super.key});
 
@@ -33,11 +55,12 @@ class Dialer extends StatelessWidget {
   Widget build(BuildContext context) {
     return ContactState(
       child: MaterialApp(
+        navigatorKey: CallService.navigatorKey,
         theme: ThemeData(
-          brightness: Brightness.dark
+          brightness: Brightness.dark,
         ),
         home: SafeArea(child: MyHomePage()),
-      )
+      ),
     );
   }
-}
+}
\ No newline at end of file
diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart
index c07027e..d42326e 100644
--- a/dialer/lib/services/call_service.dart
+++ b/dialer/lib/services/call_service.dart
@@ -1,26 +1,154 @@
+import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
+import '../features/call/call_page.dart';
+import '../features/call/incoming_call_page.dart'; // Import the new page
 
-// Service to manage call-related operations
 class CallService {
   static const MethodChannel _channel = MethodChannel('call_service');
+  static String? currentPhoneNumber;
+  static bool _isCallPageVisible = false;
 
-  // Function to make a GSM call
-  Future<void> makeGsmCall(String phoneNumber) async {
+  static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
+
+  CallService() {
+    _channel.setMethodCallHandler((call) async {
+      final context = navigatorKey.currentContext;
+      print('CallService: Received method ${call.method} with args ${call.arguments}');
+      if (context == null) {
+        print('CallService: Navigator context is null, cannot navigate');
+        return;
+      }
+
+      switch (call.method) {
+        case "callAdded":
+          final phoneNumber = call.arguments["callId"] as String;
+          final state = call.arguments["state"] as String;
+          currentPhoneNumber = phoneNumber.replaceFirst('tel:', '');
+          print('CallService: Call added, number: $currentPhoneNumber, state: $state');
+          if (state == "ringing") {
+            _navigateToIncomingCallPage(context);
+          } else {
+            _navigateToCallPage(context);
+          }
+          break;
+        case "callStateChanged":
+          final state = call.arguments["state"] as String;
+          print('CallService: State changed to $state');
+          if (state == "disconnected" || state == "disconnecting") {
+            _closeCallPage(context);
+          } else if (state == "active" || state == "dialing") {
+            _navigateToCallPage(context);
+          } else if (state == "ringing") {
+            _navigateToIncomingCallPage(context);
+          }
+          break;
+        case "callEnded":
+        case "callRemoved":
+          print('CallService: Call ended/removed');
+          _closeCallPage(context);
+          currentPhoneNumber = null;
+          break;
+      }
+    });
+  }
+
+  void _navigateToCallPage(BuildContext context) {
+    if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/call') {
+      print('CallService: CallPage already visible, skipping navigation');
+      return;
+    }
+    print('CallService: Navigating to CallPage');
+    Navigator.pushReplacement(
+      context,
+      MaterialPageRoute(
+        settings: const RouteSettings(name: '/call'),
+        builder: (context) => CallPage(
+          displayName: currentPhoneNumber!,
+          phoneNumber: currentPhoneNumber!,
+          thumbnail: null,
+        ),
+      ),
+    ).then((_) {
+      _isCallPageVisible = false;
+    });
+    _isCallPageVisible = true;
+  }
+
+  void _navigateToIncomingCallPage(BuildContext context) {
+    if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/incoming_call') {
+      print('CallService: IncomingCallPage already visible, skipping navigation');
+      return;
+    }
+    print('CallService: Navigating to IncomingCallPage');
+    Navigator.push(
+      context,
+      MaterialPageRoute(
+        settings: const RouteSettings(name: '/incoming_call'),
+        builder: (context) => IncomingCallPage(
+          displayName: currentPhoneNumber!,
+          phoneNumber: currentPhoneNumber!,
+          thumbnail: null,
+        ),
+      ),
+    ).then((_) {
+      _isCallPageVisible = false;
+    });
+    _isCallPageVisible = true;
+  }
+
+  void _closeCallPage(BuildContext context) {
+    if (!_isCallPageVisible) {
+      print('CallService: CallPage not visible, skipping pop');
+      return;
+    }
+    if (Navigator.canPop(context)) {
+      print('CallService: Popping CallPage');
+      Navigator.pop(context);
+      _isCallPageVisible = false;
+    }
+  }
+
+  Future<void> makeGsmCall(
+    BuildContext context, {
+    required String phoneNumber,
+    String? displayName,
+    Uint8List? thumbnail,
+  }) async {
     try {
-      await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber});
+      currentPhoneNumber = phoneNumber;
+      print('CallService: Making GSM call to $phoneNumber');
+      final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber});
+      print('CallService: makeGsmCall result: $result');
+      if (result["status"] != "calling") {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(content: Text("Failed to initiate call")),
+        );
+      }
     } catch (e) {
-      print("Error making call: $e");
+      print("CallService: Error making call: $e");
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(content: Text("Error making call: $e")),
+      );
       rethrow;
     }
   }
 
-  // Function to hang up the current call
-  Future<void> hangUpCall() async {
+  Future<void> hangUpCall(BuildContext context) async {
     try {
-      await _channel.invokeMethod('hangUpCall');
+      print('CallService: Hanging up call');
+      final result = await _channel.invokeMethod('hangUpCall');
+      print('CallService: hangUpCall result: $result');
+      if (result["status"] != "ended") {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(content: Text("Failed to end call")),
+        );
+      }
     } catch (e) {
-      print("Error hanging up call: $e");
+      print("CallService: Error hanging up call: $e");
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(content: Text("Error hanging up call: $e")),
+      );
       rethrow;
     }
   }
-}
+}
\ No newline at end of file