diff --git a/dialer/android/app/src/main/AndroidManifest.xml b/dialer/android/app/src/main/AndroidManifest.xml
index 95b4a91..4c1cb5f 100644
--- a/dialer/android/app/src/main/AndroidManifest.xml
+++ b/dialer/android/app/src/main/AndroidManifest.xml
@@ -1,17 +1,19 @@
-
-
+
+
-
-
+
+
+
+
+
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
-
-
+
+
@@ -75,17 +82,22 @@
-->
-
+
+
-
-
+
+
-
\ No newline at end of file
+
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 2fe4eb9..f58cdab 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
@@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
-import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.CallLog
@@ -26,24 +25,76 @@ class MainActivity : FlutterActivity() {
private val CALL_CHANNEL = "call_service"
private val TAG = "MainActivity"
private val REQUEST_CODE_SET_DEFAULT_DIALER = 1001
+ private val REQUEST_CODE_CALL_LOG_PERMISSION = 1002
+ private var pendingIncomingCall: Pair? = null
+ private var wasPhoneLocked: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate started")
- Log.d(TAG, "Waiting for Flutter to signal permissions")
+ wasPhoneLocked = intent.getBooleanExtra("wasPhoneLocked", false)
+ Log.d(TAG, "Was phone locked at start: $wasPhoneLocked")
+ updateLockScreenFlags(intent)
+ handleIncomingCallIntent(intent)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ wasPhoneLocked = intent.getBooleanExtra("wasPhoneLocked", false)
+ Log.d(TAG, "onNewIntent, wasPhoneLocked: $wasPhoneLocked")
+ updateLockScreenFlags(intent)
+ handleIncomingCallIntent(intent)
+ }
+
+ private fun updateLockScreenFlags(intent: Intent?) {
+ val isIncomingCall = intent?.getBooleanExtra("isIncomingCall", false) ?: false
+ if (isIncomingCall && wasPhoneLocked) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ setShowWhenLocked(true)
+ setTurnScreenOn(true)
+ } else {
+ @Suppress("DEPRECATION")
+ window.addFlags(
+ android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+ android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ )
+ }
+ Log.d(TAG, "Enabled showWhenLocked and turnScreenOn for incoming call")
+ } else {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ setShowWhenLocked(false)
+ setTurnScreenOn(false)
+ } else {
+ @Suppress("DEPRECATION")
+ window.clearFlags(
+ android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+ android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ )
+ }
+ Log.d(TAG, "Disabled showWhenLocked and turnScreenOn for normal usage")
+ }
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.d(TAG, "Configuring Flutter engine")
-
MyInCallService.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")
- checkAndRequestDefaultDialer()
+ pendingIncomingCall?.let { (phoneNumber, showScreen) ->
+ if (showScreen) {
+ MyInCallService.channel?.invokeMethod("incomingCallFromNotification", mapOf(
+ "phoneNumber" to phoneNumber,
+ "wasPhoneLocked" to wasPhoneLocked
+ ))
+ pendingIncomingCall = null
+ }
+ }
result.success(true)
}
"makeGsmCall" -> {
@@ -60,43 +111,144 @@ class MainActivity : FlutterActivity() {
}
}
"hangUpCall" -> {
- val success = CallService.hangUpCall(this)
+ val success = MyInCallService.currentCall?.let {
+ it.disconnect()
+ Log.d(TAG, "Call disconnected")
+ MyInCallService.channel?.invokeMethod("callEnded", mapOf(
+ "callId" to it.details.handle.toString(),
+ "wasPhoneLocked" to wasPhoneLocked
+ ))
+ true
+ } ?: false
if (success) {
result.success(mapOf("status" to "ended"))
+ if (wasPhoneLocked) {
+ Log.d(TAG, "Finishing and removing task after hangup, phone was locked")
+ finishAndRemoveTask()
+ }
} else {
- result.error("HANGUP_FAILED", "Failed to end call", null)
+ Log.w(TAG, "No active call to hang up")
+ result.error("HANGUP_FAILED", "No active call to hang up", null)
}
}
"answerCall" -> {
val success = MyInCallService.currentCall?.let {
- it.answer(0) // 0 for default video state (audio-only)
+ it.answer(0)
Log.d(TAG, "Answered call")
true
} ?: false
if (success) {
result.success(mapOf("status" to "answered"))
} else {
+ Log.w(TAG, "No active call to answer")
result.error("ANSWER_FAILED", "No active call to answer", null)
}
}
+ "callEndedFromFlutter" -> {
+ Log.d(TAG, "Call ended from Flutter, wasPhoneLocked: $wasPhoneLocked")
+ if (wasPhoneLocked) {
+ finishAndRemoveTask()
+ Log.d(TAG, "Finishing and removing task after call ended, phone was locked")
+ }
+ result.success(true)
+ }
+ "getCallState" -> {
+ val stateStr = when (MyInCallService.currentCall?.state) {
+ android.telecom.Call.STATE_ACTIVE -> "active"
+ android.telecom.Call.STATE_RINGING -> "ringing"
+ android.telecom.Call.STATE_DIALING -> "dialing"
+ android.telecom.Call.STATE_DISCONNECTED -> "disconnected"
+ android.telecom.Call.STATE_DISCONNECTING -> "disconnecting"
+ else -> "unknown"
+ }
+ Log.d(TAG, "getCallState called, returning: $stateStr")
+ result.success(stateStr)
+ }
+ "muteCall" -> {
+ val mute = call.argument("mute") ?: false
+ val success = MyInCallService.currentCall?.let {
+ MyInCallService.toggleMute(mute)
+ } ?: false
+ if (success) {
+ Log.d(TAG, "Mute call set to $mute")
+ result.success(mapOf("status" to "success"))
+ } else {
+ Log.w(TAG, "No active call or failed to mute")
+ result.error("MUTE_FAILED", "No active call or failed to mute", null)
+ }
+ }
+ "speakerCall" -> {
+ val speaker = call.argument("speaker") ?: false
+ val success = MyInCallService.currentCall?.let {
+ MyInCallService.toggleSpeaker(speaker)
+ } ?: false
+ if (success) {
+ Log.d(TAG, "Speaker call set to $speaker")
+ result.success(mapOf("status" to "success"))
+ } else {
+ Log.w(TAG, "No active call or failed to set speaker")
+ result.error("SPEAKER_FAILED", "No active call or failed to set speaker", null)
+ }
+ }
+ "isDefaultDialer" -> {
+ val isDefault = isDefaultDialer()
+ Log.d(TAG, "isDefaultDialer called, returning: $isDefault")
+ result.success(isDefault)
+ }
+ "requestDefaultDialer" -> {
+ checkAndRequestDefaultDialer()
+ result.success(true)
+ }
+ "sendDtmfTone" -> {
+ val digit = call.argument("digit")
+ if (digit != null) {
+ val success = MyInCallService.sendDtmfTone(digit)
+ result.success(success)
+ } else {
+ result.error("INVALID_ARGUMENT", "Digit is null", null)
+ }
+ }
+ "isDefaultDialer" -> {
+ val isDefault = isDefaultDialer()
+ Log.d(TAG, "isDefaultDialer called, returning: $isDefault")
+ result.success(isDefault)
+ }
+ "requestDefaultDialer" -> {
+ checkAndRequestDefaultDialer()
+ result.success(true)
+ }
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)
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED) {
+ val callLogs = getCallLogs()
+ result.success(callLogs)
+ } else {
+ requestPermissions(arrayOf(Manifest.permission.READ_CALL_LOG), REQUEST_CODE_CALL_LOG_PERMISSION)
+ result.error("PERMISSION_DENIED", "Call log permission not granted", null)
+ }
} else {
result.notImplemented()
}
}
}
+ private fun isDefaultDialer(): Boolean {
+ val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
+ val currentDefault = telecomManager.defaultDialerPackage
+ Log.d(TAG, "Checking default dialer: current=$currentDefault, myPackage=$packageName")
+ return currentDefault == packageName
+ }
+
private fun checkAndRequestDefaultDialer() {
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
val currentDefault = telecomManager.defaultDialerPackage
@@ -109,8 +261,6 @@ class MainActivity : FlutterActivity() {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER)
startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER)
Log.d(TAG, "Launched RoleManager intent for default dialer on API 29+")
- } else {
- Log.d(TAG, "RoleManager: Available=${roleManager.isRoleAvailable(RoleManager.ROLE_DIALER)}, Held=${roleManager.isRoleHeld(RoleManager.ROLE_DIALER)}")
}
} else {
val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
@@ -148,6 +298,18 @@ class MainActivity : FlutterActivity() {
}
}
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == REQUEST_CODE_CALL_LOG_PERMISSION) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Call log permission granted")
+ MyInCallService.channel?.invokeMethod("callLogPermissionGranted", null)
+ } else {
+ Log.w(TAG, "Call log permission denied")
+ }
+ }
+ }
+
private fun getCallLogs(): List