@qusaieilouti99/call-manager 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,8 @@ package com.qusaieilouti99.callmanager
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Intent
5
- import android.graphics.Color
6
5
  import android.os.Bundle
7
- import android.view.View
6
+ import android.view.WindowManager
8
7
  import android.widget.Button
9
8
  import android.widget.ImageView
10
9
  import android.widget.TextView
@@ -13,34 +12,39 @@ class CallActivity : Activity() {
13
12
  override fun onCreate(savedInstanceState: Bundle?) {
14
13
  super.onCreate(savedInstanceState)
15
14
  window.addFlags(
16
- android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
17
- android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
18
- android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
15
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
16
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
17
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
19
18
  )
20
19
  setContentView(R.layout.activity_call)
21
20
 
21
+ // Cancel the notification as soon as CallActivity is shown
22
+ CallEngine.cancelIncomingCallUI(this)
23
+
22
24
  val callerName = intent.getStringExtra("callerName") ?: "Unknown"
23
- val avatarView = findViewById<ImageView>(R.id.avatar)
24
25
  val nameView = findViewById<TextView>(R.id.caller_name)
25
26
  val answerBtn = findViewById<Button>(R.id.answer_btn)
26
27
  val declineBtn = findViewById<Button>(R.id.decline_btn)
27
28
 
28
29
  nameView.text = callerName
29
- // Optionally set avatarView image
30
30
 
31
31
  answerBtn.setOnClickListener {
32
- // Start MainActivity (React Native) using launch intent
33
- val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
34
- launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
35
- launchIntent?.putExtra("incomingCall", true)
36
- startActivity(launchIntent)
32
+ CallEngine.bringAppToForeground(this)
37
33
  finish()
38
34
  }
39
35
 
40
36
  declineBtn.setOnClickListener {
41
- // Stop ringtone, cancel notification, finish
42
- CallNotificationHelper(this).cancelIncomingCallNotification()
37
+ CallEngine.cancelIncomingCallUI(this)
38
+ CallEngine.stopForegroundService(this)
39
+ CallEngine.disconnectTelecomCall(this, intent.getStringExtra("callId") ?: "")
43
40
  finish()
44
41
  }
45
42
  }
43
+
44
+ override fun onDestroy() {
45
+ super.onDestroy()
46
+ CallEngine.cancelIncomingCallUI(this)
47
+ CallEngine.stopForegroundService(this)
48
+ CallEngine.disconnectTelecomCall(this, intent.getStringExtra("callId") ?: "")
49
+ }
46
50
  }
