feat: request perm in flutter, wait for perm before trying to become main dialer
All checks were successful
/ build (push) Successful in 8m35s
/ build-stealth (push) Successful in 8m33s
/ mirror (push) Successful in 5s

This commit is contained in:
Florian Griffon 2025-03-05 16:04:05 +01:00
parent c886e29d75
commit 5529a6e038
3 changed files with 77 additions and 68 deletions

View File

@ -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<out String>,
// 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<String>("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()
}
}
}

View File

@ -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()
}

View File

@ -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<void> _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(', ')}");
}