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 7df838e..9a8ff2e 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,14 +1,18 @@ package com.icing.dialer.activities +import android.Manifest import android.content.ComponentName import android.content.Intent +import android.content.pm.PackageManager import android.database.Cursor +import android.net.Uri import android.os.Bundle import android.provider.CallLog import android.telecom.PhoneAccount import android.telecom.PhoneAccountHandle import android.telecom.TelecomManager import android.util.Log +import androidx.core.content.ContextCompat import com.icing.dialer.KeystoreHelper import com.icing.dialer.services.CallConnectionService import com.icing.dialer.services.CallService @@ -20,69 +24,14 @@ class MainActivity : FlutterActivity() { private val KEYSTORE_CHANNEL = "com.example.keystore" private val CALLLOG_CHANNEL = "com.example.calllog" private val CALL_CHANNEL = "call_service" - private val REQUEST_CALL_PERMISSIONS = 1 private val TAG = "MainActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d(TAG, "onCreate started") - registerPhoneAccount() - checkAndRequestDefaultDialer() - Log.d(TAG, "onCreate completed") + Log.d(TAG, "Waiting for Flutter to signal permissions") } - // private fun checkPermissions(): Boolean { - // val permissions = - // arrayOf( - // Manifest.permission.CALL_PHONE, - // Manifest.permission.READ_PHONE_STATE, - // Manifest.permission.MANAGE_OWN_CALLS, - // Manifest.permission.READ_CONTACTS // Add this - // ) - // return permissions - // .all { - // ContextCompat.checkSelfPermission(this, it) == - // PackageManager.PERMISSION_GRANTED - // } - // .also { Log.d(TAG, "Permissions check result: $it") } - // } - - // private fun requestPermissions() { - // ActivityCompat.requestPermissions( - // this, - // arrayOf( - // Manifest.permission.CALL_PHONE, - // Manifest.permission.READ_PHONE_STATE, - // Manifest.permission.MANAGE_OWN_CALLS, - // Manifest.permission.READ_CONTACTS // Add this - // ), - // REQUEST_CALL_PERMISSIONS - // ) - // Log.d(TAG, "Permission request dispatched") - // } - - // override fun onRequestPermissionsResult( - // requestCode: Int, - // permissions: Array, - // grantResults: IntArray - // ) { - // super.onRequestPermissionsResult(requestCode, permissions, grantResults) - // Log.d(TAG, "onRequestPermissionsResult: $requestCode, ${grantResults.joinToString()}") - // if (requestCode == REQUEST_CALL_PERMISSIONS && - // grantResults.all { it == PackageManager.PERMISSION_GRANTED } - // ) { - // Log.d(TAG, "All permissions granted") - // registerPhoneAccount() - // checkAndRequestDefaultDialer() - // } else { - // Log.e( - // TAG, - // "Required permissions not granted: ${permissions.joinToString()}, - // ${grantResults.joinToString()}" - // ) - // } - // } - private fun registerPhoneAccount() { val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager val phoneAccountHandle = @@ -90,6 +39,7 @@ class MainActivity : FlutterActivity() { ComponentName(this, CallConnectionService::class.java), "IcingDialerAccount" ) + Log.d(TAG, "PhoneAccountHandle component: ${phoneAccountHandle.componentName}") val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Icing Dialer") .setCapabilities( @@ -100,18 +50,46 @@ class MainActivity : FlutterActivity() { telecomManager.registerPhoneAccount(phoneAccount) CallService.setPhoneAccountHandle(phoneAccountHandle) Log.d(TAG, "PhoneAccount registered: ${phoneAccountHandle.id}") - Log.d( - TAG, - "Registered PhoneAccounts: ${telecomManager.callCapablePhoneAccounts.joinToString()}" - ) + + val registeredAccounts = telecomManager.callCapablePhoneAccounts + Log.d(TAG, "Registered PhoneAccounts: ${registeredAccounts.joinToString()}") + if (!registeredAccounts.contains(phoneAccountHandle)) { + Log.w(TAG, "PhoneAccount not found in callCapablePhoneAccounts") + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == + PackageManager.PERMISSION_GRANTED + ) { + val uri = Uri.parse("tel:1234567890") + val extras = + Bundle().apply { + putParcelable( + TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, + phoneAccountHandle + ) + } + telecomManager.placeCall(uri, extras) + Log.d(TAG, "Triggered dummy call to bind CallConnectionService") + } else { + Log.w(TAG, "CALL_PHONE permission not granted, cannot test binding") + } + } else { + Log.d(TAG, "PhoneAccount successfully found in callCapablePhoneAccounts") + } } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - + Log.d(TAG, "Configuring Flutter engine") + CallConnectionService.channel = + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { + "permissionsGranted" -> { + Log.d(TAG, "Received permissionsGranted from Flutter") + registerPhoneAccount() + checkAndRequestDefaultDialer() + result.success(true) + } "makeGsmCall" -> { val phoneNumber = call.argument("phoneNumber") if (phoneNumber != null) { @@ -173,25 +151,34 @@ class MainActivity : FlutterActivity() { TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName ) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) try { - startActivityForResult(intent, 1001) // Use startActivityForResult to track response + startActivityForResult(intent, 1001) Log.d(TAG, "Default dialer prompt launched with requestCode 1001") } 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") + Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode, data=$data") if (requestCode == 1001) { if (resultCode == RESULT_OK) { Log.d(TAG, "User accepted default dialer change") } else { - Log.d(TAG, "User rejected or canceled default dialer change") + Log.d(TAG, "Default dialer prompt canceled (resultCode=$resultCode)") + launchDefaultAppsSettings() } } } 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 index ea5e1f0..53d73b2 100644 --- 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 @@ -5,24 +5,38 @@ import android.telecom.ConnectionService import android.telecom.PhoneAccountHandle import android.telecom.TelecomManager import android.telecom.DisconnectCause -import android.net.Uri -import android.os.Bundle 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("CallConnectionService", "Connection state changed: $state") + Log.d(TAG, "Connection state changed: $state") val stateStr = when (state) { STATE_DIALING -> "dialing" STATE_ACTIVE -> "active" @@ -33,13 +47,14 @@ class CallConnectionService : ConnectionService() { } override fun onDisconnect() { + Log.d(TAG, "Connection disconnected") setDisconnected(DisconnectCause(DisconnectCause.LOCAL)) destroy() } } connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED) connection.setInitialized() - connection.setDialing() // Start in dialing state + connection.setDialing() return connection } @@ -47,12 +62,15 @@ class CallConnectionService : ConnectionService() { 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() } diff --git a/dialer/lib/main.dart b/dialer/lib/main.dart index 4aa2170..80751b2 100644 --- a/dialer/lib/main.dart +++ b/dialer/lib/main.dart @@ -2,6 +2,7 @@ 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'; @@ -39,6 +40,9 @@ Future _requestPermissions() async { ].request(); if (statuses.values.every((status) => status.isGranted)) { print("All required permissions granted"); + // Signal MainActivity + 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(', ')}"); }