@@ -0,0 +1,195 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.app.*
4
+ import android.content.ComponentName
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.media.AudioAttributes
8
+ import android.media.RingtoneManager
9
+ import android.os.Build
10
+ import android.os.Bundle
11
+ import android.telecom.PhoneAccount
12
+ import android.telecom.PhoneAccountHandle
13
+ import android.telecom.TelecomManager
14
+ import android.util.Log
15
+ import android.app.Person
16
+
17
+ object CallEngine {
18
+ private const val TAG = "CallEngine"
19
+ private const val PHONE_ACCOUNT_ID = "com.qusaieilouti99.callmanager.SELF_MANAGED"
20
+ private const val NOTIF_CHANNEL_ID = "incoming_call_channel"
21
+ private const val NOTIF_ID = 2001
22
+ private const val FOREGROUND_CHANNEL_ID = "call_foreground_channel"
23
+ private const val FOREGROUND_NOTIF_ID = 1001
24
+
25
+ private var ringtone: android.media.Ringtone? = null
26
+
27
+ // --- Public API ---
28
+
29
+ fun reportIncomingCall(context: Context, callId: String, callData: String) {
30
+ Log.d(TAG, "reportIncomingCall: $callId, $callData")
31
+ val callerName = try {
32
+ val json = org.json.JSONObject(callData)
33
+ json.optString("name", "Unknown")
34
+ } catch (e: Exception) { "Unknown" }
35
+
36
+ showIncomingCallUI(context, callId, callerName)
37
+ registerPhoneAccount(context)
38
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
39
+ val phoneAccountHandle = getPhoneAccountHandle(context)
40
+ val extras = Bundle().apply { putString(MyConnectionService.EXTRA_CALL_DATA, callData) }
41
+ try {
42
+ telecomManager.addNewIncomingCall(phoneAccountHandle, extras)
43
+ startForegroundService(context)
44
+ } catch (e: Exception) {
45
+ Log.e(TAG, "Failed to report incoming call: ${e.message}")
46
+ }
47
+ }
48
+
49
+ fun showIncomingCallUI(context: Context, callId: String, callerName: String) {
50
+ createNotificationChannel(context)
51
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
52
+
53
+ // PendingIntents
54
+ val answerIntent = Intent(context, CallNotificationActionReceiver::class.java).apply {
55
+ action = "com.qusaieilouti99.callmanager.ANSWER_CALL"
56
+ putExtra("callId", callId)
57
+ }
58
+ val answerPendingIntent = PendingIntent.getBroadcast(context, 0, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
59
+
60
+ val declineIntent = Intent(context, CallNotificationActionReceiver::class.java).apply {
61
+ action = "com.qusaieilouti99.callmanager.DECLINE_CALL"
62
+ putExtra("callId", callId)
63
+ }
64
+ val declinePendingIntent = PendingIntent.getBroadcast(context, 1, declineIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
65
+
66
+ val fullScreenIntent = Intent(context, CallActivity::class.java).apply {
67
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
68
+ putExtra("callId", callId)
69
+ putExtra("callerName", callerName)
70
+ }
71
+ val fullScreenPendingIntent = PendingIntent.getActivity(context, 2, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
72
+
73
+ val notification: Notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
74
+ val person = Person.Builder().setName(callerName).setImportant(true).build()
75
+ Notification.Builder(context, NOTIF_CHANNEL_ID)
76
+ .setSmallIcon(android.R.drawable.sym_call_incoming)
77
+ .setStyle(Notification.CallStyle.forIncomingCall(person, declinePendingIntent, answerPendingIntent))
78
+ .setFullScreenIntent(fullScreenPendingIntent, true)
79
+ .setOngoing(true)
80
+ .setAutoCancel(false)
81
+ .build()
82
+ } else {
83
+ Notification.Builder(context, NOTIF_CHANNEL_ID)
84
+ .setSmallIcon(android.R.drawable.sym_call_incoming)
85
+ .setContentTitle("Incoming Call")
86
+ .setContentText(callerName)
87
+ .setPriority(Notification.PRIORITY_HIGH)
88
+ .setCategory(Notification.CATEGORY_CALL)
89
+ .setFullScreenIntent(fullScreenPendingIntent, true)
90
+ .addAction(android.R.drawable.sym_action_call, "Answer", answerPendingIntent)
91
+ .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Decline", declinePendingIntent)
92
+ .setOngoing(true)
93
+ .setAutoCancel(false)
94
+ .build()
95
+ }
96
+
97
+ notificationManager.notify(NOTIF_ID, notification)
98
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) playRingtone(context)
99
+ }
100
+
101
+ fun cancelIncomingCallUI(context: Context) {
102
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
103
+ notificationManager.cancel(NOTIF_ID)
104
+ stopRingtone()
105
+ }
106
+
107
+ fun startForegroundService(context: Context) {
108
+ val intent = Intent(context, CallForegroundService::class.java)
109
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent)
110
+ else context.startService(intent)
111
+ }
112
+
113
+ fun stopForegroundService(context: Context) {
114
+ val intent = Intent(context, CallForegroundService::class.java)
115
+ context.stopService(intent)
116
+ }
117
+
118
+ fun disconnectTelecomCall(context: Context, callId: String) {
119
+ // You can extend this to track and disconnect specific calls if needed.
120
+ Log.d(TAG, "disconnectTelecomCall called for callId=$callId")
121
+ }
122
+
123
+ fun bringAppToForeground(context: Context) {
124
+ val packageName = context.packageName
125
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
126
+ launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
127
+ context.startActivity(launchIntent)
128
+ Log.d(TAG, "App brought to foreground via launchIntent")
129
+ }
130
+
131
+ // --- Private helpers ---
132
+
133
+ private fun playRingtone(context: Context) {
134
+ try {
135
+ Log.d(TAG, "Playing ringtone")
136
+ val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
137
+ ringtone = RingtoneManager.getRingtone(context, uri)
138
+ ringtone?.audioAttributes = AudioAttributes.Builder()
139
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
140
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
141
+ .build()
142
+ ringtone?.play()
143
+ } catch (e: Exception) {
144
+ Log.e(TAG, "Failed to play ringtone: ${e.message}")
145
+ }
146
+ }
147
+
148
+ private fun stopRingtone() {
149
+ try { ringtone?.stop() } catch (_: Exception) {}
150
+ ringtone = null
151
+ }
152
+
153
+ private fun createNotificationChannel(context: Context) {
154
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
155
+ val channel = NotificationChannel(
156
+ NOTIF_CHANNEL_ID,
157
+ "Incoming Call Channel",
158
+ NotificationManager.IMPORTANCE_HIGH
159
+ )
160
+ channel.description = "Notifications for incoming calls"
161
+ channel.enableLights(true)
162
+ channel.lightColor = Color.GREEN
163
+ channel.enableVibration(true)
164
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
165
+ channel.setSound(
166
+ RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
167
+ AudioAttributes.Builder()
168
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
169
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
170
+ .build()
171
+ )
172
+ }
173
+ val manager = context.getSystemService(NotificationManager::class.java)
174
+ manager.createNotificationChannel(channel)
175
+ }
176
+ }
177
+
178
+ private fun registerPhoneAccount(context: Context) {
179
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
180
+ val phoneAccountHandle = getPhoneAccountHandle(context)
181
+ if (telecomManager.getPhoneAccount(phoneAccountHandle) == null) {
182
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "PingMe Call")
183
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
184
+ .build()
185
+ telecomManager.registerPhoneAccount(phoneAccount)
186
+ }
187
+ }
188
+
189
+ private fun getPhoneAccountHandle(context: Context): PhoneAccountHandle {
190
+ return PhoneAccountHandle(
191
+ ComponentName(context, MyConnectionService::class.java),
192
+ PHONE_ACCOUNT_ID
193
+ )
194
+ }
195
+ }
@@ -53,4 +53,11 @@ class CallForegroundService : Service() {
53
53
  manager.createNotificationChannel(channel)
54
54
  }
