feat: open dialer on top of locked screen when incoming call

This commit is contained in:
Florian Griffon 2025-04-08 16:10:54 +03:00 committed by stcb
parent 16d759bc4e
commit ffbd7bdfaa
4 changed files with 37 additions and 14 deletions

View File

@ -28,7 +28,9 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues

View File

@ -9,6 +9,8 @@ import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.CallLog import android.provider.CallLog
import android.telecom.TelecomManager import android.telecom.TelecomManager
import android.util.Log import android.util.Log
@ -27,6 +29,7 @@ class MainActivity : FlutterActivity() {
private val TAG = "MainActivity" private val TAG = "MainActivity"
private val REQUEST_CODE_SET_DEFAULT_DIALER = 1001 private val REQUEST_CODE_SET_DEFAULT_DIALER = 1001
private val REQUEST_CODE_CALL_LOG_PERMISSION = 1002 private val REQUEST_CODE_CALL_LOG_PERMISSION = 1002
private var pendingIncomingCall: Pair<String?, Boolean>? = null // Store incoming call data
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -57,12 +60,10 @@ class MainActivity : FlutterActivity() {
"makeGsmCall" -> { "makeGsmCall" -> {
val phoneNumber = call.argument<String>("phoneNumber") val phoneNumber = call.argument<String>("phoneNumber")
if (phoneNumber != null) { if (phoneNumber != null) {
try { val success = CallService.makeGsmCall(this, phoneNumber) // Use CallService
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phoneNumber")) if (success) {
startActivity(intent)
result.success(mapOf("status" to "calling", "phoneNumber" to phoneNumber)) result.success(mapOf("status" to "calling", "phoneNumber" to phoneNumber))
} catch (e: Exception) { } else {
Log.e(TAG, "Failed to make GSM call: ${e.message}")
result.error("CALL_FAILED", "Failed to initiate call", null) result.error("CALL_FAILED", "Failed to initiate call", null)
} }
} else { } else {
@ -98,6 +99,18 @@ class MainActivity : FlutterActivity() {
result.error("ANSWER_FAILED", "No active call to answer", null) result.error("ANSWER_FAILED", "No active call to answer", null)
} }
} }
"flutterReady" -> { // New method to signal Flutter is initialized
Log.d(TAG, "Flutter is ready")
pendingIncomingCall?.let { (phoneNumber, showScreen) ->
if (showScreen) {
MyInCallService.channel?.invokeMethod("incomingCallFromNotification", mapOf(
"phoneNumber" to phoneNumber
))
pendingIncomingCall = null // Clear after handling
}
}
result.success(true)
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
@ -217,9 +230,15 @@ class MainActivity : FlutterActivity() {
val showScreen = it.getBooleanExtra("showIncomingCallScreen", false) val showScreen = it.getBooleanExtra("showIncomingCallScreen", false)
Log.d(TAG, "Received incoming call intent for $phoneNumber, showScreen=$showScreen") Log.d(TAG, "Received incoming call intent for $phoneNumber, showScreen=$showScreen")
if (showScreen) { if (showScreen) {
if (MyInCallService.channel != null) {
MyInCallService.channel?.invokeMethod("incomingCallFromNotification", mapOf( MyInCallService.channel?.invokeMethod("incomingCallFromNotification", mapOf(
"phoneNumber" to phoneNumber "phoneNumber" to phoneNumber
)) ))
} else {
// Store the intent data if Flutter isn't ready yet
pendingIncomingCall = Pair(phoneNumber, true)
Log.d(TAG, "Flutter channel not ready, storing pending call: $phoneNumber")
}
} }
} }
} }

View File

@ -85,17 +85,15 @@ class MyInCallService : InCallService() {
} }
private fun showIncomingCallScreen(phoneNumber: String) { private fun showIncomingCallScreen(phoneNumber: String) {
// Launch MainActivity with intent to show IncomingCallPage
val intent = Intent(this, MainActivity::class.java).apply { val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
putExtra("phoneNumber", phoneNumber) putExtra("phoneNumber", phoneNumber)
putExtra("isIncomingCall", true) putExtra("isIncomingCall", true)
putExtra("showIncomingCallScreen", true) // New flag to signal immediate UI putExtra("showIncomingCallScreen", true)
} }
startActivity(intent) startActivity(intent)
Log.d(TAG, "Launched MainActivity to show incoming call screen for $phoneNumber") Log.d(TAG, "Launched MainActivity to show incoming call screen for $phoneNumber")
// Optional: Keep notification as a fallback
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
@ -116,7 +114,7 @@ class MyInCallService : InCallService() {
) )
val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_alert) // Replace with your app icon .setSmallIcon(android.R.drawable.ic_dialog_alert)
.setContentTitle("Incoming Call") .setContentTitle("Incoming Call")
.setContentText("Call from $phoneNumber") .setContentText("Call from $phoneNumber")
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)

View File

@ -55,6 +55,10 @@ class CallService {
break; break;
} }
}); });
// Signal Flutter is ready
WidgetsFlutterBinding.ensureInitialized();
_channel.invokeMethod("flutterReady");
} }
void _navigateToCallPage(BuildContext context) { void _navigateToCallPage(BuildContext context) {