feat: update flutter UI via methodchannel, permissions via flutter at startup
This commit is contained in:
parent
b042a68a8e
commit
24dc5a9bbe
@ -1,4 +1,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.icing.dialer">
|
<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.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
@ -48,7 +50,6 @@
|
|||||||
<data android:scheme="tel" />
|
<data android:scheme="tel" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Moved service outside of activity -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.CallConnectionService"
|
android:name=".services.CallConnectionService"
|
||||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||||
|
@ -1,106 +1,211 @@
|
|||||||
package com.icing.dialer.activities
|
package com.icing.dialer.activities
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.CallLog
|
import android.provider.CallLog
|
||||||
import android.telecom.TelecomManager
|
|
||||||
import android.telecom.PhoneAccountHandle
|
|
||||||
import android.telecom.PhoneAccount
|
import android.telecom.PhoneAccount
|
||||||
import android.content.Intent
|
import android.telecom.PhoneAccountHandle
|
||||||
import android.content.ComponentName
|
import android.telecom.TelecomManager
|
||||||
|
import android.util.Log
|
||||||
|
import com.icing.dialer.KeystoreHelper
|
||||||
|
import com.icing.dialer.services.CallConnectionService
|
||||||
|
import com.icing.dialer.services.CallService
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import com.icing.dialer.KeystoreHelper
|
|
||||||
import com.icing.dialer.services.CallService
|
|
||||||
import com.icing.dialer.services.CallConnectionService
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
private val KEYSTORE_CHANNEL = "com.example.keystore"
|
private val KEYSTORE_CHANNEL = "com.example.keystore"
|
||||||
private val CALLLOG_CHANNEL = "com.example.calllog"
|
private val CALLLOG_CHANNEL = "com.example.calllog"
|
||||||
private val CALL_CHANNEL = "call_service"
|
private val CALL_CHANNEL = "call_service"
|
||||||
|
private val REQUEST_CALL_PERMISSIONS = 1
|
||||||
|
private val TAG = "MainActivity"
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.d(TAG, "onCreate started")
|
||||||
registerPhoneAccount()
|
registerPhoneAccount()
|
||||||
checkAndRequestDefaultDialer()
|
checkAndRequestDefaultDialer()
|
||||||
|
Log.d(TAG, "onCreate completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
private fun registerPhoneAccount() {
|
||||||
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||||
val phoneAccountHandle = PhoneAccountHandle(
|
val phoneAccountHandle =
|
||||||
ComponentName(this, CallConnectionService::class.java),
|
PhoneAccountHandle(
|
||||||
"IcingDialerAccount"
|
ComponentName(this, CallConnectionService::class.java),
|
||||||
)
|
"IcingDialerAccount"
|
||||||
val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Icing Dialer")
|
)
|
||||||
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
|
val phoneAccount =
|
||||||
.build()
|
PhoneAccount.builder(phoneAccountHandle, "Icing Dialer")
|
||||||
|
.setCapabilities(
|
||||||
|
PhoneAccount.CAPABILITY_CALL_PROVIDER or
|
||||||
|
PhoneAccount.CAPABILITY_CONNECTION_MANAGER
|
||||||
|
)
|
||||||
|
.build()
|
||||||
telecomManager.registerPhoneAccount(phoneAccount)
|
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) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL).setMethodCallHandler { call, result ->
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL)
|
||||||
when (call.method) {
|
.setMethodCallHandler { call, result ->
|
||||||
"makeGsmCall" -> {
|
when (call.method) {
|
||||||
val phoneNumber = call.argument<String>("phoneNumber")
|
"makeGsmCall" -> {
|
||||||
if (phoneNumber != null) {
|
val phoneNumber = call.argument<String>("phoneNumber")
|
||||||
val success = CallService.makeGsmCall(this, phoneNumber)
|
if (phoneNumber != null) {
|
||||||
if (success) {
|
val success = CallService.makeGsmCall(this, phoneNumber)
|
||||||
result.success(mapOf("status" to "calling", "phoneNumber" to phoneNumber))
|
if (success) {
|
||||||
} else {
|
result.success(
|
||||||
result.error("CALL_FAILED", "Failed to initiate call", null)
|
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 {
|
"hangUpCall" -> {
|
||||||
result.error("INVALID_PHONE_NUMBER", "Phone number is required", null)
|
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)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, KEYSTORE_CHANNEL)
|
||||||
.setMethodCallHandler { call, result ->
|
.setMethodCallHandler { call, result ->
|
||||||
KeystoreHelper(call, result).handleMethodCall()
|
KeystoreHelper(call, result).handleMethodCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALLLOG_CHANNEL)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALLLOG_CHANNEL)
|
||||||
.setMethodCallHandler { call, result ->
|
.setMethodCallHandler { call, result ->
|
||||||
if (call.method == "getCallLogs") {
|
if (call.method == "getCallLogs") {
|
||||||
val callLogs = getCallLogs()
|
val callLogs = getCallLogs()
|
||||||
result.success(callLogs)
|
result.success(callLogs)
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkAndRequestDefaultDialer() {
|
private fun checkAndRequestDefaultDialer() {
|
||||||
val tm = getSystemService(TELECOM_SERVICE) as TelecomManager
|
val tm = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||||
tm.defaultDialerPackage?.let {
|
val currentDefault = tm.defaultDialerPackage
|
||||||
if (it != packageName) {
|
Log.d(TAG, "Current default dialer: $currentDefault, My package: $packageName")
|
||||||
val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
|
if (currentDefault != packageName) {
|
||||||
.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
|
val intent =
|
||||||
startActivity(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<Map<String, Any?>> {
|
private fun getCallLogs(): List<Map<String, Any?>> {
|
||||||
val logsList = mutableListOf<Map<String, Any?>>()
|
val logsList = mutableListOf<Map<String, Any?>>()
|
||||||
val cursor: Cursor? = contentResolver.query(
|
val cursor: Cursor? =
|
||||||
CallLog.Calls.CONTENT_URI, null, null, null, CallLog.Calls.DATE + " DESC"
|
contentResolver.query(
|
||||||
)
|
CallLog.Calls.CONTENT_URI,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
CallLog.Calls.DATE + " DESC"
|
||||||
|
)
|
||||||
cursor?.use {
|
cursor?.use {
|
||||||
while (it.moveToNext()) {
|
while (it.moveToNext()) {
|
||||||
val number = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.NUMBER))
|
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 date = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DATE))
|
||||||
val duration = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DURATION))
|
val duration = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DURATION))
|
||||||
|
|
||||||
val map = mutableMapOf<String, Any?>(
|
val map =
|
||||||
"number" to number,
|
mutableMapOf<String, Any?>(
|
||||||
"type" to type,
|
"number" to number,
|
||||||
"date" to date,
|
"type" to type,
|
||||||
"duration" to duration
|
"date" to date,
|
||||||
)
|
"duration" to duration
|
||||||
|
)
|
||||||
logsList.add(map)
|
logsList.add(map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return logsList
|
return logsList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,13 @@ import android.telecom.DisconnectCause
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class CallConnectionService : ConnectionService() {
|
class CallConnectionService : ConnectionService() {
|
||||||
|
companion object {
|
||||||
|
var channel: MethodChannel? = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOutgoingConnection(
|
override fun onCreateOutgoingConnection(
|
||||||
connectionManagerPhoneAccount: PhoneAccountHandle?,
|
connectionManagerPhoneAccount: PhoneAccountHandle?,
|
||||||
request: android.telecom.ConnectionRequest
|
request: android.telecom.ConnectionRequest
|
||||||
@ -18,7 +23,13 @@ class CallConnectionService : ConnectionService() {
|
|||||||
override fun onStateChanged(state: Int) {
|
override fun onStateChanged(state: Int) {
|
||||||
super.onStateChanged(state)
|
super.onStateChanged(state)
|
||||||
Log.d("CallConnectionService", "Connection state changed: $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() {
|
override fun onDisconnect() {
|
||||||
@ -28,7 +39,7 @@ class CallConnectionService : ConnectionService() {
|
|||||||
}
|
}
|
||||||
connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED)
|
connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED)
|
||||||
connection.setInitialized()
|
connection.setInitialized()
|
||||||
connection.setActive()
|
connection.setDialing() // Start in dialing state
|
||||||
return connection
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user