55
55
  }
56
+
57
+ override fun onDestroy() {
58
+ super.onDestroy()
59
+ // Clean up everything when the foreground service is stopped
60
+ CallEngine.cancelIncomingCallUI(this)
61
+ CallEngine.disconnectTelecomCall(this, "")
62
+ }
56
63
  }
@@ -1,10 +1,6 @@
1
1
  package com.qusaieilouti99.callmanager
2
2
 
3
3
  import android.util.Log
4
- import android.app.ActivityManager
5
- import android.content.Context
6
- import android.content.Intent
7
- import android.os.Build
8
4
  import com.facebook.react.bridge.*
9
5
  import com.facebook.react.turbomodule.core.interfaces.TurboModule
10
6
  import com.facebook.react.module.annotations.ReactModule
@@ -18,73 +14,45 @@ class CallManagerModule(reactContext: ReactApplicationContext) :
18
14
  const val TAG = "CallManagerModule"
19
15
  }
20
16
 
21
- private var currentCallId: String? = null
22
17
  private var eventHandler: Callback? = null
23
18
 
24
19
  override fun getName(): String = NAME
25
20
 
26
- // Register JS event handler
27
21
  override fun setEventHandler(callback: Callback) {
28
22
  Log.d(TAG, "setEventHandler called, registering JS callback")
29
23
  eventHandler = callback
30
24
  }
31
25
 
32
- // Helper to emit events to JS
33
26
  fun emitEvent(event: String, callData: String) {
34
27
  Log.d(TAG, "emitEvent: event=$event, callData=$callData")
35
28
  eventHandler?.invoke(event, callData)
36
29
  }
37
30
 
38
- // 1. Report incoming call (for JS, not used by NotificationService anymore)
39
31
  override fun reportIncomingCall(callId: String, callData: String) {
40
32
  Log.d(TAG, "reportIncomingCall called from JS with callId=$callId, callData=$callData")
41
- // Optionally, you can call CallNativeHelper here if you want to trigger from JS
42
- CallNativeHelper.reportIncomingCall(reactApplicationContext, callId, callData)
33
+ CallEngine.reportIncomingCall(reactApplicationContext, callId, callData)
43
34
  }
44
35
 
45
- // 2. End call
46
36
  override fun endCall(callId: String) {
47
37
  Log.d(TAG, "endCall called with callId=$callId")
48
- val context = reactApplicationContext
49
- val intent = Intent(context, CallForegroundService::class.java)
50
- val stopped = context.stopService(intent)
51
- Log.d(TAG, "CallForegroundService stopped: $stopped")
38
+ CallEngine.cancelIncomingCallUI(reactApplicationContext)
39
+ CallEngine.stopForegroundService(reactApplicationContext)
40
+ CallEngine.disconnectTelecomCall(reactApplicationContext, callId)
52
41
  }
