From 24dc5a9bbe27d95cbf6fac66831189b54cc0489c Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 4 Mar 2025 18:43:48 +0100 Subject: [PATCH] feat: update flutter UI via methodchannel, permissions via flutter at startup --- .../android/app/src/main/AndroidManifest.xml | 3 +- .../icing/dialer/activities/MainActivity.kt | 232 +++++++++++++----- .../dialer/services/CallConnectionService.kt | 15 +- 3 files changed, 184 insertions(+), 66 deletions(-) diff --git a/dialer/android/app/src/main/AndroidManifest.xml b/dialer/android/app/src/main/AndroidManifest.xml index cc51efb..43eb3c3 100644 --- a/dialer/android/app/src/main/AndroidManifest.xml +++ b/dialer/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + @@ -48,7 +50,6 @@ - , + // 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 = PhoneAccountHandle( - ComponentName(this, CallConnectionService::class.java), - "IcingDialerAccount" - ) - val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Icing Dialer") - .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) - .build() + val phoneAccountHandle = + PhoneAccountHandle( + ComponentName(this, CallConnectionService::class.java), + "IcingDialerAccount" + ) + val phoneAccount = + PhoneAccount.builder(phoneAccountHandle, "Icing Dialer") + .setCapabilities( + PhoneAccount.CAPABILITY_CALL_PROVIDER or + PhoneAccount.CAPABILITY_CONNECTION_MANAGER + ) + .build() telecomManager.registerPhoneAccount(phoneAccount) + CallService.setPhoneAccountHandle(phoneAccountHandle) + Log.d(TAG, "PhoneAccount registered: ${phoneAccountHandle.id}") + Log.d( + TAG, + "Registered PhoneAccounts: ${telecomManager.callCapablePhoneAccounts.joinToString()}" + ) } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL).setMethodCallHandler { call, result -> - when (call.method) { - "makeGsmCall" -> { - val phoneNumber = call.argument("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) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL) + .setMethodCallHandler { call, result -> + when (call.method) { + "makeGsmCall" -> { + val phoneNumber = call.argument("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 + ) + } } - } 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) + } + } + else -> result.notImplemented() } } - "hangUpCall" -> { - val success = CallService.hangUpCall(this) - if (success) { - result.success(mapOf("status" to "ended")) - } else { - result.error("HANGUP_FAILED", "Failed to end call", null) - } - } - else -> result.notImplemented() - } - } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, KEYSTORE_CHANNEL) - .setMethodCallHandler { call, result -> - KeystoreHelper(call, result).handleMethodCall() - } + .setMethodCallHandler { call, result -> + KeystoreHelper(call, result).handleMethodCall() + } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALLLOG_CHANNEL) - .setMethodCallHandler { call, result -> - if (call.method == "getCallLogs") { - val callLogs = getCallLogs() - result.success(callLogs) - } else { - result.notImplemented() + .setMethodCallHandler { call, result -> + if (call.method == "getCallLogs") { + val callLogs = getCallLogs() + result.success(callLogs) + } else { + result.notImplemented() + } } - } } private fun checkAndRequestDefaultDialer() { val tm = getSystemService(TELECOM_SERVICE) as TelecomManager - tm.defaultDialerPackage?.let { - if (it != packageName) { - val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER) - .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName) - startActivity(intent) + val currentDefault = tm.defaultDialerPackage + Log.d(TAG, "Current default dialer: $currentDefault, My package: $packageName") + if (currentDefault != packageName) { + val intent = + Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER) + .putExtra( + TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, + packageName + ) + try { + startActivityForResult(intent, 1001) // Use startActivityForResult to track response + 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) + } + } else { + Log.d(TAG, "Already the default dialer") + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + Log.d(TAG, "onActivityResult: requestCode=$requestCode, resultCode=$resultCode") + 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") } } } private fun getCallLogs(): List> { val logsList = mutableListOf>() - val cursor: Cursor? = contentResolver.query( - CallLog.Calls.CONTENT_URI, null, null, null, CallLog.Calls.DATE + " DESC" - ) + val cursor: Cursor? = + contentResolver.query( + CallLog.Calls.CONTENT_URI, + null, + null, + null, + CallLog.Calls.DATE + " DESC" + ) cursor?.use { while (it.moveToNext()) { val number = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.NUMBER)) @@ -108,15 +213,16 @@ class MainActivity : FlutterActivity() { val date = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DATE)) val duration = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DURATION)) - val map = mutableMapOf( - "number" to number, - "type" to type, - "date" to date, - "duration" to duration - ) + val map = + mutableMapOf( + "number" to number, + "type" to type, + "date" to date, + "duration" to duration + ) logsList.add(map) } } 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 index 8464027..ea5e1f0 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 @@ -8,8 +8,13 @@ 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 + } + override fun onCreateOutgoingConnection( connectionManagerPhoneAccount: PhoneAccountHandle?, request: android.telecom.ConnectionRequest @@ -18,7 +23,13 @@ class CallConnectionService : ConnectionService() { override fun onStateChanged(state: Int) { super.onStateChanged(state) Log.d("CallConnectionService", "Connection state changed: $state") - // Update Flutter UI via MethodChannel if needed + 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() { @@ -28,7 +39,7 @@ class CallConnectionService : ConnectionService() { } connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED) connection.setInitialized() - connection.setActive() + connection.setDialing() // Start in dialing state return connection }