Compare commits
No commits in common. "c886e29d752ab2c2dee6b25855f3bfb10b5b1dff" and "b042a68a8e2bc6ec930bb5274e56e87629959e99" have entirely different histories.
c886e29d75
...
b042a68a8e
@ -1,6 +1,4 @@
|
||||
<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.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
@ -50,6 +48,7 @@
|
||||
<data android:scheme="tel" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Moved service outside of activity -->
|
||||
<service
|
||||
android:name=".services.CallConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
|
@ -1,137 +1,60 @@
|
||||
package com.icing.dialer.activities
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
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 com.icing.dialer.KeystoreHelper
|
||||
import com.icing.dialer.services.CallConnectionService
|
||||
import com.icing.dialer.services.CallService
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.telecom.PhoneAccount
|
||||
import android.content.Intent
|
||||
import android.content.ComponentName
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
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() {
|
||||
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")
|
||||
}
|
||||
|
||||
// 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 =
|
||||
PhoneAccountHandle(
|
||||
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
|
||||
)
|
||||
val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Icing Dialer")
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
|
||||
.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 ->
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"makeGsmCall" -> {
|
||||
val phoneNumber = call.argument<String>("phoneNumber")
|
||||
if (phoneNumber != null) {
|
||||
val success = CallService.makeGsmCall(this, phoneNumber)
|
||||
if (success) {
|
||||
result.success(
|
||||
mapOf(
|
||||
"status" to "calling",
|
||||
"phoneNumber" to phoneNumber
|
||||
)
|
||||
)
|
||||
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
|
||||
)
|
||||
result.error("INVALID_PHONE_NUMBER", "Phone number is required", null)
|
||||
}
|
||||
}
|
||||
"hangUpCall" -> {
|
||||
@ -164,47 +87,19 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
private fun checkAndRequestDefaultDialer() {
|
||||
val tm = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||
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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCallLogs(): List<Map<String, Any?>> {
|
||||
val logsList = mutableListOf<Map<String, Any?>>()
|
||||
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()) {
|
||||
@ -213,8 +108,7 @@ class MainActivity : FlutterActivity() {
|
||||
val date = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DATE))
|
||||
val duration = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DURATION))
|
||||
|
||||
val map =
|
||||
mutableMapOf<String, Any?>(
|
||||
val map = mutableMapOf<String, Any?>(
|
||||
"number" to number,
|
||||
"type" to type,
|
||||
"date" to date,
|
||||
|
@ -8,13 +8,8 @@ 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
|
||||
@ -23,13 +18,7 @@ class CallConnectionService : ConnectionService() {
|
||||
override fun onStateChanged(state: Int) {
|
||||
super.onStateChanged(state)
|
||||
Log.d("CallConnectionService", "Connection state changed: $state")
|
||||
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()))
|
||||
// Update Flutter UI via MethodChannel if needed
|
||||
}
|
||||
|
||||
override fun onDisconnect() {
|
||||
@ -39,7 +28,7 @@ class CallConnectionService : ConnectionService() {
|
||||
}
|
||||
connection.setAddress(request.address, TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.setInitialized()
|
||||
connection.setDialing() // Start in dialing state
|
||||
connection.setActive()
|
||||
return connection
|
||||
}
|
||||
|
||||
|
@ -5,31 +5,23 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.telecom.TelecomManager
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.telephony.TelephonyManager
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.content.pm.PackageManager
|
||||
|
||||
object CallService {
|
||||
private var phoneAccountHandle: PhoneAccountHandle? = null
|
||||
|
||||
fun setPhoneAccountHandle(handle: PhoneAccountHandle) {
|
||||
phoneAccountHandle = handle
|
||||
}
|
||||
|
||||
fun makeGsmCall(context: Context, phoneNumber: String): Boolean {
|
||||
return try {
|
||||
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
||||
val uri = Uri.parse("tel:$phoneNumber")
|
||||
|
||||
// Check CALL_PHONE permission
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val extras = Bundle()
|
||||
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
|
||||
telecomManager.placeCall(uri, extras)
|
||||
telecomManager.placeCall(uri, null)
|
||||
true
|
||||
} else {
|
||||
Log.e("CallService", "GSM call not supported below Android M")
|
||||
|
@ -1,10 +1,8 @@
|
||||
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 'globals.dart' as globals;
|
||||
import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() async {
|
||||
@ -15,35 +13,19 @@ void main() async {
|
||||
final AsymmetricCryptoService cryptoService = AsymmetricCryptoService();
|
||||
await cryptoService.initializeDefaultKeyPair();
|
||||
|
||||
// Request permissions before running the app
|
||||
await _requestPermissions();
|
||||
|
||||
CallService(); // Initialize CallService
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider<AsymmetricCryptoService>(
|
||||
create: (_) => cryptoService,
|
||||
),
|
||||
// Add other providers here
|
||||
],
|
||||
child: Dialer(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _requestPermissions() async {
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
Permission.phone,
|
||||
Permission.contacts,
|
||||
].request();
|
||||
if (statuses.values.every((status) => status.isGranted)) {
|
||||
print("All required permissions granted");
|
||||
} else {
|
||||
print("Permissions denied: ${statuses.entries.where((e) => !e.value.isGranted).map((e) => e.key).join(', ')}");
|
||||
}
|
||||
}
|
||||
|
||||
class Dialer extends StatelessWidget {
|
||||
const Dialer({super.key});
|
||||
|
||||
@ -51,12 +33,11 @@ class Dialer extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ContactState(
|
||||
child: MaterialApp(
|
||||
navigatorKey: CallService.navigatorKey,
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
brightness: Brightness.dark
|
||||
),
|
||||
home: SafeArea(child: MyHomePage()),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -4,45 +4,26 @@ import '../features/call/call_page.dart';
|
||||
|
||||
class CallService {
|
||||
static const MethodChannel _channel = MethodChannel('call_service');
|
||||
static String? currentPhoneNumber;
|
||||
|
||||
CallService() {
|
||||
_channel.setMethodCallHandler((call) async {
|
||||
if (call.method == "callStateChanged") {
|
||||
final state = call.arguments["state"] as String;
|
||||
final phoneNumber = call.arguments["phoneNumber"] as String;
|
||||
if (state == "dialing" || state == "active") {
|
||||
Navigator.push(
|
||||
navigatorKey.currentContext!,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CallPage(
|
||||
displayName: phoneNumber, // Replace with contact lookup if available
|
||||
phoneNumber: phoneNumber,
|
||||
thumbnail: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (state == "disconnected") {
|
||||
Navigator.pop(navigatorKey.currentContext!);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add a GlobalKey for Navigator
|
||||
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
Future<void> makeGsmCall(
|
||||
BuildContext context, {
|
||||
required String phoneNumber,
|
||||
String? displayName,
|
||||
Uint8List? thumbnail,
|
||||
Uint8List? thumbnail, // Added optional thumbnail
|
||||
}) async {
|
||||
try {
|
||||
currentPhoneNumber = phoneNumber;
|
||||
final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber});
|
||||
if (result["status"] == "calling") {
|
||||
// CallPage will be shown via CallConnectionService callback
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CallPage(
|
||||
displayName: displayName ?? phoneNumber, // Fallback to phoneNumber if no name
|
||||
phoneNumber: phoneNumber,
|
||||
thumbnail: thumbnail, // Pass the thumbnail
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (result["status"] == "pending_default_dialer") {
|
||||
print("Waiting for user to set app as default dialer");
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
Loading…
Reference in New Issue
Block a user