53
42
 
54
- // 3. Answer call
55
43
  override fun answerCall(callId: String) {
56
44
  Log.d(TAG, "answerCall called with callId=$callId")
57
- bringAppToForeground()
45
+ CallEngine.bringAppToForeground(reactApplicationContext)
58
46
  }
59
47
 
60
- // 4. Silence ringtone
61
48
  override fun silenceRingtone() {
62
49
  Log.d(TAG, "silenceRingtone called")
63
- // TODO: Implement ringtone silencing logic if you play a custom ringtone
50
+ CallEngine.cancelIncomingCallUI(reactApplicationContext)
64
51
  }
65
52
 
66
- // 5. Reject current and answer new
67
53
  override fun rejectCurrentAndAnswerNew(callId: String, callData: String) {
68
54
  Log.d(TAG, "rejectCurrentAndAnswerNew called with callId=$callId, callData=$callData")
69
- endCall(currentCallId ?: "")
70
- CallNativeHelper.reportIncomingCall(reactApplicationContext, callId, callData)
71
- }
72
-
73
- // --- Bring App to Foreground ---
74
-
75
- private fun bringAppToForeground() {
76
- val context = reactApplicationContext
77
- val packageName = context.packageName
78
- val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
79
- val tasks = activityManager.appTasks
80
- Log.d(TAG, "bringAppToForeground called. tasks.isNullOrEmpty()=${tasks.isNullOrEmpty()}")
81
- if (tasks.isNullOrEmpty()) {
82
- val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
83
- launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
84
- context.startActivity(launchIntent)
85
- Log.d(TAG, "App brought to foreground via launchIntent")
86
- } else {
87
- Log.d(TAG, "App already in foreground or has tasks")
88
- }
55
+ endCall(callId)
56
+ CallEngine.reportIncomingCall(reactApplicationContext, callId, callData)
89
57
  }
90
58
  }
@@ -7,37 +7,20 @@ import android.util.Log
7
7
 
8
8
  class CallNotificationActionReceiver : BroadcastReceiver() {
9
9
  override fun onReceive(context: Context, intent: Intent) {
10
-
11
10
  val callId = intent.getStringExtra("callId") ?: return
12
11
  Log.d("CallNotificationActionReceiver", "onReceive called with action=${intent.action}, callId=$callId")
13
- val action = intent.action
14
-
15
- val app = context.applicationContext as? com.facebook.react.ReactApplication
16
- val reactInstanceManager = app?.reactNativeHost?.reactInstanceManager
17
- val reactContext = reactInstanceManager?.currentReactContext
18
- val module = reactContext?.getNativeModule(CallManagerModule::class.java)
19
-
20
- when (action) {
12
+ when (intent.action) {
21
13
  "com.qusaieilouti99.callmanager.ANSWER_CALL" -> {
22
14
  Log.d("CallNotificationActionReceiver", "Answer action received")
23
- bringAppToForeground(context)
15
+ CallEngine.bringAppToForeground(context)
24
16
  }
25
17
  "com.qusaieilouti99.callmanager.DECLINE_CALL" -> {
26
18
  Log.d("CallNotificationActionReceiver", "Decline action received")
27
- module?.endCall(callId)
19
+ CallEngine.cancelIncomingCallUI(context)
20
+ CallEngine.stopForegroundService(context)
21
+ CallEngine.disconnectTelecomCall(context, callId)
28
22
  }
29
23
  }
30
-
31
- // Cancel notification and stop ringtone
32
- CallNotificationHelper(context).cancelIncomingCallNotification()
33
- }
34
-
35
- private fun bringAppToForeground(context: Context) {
36
- val packageName = context.packageName
37
- val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
38
- launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
39
- launchIntent?.putExtra("incomingCall", true)
40
- context.startActivity(launchIntent)
41
- Log.d("CallNotificationActionReceiver", "Bringing app to foreground for incoming call")
24
+ CallEngine.cancelIncomingCallUI(context)
42
25
  }
43
26
  }
@@ -25,6 +25,8 @@ class MyConnection(
25
25
  ?.getNativeModule(CallManagerModule::class.java)
26
26
  module?.emitEvent("onCallAnswered", callData)
27
27
  }
28
+ // Bring app to foreground
29
+ CallEngine.bringAppToForeground(context)
28
30
  }
29
31
 
30
32
  override fun onReject() {
@@ -37,6 +39,10 @@ class MyConnection(
37
39
  ?.getNativeModule(CallManagerModule::class.java)
38
40
  module?.emitEvent("onCallRejected", callData)
39
41
  }
42
+ // Clean up
43
+ CallEngine.cancelIncomingCallUI(context)
44
+ CallEngine.stopForegroundService(context)
45
+ CallEngine.disconnectTelecomCall(context, "")
40
46
  }
41
47
 
42
48
  override fun onDisconnect() {
@@ -49,5 +55,9 @@ class MyConnection(
49
55
  ?.getNativeModule(CallManagerModule::class.java)
50
56
  module?.emitEvent("onCallEnded", callData)
51
57
  }
58
+ // Clean up
59
+ CallEngine.cancelIncomingCallUI(context)
60
+ CallEngine.stopForegroundService(context)
61
+ CallEngine.disconnectTelecomCall(context, "")
52
62
  }
53
63
  }
@@ -18,7 +18,6 @@ class MyConnectionService : ConnectionService() {
18
18
  request: ConnectionRequest
19
19
  ): Connection {
20
20
  Log.d(TAG, "onCreateIncomingConnection: ${request.extras}")
21
-
22
21
  val callData = request.extras?.getString(EXTRA_CALL_DATA) ?: ""
23
22
  val connection = MyConnection(applicationContext, callData)
24
23
  connection.setRinging()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1,61 +0,0 @@
1
- package com.qusaieilouti99.callmanager
2
-
3
- import android.content.ComponentName
4
- import android.content.Context
5
- import android.os.Build
6
- import android.os.Bundle
7
- import android.telecom.PhoneAccount
8
- import android.telecom.PhoneAccountHandle
9
- import android.telecom.TelecomManager
10
- import android.util.Log
11
-
12
- object CallNativeHelper {
13
- private const val TAG = "CallNativeHelper"
14
- private const val PHONE_ACCOUNT_ID = "com.qusaieilouti99.callmanager.SELF_MANAGED"
15
-
16
- fun reportIncomingCall(context: Context, callId: String, callData: String) {
17
- Log.d(TAG, "reportIncomingCall called with callId=$callId, callData=$callData")
18
- // Parse caller name from callData (or use a default)
19
- val callerName = try {
20
- val json = org.json.JSONObject(callData)
21
- json.optString("name", "Unknown")
22
- } catch (e: Exception) {
23
- "Unknown"
24
- }
25
-
26
- // Show incoming call notification and play ringtone
27
- CallNotificationHelper(context).showIncomingCallNotification(callId, callerName)
28
-
29
- val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
30
-
31
- // Register self-managed PhoneAccount if not already registered
32
- val phoneAccountHandle = PhoneAccountHandle(
33
- ComponentName(context, MyConnectionService::class.java),
34
- PHONE_ACCOUNT_ID
35
- )
36
- if (telecomManager.getPhoneAccount(phoneAccountHandle) == null) {
37
- val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "PingMe Call")
38
- .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
39
- .build()
40
- telecomManager.registerPhoneAccount(phoneAccount)
41
- }
42
-
43
- // Prepare extras
44
- val extras = Bundle()
45
- extras.putString(MyConnectionService.EXTRA_CALL_DATA, callData)
46
-
47
- // Report the call
48
- try {
49
- telecomManager.addNewIncomingCall(phoneAccountHandle, extras)
50
- // Optionally, start foreground service for ongoing call
51
- val intent = android.content.Intent(context, CallForegroundService::class.java)
52
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
53
- context.startForegroundService(intent)
54
- } else {
55
- context.startService(intent)
56
- }
57
- } catch (e: Exception) {
58
- Log.e(TAG, "Failed to report incoming call: ${e.message}")
59
- }
60
- }
61
- }
@@ -1,147 +0,0 @@
1
- package com.qusaieilouti99.callmanager
2
-
3
- import android.app.Notification
4
- import android.app.NotificationChannel
5
- import android.app.NotificationManager
6
- import android.app.PendingIntent
7
- import android.app.Person
8
- import android.content.Context
9
- import android.content.Intent
10
- import android.graphics.Color
11
- import android.media.AudioAttributes
12
- import android.media.RingtoneManager
13
- import android.os.Build
14
- import android.util.Log
15
-
16
- class CallNotificationHelper(private val context: Context) {
17
-
18
- companion object {
19
- const val CHANNEL_ID = "incoming_call_channel"
20
- const val NOTIFICATION_ID = 2001
21
- }
22
-
23
- private var ringtone: android.media.Ringtone? = null
24
-
25
- fun showIncomingCallNotification(callId: String, callerName: String) {
26
- Log.d("CallNotificationHelper", "Showing incoming call notification for $callerName, callId=$callId")
27
- createNotificationChannel()
28
-
29
- // Intent for answer action
30
- val answerIntent = Intent(context, CallNotificationActionReceiver::class.java).apply {
31
- action = "com.qusaieilouti99.callmanager.ANSWER_CALL"
32
- putExtra("callId", callId)
33
- }
34
- val answerPendingIntent = PendingIntent.getBroadcast(
35
- context, 0, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
36
- )
37
-
38
- // Intent for decline action
39
- val declineIntent = Intent(context, CallNotificationActionReceiver::class.java).apply {
40
- action = "com.qusaieilouti99.callmanager.DECLINE_CALL"
41
- putExtra("callId", callId)
42
- }
43
- val declinePendingIntent = PendingIntent.getBroadcast(
44
- context, 1, declineIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
45
- )
46
-
47
- // Full-screen intent to pop up the native call UI
48
- val fullScreenIntent = Intent(context, CallActivity::class.java).apply {
49
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
50
- putExtra("callId", callId)
51
- putExtra("callerName", callerName)
52
- }
53
- val fullScreenPendingIntent = PendingIntent.getActivity(
54
- context, 2, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
55
- )
56
-
57
- val notification: Notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
58
- val person = Person.Builder()
59
- .setName(callerName)
60
- .setImportant(true)
61
- .build()
62
-
63
- Notification.Builder(context, CHANNEL_ID)
64
- .setSmallIcon(android.R.drawable.sym_call_incoming) // <-- REQUIRED!
65
- .setStyle(
66
- Notification.CallStyle.forIncomingCall(
67
- person,
68
- declinePendingIntent, // <-- Decline goes first!
69
- answerPendingIntent // <-- Answer goes second!
70
- )
71
- )
72
- .setFullScreenIntent(fullScreenPendingIntent, true)
73
- .setOngoing(true)
74
- .setAutoCancel(false)
75
- .build()
76
- } else {
77
- Notification.Builder(context, CHANNEL_ID)
78
- .setSmallIcon(android.R.drawable.sym_call_incoming) // <-- REQUIRED!
79
- .setContentTitle("Incoming Call")
80
- .setContentText(callerName)
81
- .setPriority(Notification.PRIORITY_HIGH)
82
- .setCategory(Notification.CATEGORY_CALL)
83
- .setFullScreenIntent(fullScreenPendingIntent, true)
84
- .addAction(android.R.drawable.sym_action_call, "Answer", answerPendingIntent)
85
- .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Decline", declinePendingIntent)
86
- .setOngoing(true)
87
- .setAutoCancel(false)
88
- .build()
89
- }
90
-
91
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
92
- notificationManager.notify(NOTIFICATION_ID, notification)
93
-
94
- playRingtone()
95
- }
96
-
97
- fun cancelIncomingCallNotification() {
98
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
99
- notificationManager.cancel(NOTIFICATION_ID)
100
- stopRingtone()
101
- }
102
-
103
- private fun createNotificationChannel() {
104
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
105
- val channel = NotificationChannel(
106
- CHANNEL_ID,
107
- "Incoming Call Channel",
108
- NotificationManager.IMPORTANCE_HIGH
109
- )
110
- channel.description = "Notifications for incoming calls"
111
- channel.enableLights(true)
112
- channel.lightColor = Color.GREEN
113
- channel.enableVibration(true)
114
- channel.setSound(
115
- RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
116
- AudioAttributes.Builder()
117
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
118
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
119
- .build()
120
- )
121
- val manager = context.getSystemService(NotificationManager::class.java)
122
- manager.createNotificationChannel(channel)
123
- }
124
- }
125
-
126
- private fun playRingtone() {
127
- try {
128
- Log.d("CallNotificationHelper", "Playing ringtone")
129
- val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
130
- ringtone = RingtoneManager.getRingtone(context, uri)
131
- ringtone?.audioAttributes = AudioAttributes.Builder()
132
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
133
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
134
- .build()
135
- ringtone?.play()
136
- } catch (e: Exception) {
137
- Log.e("CallNotificationHelper", "Failed to play ringtone: ${e.message}")
138
- }
139
- }
140
-
141
- fun stopRingtone() {
142
- try {
143
- ringtone?.stop()
144
- } catch (_: Exception) {}
145
- ringtone = null
146
- }
147
- }