rns-nativecall 0.1.7 → 0.1.9

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.
@@ -9,30 +9,46 @@ import android.os.Bundle
9
9
  class AcceptCallActivity : Activity() {
10
10
  override fun onCreate(savedInstanceState: Bundle?) {
11
11
  super.onCreate(savedInstanceState)
12
-
13
- // 1. CLEAR THE PILL IMMEDIATELY
12
+ NativeCallManager.stopRingtone()
13
+ // 1. CLEAR THE NOTIFICATION
14
+ // Use the same ID (101) used in NativeCallManager
14
15
  val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
15
16
  notificationManager.cancel(101)
16
17
 
17
- // 2. PREPARE DATA FOR JS
18
+ // 2. EXTRACT DATA SAFELY
19
+ // We iterate through all extras and convert them to Strings for the JS Map
18
20
  val dataMap = mutableMapOf<String, String>()
19
- intent.extras?.keySet()?.forEach { key ->
20
- intent.getStringExtra(key)?.let { dataMap[key] = it }
21
+ val extras = intent.extras
22
+ extras?.keySet()?.forEach { key ->
23
+ val value = extras.get(key)
24
+ if (value != null) {
25
+ dataMap[key] = value.toString()
26
+ }
21
27
  }
22
28
 
23
- // 3. SEND EVENT TO REACT NATIVE
29
+ // 3. EMIT EVENT TO REACT NATIVE
30
+ // This handles the case where the app is already in the background/foreground
24
31
  CallModule.sendEventToJS("onCallAccepted", dataMap)
25
32
 
26
- // 4. OPEN THE MAIN APP
33
+ // 4. LAUNCH OR BRING MAIN APP TO FOREGROUND
27
34
  val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
28
- launchIntent?.apply {
29
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
30
- putExtras(intent.extras ?: Bundle())
31
- putExtra("navigatingToCall", true)
35
+ if (launchIntent != null) {
36
+ launchIntent.apply {
37
+ // FLAG_ACTIVITY_SINGLE_TOP: Updates the app if it's already open
38
+ // FLAG_ACTIVITY_NEW_TASK: Required when starting from a non-activity context
39
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
40
+
41
+ // Copy all call data to the launch intent
42
+ putExtras(extras ?: Bundle())
43
+
44
+ // Helper flag for your React Native logic
45
+ putExtra("navigatingToCall", true)
46
+ }
47
+ startActivity(launchIntent)
32
48
  }
33
- startActivity(launchIntent)
34
49
 
35
- // 5. KILL THIS INVISIBLE WINDOW
50
+ // 5. FINISH TRAMPOLINE
51
+ // This ensures this invisible activity doesn't stay in the "Recent Apps" list
36
52
  finish()
37
53
  }
38
54
  }
@@ -8,6 +8,7 @@ import android.os.Bundle
8
8
 
9
9
  class CallActionReceiver : BroadcastReceiver() {
10
10
  override fun onReceive(context: Context, intent: Intent) {
11
+ NativeCallManager.stopRingtone()
11
12
  val uuid = intent.getStringExtra("EXTRA_CALL_UUID")
12
13
 
13
14
  // 1. Clear the notification
@@ -15,6 +16,7 @@ class CallActionReceiver : BroadcastReceiver() {
15
16
  notificationManager.cancel(101)
16
17
 
17
18
  if (intent.action == "ACTION_ACCEPT") {
19
+ NativeCallManager.stopRingtone()
18
20
  // 2. Prepare the data for React Native
19
21
  val dataMap = mutableMapOf<String, String>()
20
22
  intent.extras?.keySet()?.forEach { key ->
@@ -34,6 +36,7 @@ class CallActionReceiver : BroadcastReceiver() {
34
36
  }
35
37
  context.startActivity(launchIntent)
36
38
  } else {
39
+ NativeCallManager.stopRingtone()
37
40
  // Logic for Reject
38
41
  CallModule.sendEventToJS("onCallRejected", mapOf("callUUID" to uuid))
39
42
  }
@@ -1,14 +1,34 @@
1
- ///
2
- // Users/bush/Desktop/Packages/rns-nativecall/android/src/main/java/com/rnsnativecall/CallMessagingService.kt
3
1
  package com.rnsnativecall
4
2
 
3
+ import android.app.ActivityManager
4
+ import android.content.Context
5
5
  import com.google.firebase.messaging.FirebaseMessagingService
6
6
  import com.google.firebase.messaging.RemoteMessage
7
7
 
8
8
  class CallMessagingService : FirebaseMessagingService() {
9
9
 
10
10
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
11
- // Directly forward to your native call manager
12
- NativeCallManager.handleIncomingPush(applicationContext, remoteMessage.data)
11
+ if (isAppInForeground(applicationContext)) {
12
+ // 1. App is OPEN: Don't show the system pill.
13
+ // Just send the data to your React Native listeners.
14
+ CallModule.sendEventToJS("onCallReceived", remoteMessage.data)
15
+ } else {
16
+ // 2. App is CLOSED/BACKGROUND: Show the sticky system pill.
17
+ NativeCallManager.handleIncomingPush(applicationContext, remoteMessage.data)
18
+ }
13
19
  }
14
- }
20
+
21
+ private fun isAppInForeground(context: Context): Boolean {
22
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
23
+ val appProcesses = activityManager.runningAppProcesses ?: return false
24
+ val packageName = context.packageName
25
+
26
+ for (appProcess in appProcesses) {
27
+ if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
28
+ appProcess.processName == packageName) {
29
+ return true
30
+ }
31
+ }
32
+ return false
33
+ }
34
+ }
@@ -1,5 +1,6 @@
1
1
  package com.rnsnativecall
2
2
 
3
+ import android.app.NotificationManager
3
4
  import android.content.ComponentName
4
5
  import android.content.Context
5
6
  import android.content.Intent
@@ -9,11 +10,9 @@ import android.telecom.DisconnectCause
9
10
  import android.telecom.PhoneAccount
10
11
  import android.telecom.PhoneAccountHandle
11
12
  import android.telecom.TelecomManager
13
+ import android.widget.Toast
12
14
  import com.facebook.react.bridge.*
13
15
  import com.facebook.react.modules.core.DeviceEventManagerModule
14
- import android.widget.Toast
15
- import com.google.android.material.snackbar.Snackbar
16
- import android.view.View
17
16
 
18
17
  class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
19
18
 
@@ -30,45 +29,33 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
30
29
  }
31
30
 
32
31
  private fun registerPhoneAccount() {
33
- val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
34
- val phoneAccountHandle = getPhoneAccountHandle()
35
-
36
- val appName = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString()
37
-
38
- // ✅ FIXED: Removed CAPABILITY_CALL_PROVIDER
39
- // Self-managed apps only need these specific flags
40
- val capabilities = PhoneAccount.CAPABILITY_VIDEO_CALLING or
41
- PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING
42
-
43
- val phoneAccount = PhoneAccount.builder(phoneAccountHandle, appName)
44
- .setCapabilities(capabilities)
45
- .setShortDescription(appName)
46
- .addSupportedUriScheme("sip")
47
- .addSupportedUriScheme("tel")
48
- .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
49
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
50
- .build()
51
-
52
- telecomManager.registerPhoneAccount(phoneAccount)
53
- }
54
-
55
- @ReactMethod
56
- fun displayIncomingCall(uuid: String, number: String, name: String, hasVideo: Boolean, playRing: Boolean, promise: Promise) {
57
32
  val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
58
33
  val phoneAccountHandle = getPhoneAccountHandle()
34
+ val appName = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString()
59
35
 
60
- val extras = Bundle().apply {
61
- putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, Uri.fromParts("sip", number, null))
62
- putString("EXTRA_CALL_UUID", uuid)
36
+ val capabilities = PhoneAccount.CAPABILITY_VIDEO_CALLING or
37
+ PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING or
38
+ PhoneAccount.CAPABILITY_SELF_MANAGED
63
39
 
64
- putString("EXTRA_CALLER_NAME", name)
65
- putString(TelecomManager.EXTRA_CALL_SUBJECT, number)
66
- putBoolean("EXTRA_PLAY_RING", false)
67
- putBoolean(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, hasVideo)
40
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, appName)
41
+ .setCapabilities(capabilities)
42
+ .setShortDescription(appName)
43
+ .addSupportedUriScheme("sip")
44
+ .addSupportedUriScheme("tel")
45
+ .build()
68
46
 
69
- }
47
+ telecomManager.registerPhoneAccount(phoneAccount)
48
+ }
49
+
50
+ @ReactMethod
51
+ fun displayIncomingCall(uuid: String, name: String, callType: String, promise: Promise) {
70
52
  try {
71
- telecomManager.addNewIncomingCall(phoneAccountHandle, extras)
53
+ val data = mapOf(
54
+ "callUuid" to uuid,
55
+ "name" to name,
56
+ "callType" to callType
57
+ )
58
+ NativeCallManager.handleIncomingPush(reactApplicationContext, data)
72
59
  promise.resolve(true)
73
60
  } catch (e: Exception) {
74
61
  promise.reject("CALL_ERROR", e.message)
@@ -77,68 +64,57 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
77
64
 
78
65
  @ReactMethod
79
66
  fun endNativeCall(uuid: String) {
67
+ NativeCallManager.stopRingtone()
68
+ val notificationManager = reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
69
+ notificationManager.cancel(101)
70
+
80
71
  val connection = MyConnectionService.getConnection(uuid)
81
72
  connection?.let {
82
- it.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
73
+ it.setDisconnected(DisconnectCause(DisconnectCause.MISSED))
83
74
  it.destroy()
84
75
  MyConnectionService.removeConnection(uuid)
85
76
  }
86
77
  }
87
78
 
88
- //done // val fallbackIntent = Intent(Intent.ACTION_MAIN).apply {
89
- // addCategory(Intent.CATEGORY_LAUNCHER)
90
- // component = ComponentName("com.android.phone", "com.android.phone.CallFeaturesSetting")
91
- // addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
92
- // }
93
- // reactApplicationContext.startActivity(fallbackIntent)
94
-
95
79
  @ReactMethod
96
- fun checkTelecomPermissions(promise: Promise) {
97
- val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
98
- val phoneAccountHandle = getPhoneAccountHandle()
99
- val appName = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString()
100
-
101
- val account = telecomManager.getPhoneAccount(phoneAccountHandle)
102
- if (account != null && account.isEnabled) {
103
- promise.resolve(true)
104
- } else {
105
- try {
106
- // Show a quick message to guide the user
107
- val text = "Tap 'Active calling accounts' and then enable $appName"
108
- val duration = Toast.LENGTH_LONG
109
- val toast = Toast.makeText(reactApplicationContext, text, duration)
110
-
111
- toast.show()
112
-
113
- // Show again after 3.5s
114
- android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
115
- toast.show()
116
- }, 3500)
117
-
118
-
119
- // Open Calling Accounts settings
120
- val intent = Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS)
121
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
122
- reactApplicationContext.startActivity(intent)
80
+ fun checkTelecomPermissions(promise: Promise) {
81
+ val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
82
+ val phoneAccountHandle = getPhoneAccountHandle()
83
+ val appName = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString()
123
84
 
124
- promise.resolve(false)
125
- } catch (e: Exception) {
126
- promise.reject("PERMISSION_ERROR", "Could not open settings: ${e.message}")
85
+ val account = telecomManager.getPhoneAccount(phoneAccountHandle)
86
+ if (account != null && account.isEnabled) {
87
+ promise.resolve(true)
88
+ } else {
89
+ try {
90
+ Toast.makeText(reactApplicationContext, "Tap 'Active calling accounts' and then enable $appName", Toast.LENGTH_LONG).show()
91
+ val intent = Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS)
92
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
93
+ reactApplicationContext.startActivity(intent)
94
+ promise.resolve(false)
95
+ } catch (e: Exception) {
96
+ promise.reject("PERMISSION_ERROR", e.message)
97
+ }
127
98
  }
128
99
  }
129
- }
130
100
 
101
+ @ReactMethod
102
+ fun getInitialCallData(promise: Promise) {
103
+ promise.resolve(pendingCallData)
104
+ pendingCallData = null // Clear after consumption
105
+ }
131
106
 
132
107
  @ReactMethod fun addListener(eventName: String) {}
133
108
  @ReactMethod fun removeListeners(count: Int) {}
134
109
 
135
110
  companion object {
136
111
  private var instance: CallModule? = null
112
+ private var pendingCallData: WritableMap? = null
137
113
 
138
114
  @JvmStatic
139
115
  fun sendEventToJS(eventName: String, params: Any?) {
140
- val reactContext = instance?.reactApplicationContext ?: return
141
-
116
+ val reactContext = instance?.reactApplicationContext
117
+
142
118
  val bridgeData = when (params) {
143
119
  is Map<*, *> -> {
144
120
  val map = Arguments.createMap()
@@ -148,14 +124,21 @@ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
148
124
  map
149
125
  }
150
126
  is String -> {
151
- Arguments.createMap().apply { putString("callUUID", params) }
127
+ Arguments.createMap().apply { putString("callUuid", params) }
152
128
  }
153
129
  else -> null
154
130
  }
155
131
 
156
- reactContext
157
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
158
- ?.emit(eventName, bridgeData)
132
+ if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
133
+ reactContext
134
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
135
+ ?.emit(eventName, bridgeData)
136
+ } else {
137
+ // If app is dead/cold-starting, cache the accept event
138
+ if (eventName == "onCallAccepted") {
139
+ pendingCallData = bridgeData
140
+ }
141
+ }
159
142
  }
160
143
  }
161
144
  }
@@ -7,53 +7,64 @@ import android.content.Context
7
7
  import android.content.Intent
8
8
  import android.os.Build
9
9
  import androidx.core.app.NotificationCompat
10
+ import android.media.Ringtone
11
+ import android.media.RingtoneManager
10
12
 
11
13
  object NativeCallManager {
12
14
 
15
+ private var ringtone: Ringtone? = null
16
+
13
17
  fun handleIncomingPush(context: Context, data: Map<String, String>) {
14
- val uuid = data["callId"] ?: return
18
+ val uuid = data["callUuid"] ?: return
19
+ stopRingtone()
15
20
  val name = data["name"] ?: "Incoming Call"
16
21
  val callType = data["callType"] ?: "audio"
17
22
 
18
- val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
19
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
23
+ // Use MUTABLE flag to ensure extras are properly passed on Android 12+
24
+ val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
25
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
20
26
  } else {
21
27
  PendingIntent.FLAG_UPDATE_CURRENT
22
28
  }
23
29
 
24
- // 1. DUMMY INTENT: This is the key to making the Pill "Sticky".
25
- // It prevents the notification from disappearing but doesn't open a screen.
30
+ // 1. DUMMY INTENT: Keeps the notification "Sticky" (Persistent)
26
31
  val dummyIntent = PendingIntent.getActivity(
27
32
  context,
28
33
  0,
29
34
  Intent(),
30
- pendingFlags
35
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
31
36
  )
32
37
 
33
- // 2. Accept Action
34
- val acceptIntent = Intent(context, AcceptCallActivity::class.java).apply {
35
- action = "ACTION_ACCEPT"
36
- putExtra("EXTRA_CALL_UUID", uuid)
37
- data.forEach { (key, value) -> putExtra(key, value) }
38
- // Crucial for launching from a notification
39
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
40
- }
38
+ // 2. Accept Action - Use unique action and requestCode
39
+ val acceptIntent = Intent(context, AcceptCallActivity::class.java).apply {
40
+ // Unique action string prevents intent caching issues
41
+ action = "ACTION_ACCEPT_$uuid"
42
+ putExtra("EXTRA_CALL_UUID", uuid)
43
+ // Flatten the map into the intent extras
44
+ data.forEach { (key, value) -> putExtra(key, value) }
45
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
46
+ }
47
+
48
+ val acceptPendingIntent = PendingIntent.getActivity(
49
+ context,
50
+ uuid.hashCode(), // Unique RequestCode
51
+ acceptIntent,
52
+ pendingFlags
53
+ )
41
54
 
42
- // Keep this as getActivity
43
- val acceptPendingIntent = PendingIntent.getActivity(
44
- context,
45
- 1001,
46
- acceptIntent,
47
- pendingFlags
48
- )
49
- // 3. Reject Action
55
+ // 3. Reject Action - Use unique action and requestCode
50
56
  val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
51
- action = "ACTION_REJECT"
57
+ action = "ACTION_REJECT_$uuid"
52
58
  putExtra("EXTRA_CALL_UUID", uuid)
53
59
  }
54
- val rejectPendingIntent = PendingIntent.getBroadcast(context, 1002, rejectIntent, pendingFlags)
60
+ val rejectPendingIntent = PendingIntent.getBroadcast(
61
+ context,
62
+ uuid.hashCode() + 1, // Unique RequestCode
63
+ rejectIntent,
64
+ pendingFlags
65
+ )
55
66
 
56
- // 4. Setup Channel (Ensure Importance is HIGH)
67
+ // 4. Setup Channel
57
68
  val channelId = "CALL_CHANNEL_ID"
58
69
  val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
59
70
 
@@ -61,7 +72,6 @@ val acceptPendingIntent = PendingIntent.getActivity(
61
72
  val channel = NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
62
73
  description = "Shows incoming call notifications"
63
74
  enableVibration(true)
64
- // Critical for bypassing "Do Not Disturb"
65
75
  setBypassDnd(true)
66
76
  lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
67
77
  }
@@ -75,15 +85,28 @@ val acceptPendingIntent = PendingIntent.getActivity(
75
85
  .setContentText(name)
76
86
  .setPriority(NotificationCompat.PRIORITY_MAX)
77
87
  .setCategory(NotificationCompat.CATEGORY_CALL)
78
- .setOngoing(true) // Prevents user from swiping it away
88
+ .setOngoing(true)
79
89
  .setAutoCancel(false)
90
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
80
91
 
81
- // This is what keeps the Heads-Up "Pill" visible indefinitely:
92
+ // This hack keeps the Heads-Up "Pill" visible until action is taken
82
93
  .setFullScreenIntent(dummyIntent, true)
83
94
 
84
95
  .addAction(0, "Answer", acceptPendingIntent)
85
96
  .addAction(0, "Decline", rejectPendingIntent)
86
97
 
87
98
  notificationManager.notify(101, builder.build())
99
+
100
+ try {
101
+ val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
102
+ ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
103
+ ringtone?.play()
104
+ } catch (e: Exception) {
105
+ e.printStackTrace()
106
+ }
107
+ }
108
+ fun stopRingtone() {
109
+ ringtone?.stop()
110
+ ringtone = null
88
111
  }
89
112
  }
package/index.d.ts CHANGED
@@ -1,49 +1,41 @@
1
- ///Users/bush/Desktop/Apps/Raiidr/package/index.d.ts
2
-
3
1
  export interface CallData {
4
- callUUID: string;
2
+ callUuid: string;
3
+ name?: string;
4
+ callType?: 'audio' | 'video';
5
+ [key: string]: any; // To allow for custom FCM payload data
5
6
  }
6
7
 
7
8
  export type CallAcceptedCallback = (data: CallData) => void;
8
9
  export type CallRejectedCallback = (data: CallData) => void;
9
- export type CallFailedCallback = (data: CallData) => void;
10
+ export type CallFailedCallback = (data: any) => void;
10
11
 
11
12
  /**
12
13
  * Manually request/check Android permissions for Telecom.
13
- * @returns Promise resolving to true if granted
14
14
  */
15
15
  export function ensureAndroidPermissions(): Promise<boolean>;
16
16
 
17
17
  export interface CallHandlerType {
18
18
  /**
19
- * Display an incoming call UI using ConnectionService (Android) or CallKit (iOS).
19
+ * Display the Sticky Pill notification UI.
20
20
  * @param uuid Unique call identifier
21
- * @param number Caller number (or URI)
22
21
  * @param name Caller display name
23
- * @param hasVideo True if video call (default: false)
24
- * @param shouldRing True to play native ringtone (default: true)
25
- * @returns Promise resolving to true if successfully displayed
22
+ * @param callType 'audio' or 'video'
26
23
  */
27
24
  displayCall(
28
25
  uuid: string,
29
- number: string,
30
26
  name: string,
31
- hasVideo?: boolean,
32
- shouldRing?: boolean
27
+ callType: 'audio' | 'video',
33
28
  ): Promise<boolean>;
34
29
 
35
30
  /**
36
- * Dismiss the native call UI (e.g., if the caller hangs up before the user answers).
37
- * @param uuid Call identifier
31
+ * Dismiss the native call UI (Sticky Pill).
38
32
  */
39
33
  destroyNativeCallUI(uuid: string): void;
40
34
 
41
35
  /**
42
- * Subscribe to call events from the native side.
43
- * @param onAccept Callback when user presses Answer
44
- * @param onReject Callback when user presses Decline
45
- * @param onFailed Optional callback for system-level failures
46
- * @returns Function to unsubscribe all listeners
36
+ * Subscribe to call events. Automatically checks for cold-start data
37
+ * if the app was opened via the Answer button.
38
+ * @returns Function to unsubscribe
47
39
  */
48
40
  subscribe(
49
41
  onAccept: CallAcceptedCallback,
package/index.js CHANGED
@@ -1,4 +1,3 @@
1
- ///Users/bush/Desktop/Apps/Raiidr/package/index.js
2
1
  import {
3
2
  NativeModules,
4
3
  NativeEventEmitter,
@@ -8,11 +7,10 @@ import {
8
7
 
9
8
  const { CallModule } = NativeModules;
10
9
 
11
- // Safety check for the Native Module
12
10
  if (!CallModule && __DEV__) {
13
11
  console.warn(
14
12
  "rns-nativecall: NativeModule.CallModule is undefined. " +
15
- "Make sure you have rebuilt your native project (npx expo run:android / ios)."
13
+ "Make sure you have rebuilt your native project."
16
14
  );
17
15
  }
18
16
 
@@ -25,59 +23,68 @@ const REQUIRED_PERMISSIONS = Platform.OS === 'android' ? [
25
23
 
26
24
  export async function ensureAndroidPermissions() {
27
25
  if (Platform.OS !== 'android') return true;
28
- const result = await PermissionsAndroid.requestMultiple(REQUIRED_PERMISSIONS);
29
-
30
- // This triggers the "Calling Accounts" system settings if not enabled
31
- const isAccountEnabled = await CallModule.checkTelecomPermissions();
32
- if (!isAccountEnabled) return false;
33
-
34
- return Object.values(result).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
26
+ try {
27
+ const result = await PermissionsAndroid.requestMultiple(REQUIRED_PERMISSIONS);
28
+ const isAccountEnabled = await CallModule.checkTelecomPermissions();
29
+ if (!isAccountEnabled) return false;
30
+ return Object.values(result).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
31
+ } catch (e) {
32
+ return false;
33
+ }
35
34
  }
36
35
 
37
36
  export const CallHandler = {
38
- displayCall: async (uuid, number, name, hasVideo = false, shouldRing = true) => {
37
+ displayCall: async (uuid, name, callType = "audio") => {
39
38
  if (!CallModule) return false;
40
-
41
- // if (Platform.OS === 'android') {
42
- // const hasPerms = await ensureAndroidPermissions();
43
- // if (!hasPerms) return false;
44
- // }
45
-
46
39
  try {
47
40
  return await CallModule.displayIncomingCall(
48
41
  uuid.toLowerCase().trim(),
49
- number,
50
42
  name,
51
- hasVideo,
52
- shouldRing
43
+ callType
53
44
  );
54
45
  } catch (e) {
55
- // console.log("Native Call Error:", e);
56
46
  return false;
57
47
  }
58
48
  },
59
49
 
60
50
  destroyNativeCallUI: (uuid) => {
61
51
  if (CallModule?.endNativeCall) {
62
- CallModule.endNativeCall(uuid.toLowerCase());
52
+ CallModule.endNativeCall(uuid.toLowerCase().trim());
63
53
  }
64
54
  },
65
55
 
56
+ /**
57
+ * Subscribes to call events and checks for initial cold-start data.
58
+ */
66
59
  subscribe: (onAccept, onReject, onFailed) => {
67
60
  if (!callEventEmitter) return () => { };
68
61
 
69
62
  const subs = [
70
- callEventEmitter.addListener('onCallAccepted', onAccept),
71
- callEventEmitter.addListener('onCallRejected', onReject),
63
+ callEventEmitter.addListener('onCallAccepted', (data) => {
64
+ onAccept(data);
65
+ }),
66
+ callEventEmitter.addListener('onCallRejected', (data) => {
67
+ onReject(data);
68
+ }),
72
69
  ];
73
70
 
74
71
  if (onFailed) {
75
72
  subs.push(callEventEmitter.addListener('onCallFailed', onFailed));
76
73
  }
77
74
 
75
+ // --- COLD START LOGIC ---
76
+ // If the app was killed and opened via the Answer button,
77
+ // the event might have fired before this listener was ready.
78
+ if (Platform.OS === 'android' && CallModule.getInitialCallData) {
79
+ CallModule.getInitialCallData().then((data) => {
80
+ if (data) {
81
+ onAccept(data);
82
+ }
83
+ });
84
+ }
85
+
78
86
  return () => subs.forEach(s => s.remove());
79
87
  }
80
88
  };
81
89
 
82
- // IMPORTANT: Exporting as default ensures "import CallHandler from '...'" works
83
90
  export default CallHandler;
package/ios/CallModule.h CHANGED
@@ -1,6 +1,10 @@
1
1
  #import <React/RCTBridgeModule.h>
2
2
  #import <React/RCTEventEmitter.h>
3
3
  #import <CallKit/CallKit.h>
4
+ #import <AVFoundation/AVFoundation.h>
5
+
4
6
  @interface CallModule : RCTEventEmitter <RCTBridgeModule, CXProviderDelegate>
5
7
  @property(nonatomic, strong) CXProvider *provider;
8
+ // Add this property
9
+ @property(nonatomic, strong) NSUUID *currentCallUUID;
6
10
  @end
package/ios/CallModule.m CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  RCT_EXPORT_MODULE();
6
6
 
7
+ // Crucial: This keeps the module alive and on the main thread
8
+ + (BOOL)requiresMainQueueSetup {
9
+ return YES;
10
+ }
11
+
7
12
  - (NSArray<NSString *> *)supportedEvents {
8
13
  return @[ @"onCallAccepted", @"onCallRejected", @"onCallFailed" ];
9
14
  }
@@ -11,22 +16,15 @@ RCT_EXPORT_MODULE();
11
16
  - (instancetype)init {
12
17
  self = [super init];
13
18
  if (self) {
14
- // Dynamically get the App Name from the bundle so it's not hardcoded
15
- NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
16
- if (!appName) {
17
- appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
18
- }
19
+ NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
19
20
 
20
- #pragma clang diagnostic push
21
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
22
21
  CXProviderConfiguration *config = [[CXProviderConfiguration alloc] initWithLocalizedName:appName];
23
- #pragma clang diagnostic pop
24
-
25
22
  config.supportsVideo = YES;
26
23
  config.maximumCallGroups = 1;
27
24
  config.maximumCallsPerCallGroup = 1;
28
25
  config.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypeGeneric)];
29
- config.includesCallsInRecents = NO;
26
+ // Add this to prevent system confusion
27
+ config.ringtoneSound = @"Ringtone.caf";
30
28
 
31
29
  self.provider = [[CXProvider alloc] initWithConfiguration:config];
32
30
  [self.provider setDelegate:self queue:nil];
@@ -34,32 +32,33 @@ RCT_EXPORT_MODULE();
34
32
  return self;
35
33
  }
36
34
 
37
- // Matches the 5 arguments in your JS/Android implementation
38
35
  RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
39
- number:(NSString *)number
40
36
  name:(NSString *)name
41
- hasVideo:(BOOL)hasVideo
42
- showRing:(BOOL)showRing
37
+ callType:(NSString *)callType
43
38
  resolve:(RCTPromiseResolveBlock)resolve
44
39
  reject:(RCTPromiseRejectBlock)reject)
45
40
  {
46
41
  NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
47
42
  if (!uuid) {
48
- reject(@"INVALID_UUID", @"The provided UUID string is invalid", nil);
43
+ reject(@"INVALID_UUID", @"Invalid UUID", nil);
49
44
  return;
50
45
  }
46
+
47
+ self.currentCallUUID = uuid;
48
+
49
+ // 1. CONFIGURE AUDIO SESSION FIRST (Prevents Auto-Drop)
50
+ AVAudioSession *session = [AVAudioSession sharedInstance];
51
+ NSError *error = nil;
52
+ [session setCategory:AVAudioSessionCategoryPlayAndRecord
53
+ mode:AVAudioSessionModeVoiceChat
54
+ options:AVAudioSessionCategoryOptionAllowBluetooth
55
+ error:&error];
51
56
 
52
57
  CXCallUpdate *update = [[CXCallUpdate alloc] init];
53
- // We use 'name' here so the system UI shows the caller's name instead of just their number
54
58
  update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:name];
55
- update.hasVideo = hasVideo;
56
- update.supportsGrouping = NO;
57
- update.supportsUngrouping = NO;
58
- update.supportsHolding = NO;
59
-
60
- [self.provider reportNewIncomingCallWithUUID:uuid
61
- update:update
62
- completion:^(NSError *_Nullable error) {
59
+ update.hasVideo = [callType isEqualToString:@"video"];
60
+
61
+ [self.provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
63
62
  if (error) {
64
63
  reject(@"CALL_ERROR", error.localizedDescription, error);
65
64
  } else {
@@ -68,35 +67,25 @@ RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
68
67
  }];
69
68
  }
70
69
 
71
- RCT_EXPORT_METHOD(endNativeCall:(NSString *)uuidString) {
72
- NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
73
- if (uuid) {
74
- [self.provider reportCallWithUUID:uuid
75
- endedAtDate:[NSDate date]
76
- reason:CXCallEndedReasonRemoteEnded];
77
- }
78
- }
79
-
80
- // Placeholder for Android parity
81
- RCT_EXPORT_METHOD(checkTelecomPermissions:(RCTPromiseResolveBlock)resolve
82
- reject:(RCTPromiseRejectBlock)reject) {
83
- resolve(@YES);
84
- }
85
-
86
70
  // MARK: - CXProviderDelegate
87
71
 
88
72
  - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
73
+ // 2. ACTIVATE AUDIO SESSION (Crucial for CallKit)
74
+ [[AVAudioSession sharedInstance] setActive:YES error:nil];
75
+
89
76
  [action fulfill];
90
- [self sendEventWithName:@"onCallAccepted"
91
- body:@{@"callUUID" : [action.callUUID.UUIDString lowercaseString]}];
77
+ [self sendEventWithName:@"onCallAccepted" body:@{@"callUuid": [action.callUUID.UUIDString lowercaseString]}];
92
78
  }
93
79
 
94
80
  - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
95
81
  [action fulfill];
96
- [self sendEventWithName:@"onCallRejected"
97
- body:@{@"callUUID" : [action.callUUID.UUIDString lowercaseString]}];
82
+ self.currentCallUUID = nil;
83
+ [self sendEventWithName:@"onCallRejected" body:@{@"callUuid": [action.callUUID.UUIDString lowercaseString]}];
98
84
  }
99
85
 
100
- - (void)providerDidReset:(CXProvider *)provider { }
86
+ - (void)providerDidReset:(CXProvider *)provider {
87
+ // Stop all audio if provider resets
88
+ [[AVAudioSession sharedInstance] setActive:NO error:nil];
89
+ }
101
90
 
102
91
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "RNS nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -19,6 +19,7 @@
19
19
  "p": "npm publish --access public"
20
20
  },
21
21
  "expo": {
22
+ "autolink": true,
22
23
  "plugins": [
23
24
  "./app.plugin.js"
24
25
  ]
@@ -1,11 +1,40 @@
1
- const { withAndroidManifest, withInfoPlist, withPlugins } = require('@expo/config-plugins');
1
+ const { withAndroidManifest, withInfoPlist, withPlugins, withMainActivity } = require('@expo/config-plugins');
2
2
 
3
+ /** 1. ANDROID MAIN ACTIVITY MOD **/
4
+ function withMainActivityDataFix(config) {
5
+ return withMainActivity(config, (config) => {
6
+ let contents = config.modResults.contents;
7
+
8
+ // Ensure Intent import exists
9
+ if (!contents.includes('import android.content.Intent')) {
10
+ contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
11
+ }
12
+
13
+ // Add onNewIntent to catch data when app is open
14
+ const onNewIntentCode = `
15
+ override fun onNewIntent(intent: Intent) {
16
+ super.onNewIntent(intent)
17
+ setIntent(intent)
18
+ }
19
+ `;
20
+
21
+ if (!contents.includes('override fun onNewIntent')) {
22
+ const lastBraceIndex = contents.lastIndexOf('}');
23
+ contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
24
+ }
25
+
26
+ config.modResults.contents = contents;
27
+ return config;
28
+ });
29
+ }
30
+
31
+ /** 2. ANDROID MANIFEST CONFIG **/
3
32
  function withAndroidConfig(config) {
4
33
  return withAndroidManifest(config, (config) => {
5
34
  const manifest = config.modResults;
6
35
  const application = manifest.manifest.application[0];
7
36
 
8
- // 1. Permissions
37
+ // Permissions
9
38
  const permissions = [
10
39
  'android.permission.READ_PHONE_NUMBERS',
11
40
  'android.permission.CALL_PHONE',
@@ -23,22 +52,16 @@ function withAndroidConfig(config) {
23
52
  }
24
53
  });
25
54
 
26
- // 2. Activities (UI Components)
55
+ // Activities
27
56
  application.activity = application.activity || [];
28
57
 
29
- // IncomingCallActivity (Optional lock screen UI)
30
- if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.IncomingCallActivity')) {
31
- application.activity.push({
32
- $: {
33
- 'android:name': 'com.rnsnativecall.IncomingCallActivity',
34
- 'android:showOnLockScreen': 'true',
35
- 'android:launchMode': 'singleInstance',
36
- 'android:theme': '@style/Theme.AppCompat.Light.NoActionBar'
37
- }
38
- });
58
+ // Ensure MainActivity is singleTask
59
+ const mainActivity = application.activity.find(a => a.$['android:name'] === '.MainActivity');
60
+ if (mainActivity) {
61
+ mainActivity.$['android:launchMode'] = 'singleTask';
39
62
  }
40
63
 
41
- // AcceptCallActivity (The "Trampoline" that fixes the Answer button)
64
+ // AcceptCallActivity (The Trampoline - Must remain in Manifest)
42
65
  if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
43
66
  application.activity.push({
44
67
  $: {
@@ -52,7 +75,7 @@ function withAndroidConfig(config) {
52
75
  });
53
76
  }
54
77
 
55
- // 3. Services (FCM and Telecom)
78
+ // Services
56
79
  application.service = application.service || [];
57
80
  const services = [
58
81
  { name: 'com.rnsnativecall.MyConnectionService', permission: 'android.permission.BIND_CONNECTION_SERVICE', action: 'android.telecom.ConnectionService' },
@@ -62,13 +85,13 @@ function withAndroidConfig(config) {
62
85
  services.forEach(svc => {
63
86
  if (!application.service.some(s => s.$['android:name'] === svc.name)) {
64
87
  application.service.push({
65
- $: { 'android:name': svc.name, 'android:exported': svc.permission ? 'true' : 'false', 'android:permission': svc.permission },
88
+ $: { 'android:name': svc.name, 'android:exported': 'true', 'android:permission': svc.permission },
66
89
  'intent-filter': [{ action: [{ $: { 'android:name': svc.action } }] }]
67
90
  });
68
91
  }
69
92
  });
70
93
 
71
- // 4. Receivers (The Decline Button)
94
+ // Receivers
72
95
  application.receiver = application.receiver || [];
73
96
  if (!application.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
74
97
  application.receiver.push({
@@ -80,7 +103,7 @@ function withAndroidConfig(config) {
80
103
  });
81
104
  }
82
105
 
83
- /** IOS Config remains the same **/
106
+ /** 3. IOS CONFIG **/
84
107
  function withIosConfig(config) {
85
108
  return withInfoPlist(config, (config) => {
86
109
  const infoPlist = config.modResults;
@@ -91,5 +114,5 @@ function withIosConfig(config) {
91
114
  }
92
115
 
93
116
  module.exports = (config) => {
94
- return withPlugins(config, [withAndroidConfig, withIosConfig]);
117
+ return withPlugins(config, [withAndroidConfig, withMainActivityDataFix, withIosConfig]);
95
118
  };
@@ -1,74 +0,0 @@
1
- package com.rnsnativecall
2
-
3
- import android.app.NotificationManager
4
- import android.content.Context
5
- import android.os.Bundle
6
- import android.view.View
7
- import android.view.WindowManager
8
- import android.widget.ImageView
9
- import android.widget.TextView
10
- import androidx.appcompat.app.AppCompatActivity
11
- import com.bumptech.glide.Glide
12
-
13
- class IncomingCallActivity : AppCompatActivity() {
14
-
15
- override fun onCreate(savedInstanceState: Bundle?) {
16
- super.onCreate(savedInstanceState)
17
-
18
- // 1. Show over lockscreen
19
- window.addFlags(
20
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
21
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
22
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
23
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
24
- )
25
-
26
- // 2. Set the layout FIRST
27
- setContentView(R.layout.activity_incoming_call)
28
-
29
- // 3. Extract data from Intent
30
- val uuid = intent.getStringExtra("EXTRA_CALL_UUID")
31
- val name = intent.getStringExtra("EXTRA_CALLER_NAME") ?: "Unknown"
32
- val profileUrl = intent.getStringExtra("profile")
33
- val callType = intent.getStringExtra("callType") ?: "audio"
34
-
35
- // 4. Initialize Views
36
- val imageView = findViewById<ImageView>(R.id.caller_image)
37
- val nameText = findViewById<TextView>(R.id.caller_name)
38
- val typeText = findViewById<TextView>(R.id.call_type)
39
-
40
- nameText.text = name
41
- typeText.text = if (callType == "video") "Incoming Video..." else "Incoming Audio..."
42
-
43
- // 5. Load Image with Glide
44
- if (!profileUrl.isNullOrEmpty()) {
45
- Glide.with(this)
46
- .load(profileUrl)
47
- .circleCrop()
48
- .into(imageView)
49
- }
50
-
51
- // 6. Handle buttons
52
- findViewById<View>(R.id.btn_accept).setOnClickListener {
53
- dismissCall()
54
- CallModule.sendEventToJS("onCallAccepted", mapOf("callUUID" to uuid))
55
- finish()
56
- }
57
-
58
- findViewById<View>(R.id.btn_reject).setOnClickListener {
59
- dismissCall()
60
- CallModule.sendEventToJS("onCallRejected", mapOf("callUUID" to uuid))
61
- finish()
62
- }
63
- }
64
-
65
- private fun dismissCall() {
66
- // This removes the "Pill" notification from the status bar
67
- val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
68
- notificationManager.cancel(101) // Use the same ID you used in NativeCallManager
69
- }
70
-
71
- override fun onBackPressed() {
72
- // Disable back button so they have to click Accept or Reject
73
- }
74
- }
@@ -1,4 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
2
- <solid android:color="#34C759" />
3
- <corners android:radius="21dp" />
4
- </shape>
@@ -1,4 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
2
- <solid android:color="#FF3B30" />
3
- <corners android:radius="21dp" />
4
- </shape>
@@ -1,5 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
2
- <solid android:color="#212121" />
3
- <corners android:radius="100dp" />
4
- <stroke android:width="1dp" android:color="#33FFFFFF" />
5
- </shape>
@@ -1,67 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
- xmlns:app="http://schemas.android.com/apk/res-auto"
4
- android:layout_width="match_parent"
5
- android:layout_height="match_parent"
6
- android:background="#00000000">
7
- <LinearLayout
8
- android:layout_width="match_parent"
9
- android:layout_height="wrap_content"
10
- android:layout_marginHorizontal="8dp"
11
- android:layout_marginTop="30dp"
12
- android:background="@drawable/pill_background"
13
- android:elevation="15dp"
14
- android:orientation="horizontal"
15
- android:padding="12dp"
16
- android:gravity="center_vertical">
17
-
18
- <com.google.android.material.imageview.ShapeableImageView
19
- android:id="@+id/caller_image"
20
- android:layout_width="50dp"
21
- android:layout_height="50dp"
22
- android:background="#ccc"
23
- app:shapeAppearanceOverlay="@style/CircleImage" />
24
-
25
- <LinearLayout
26
- android:layout_width="0dp"
27
- android:layout_height="wrap_content"
28
- android:layout_weight="1"
29
- android:layout_marginStart="15dp"
30
- android:orientation="vertical">
31
-
32
- <TextView
33
- android:id="@+id/caller_name"
34
- android:layout_width="wrap_content"
35
- android:layout_height="wrap_content"
36
- android:text="Caller Name"
37
- android:textColor="#FFFFFF"
38
- android:textSize="16sp"
39
- android:textStyle="bold" />
40
-
41
- <TextView
42
- android:id="@+id/call_type"
43
- android:layout_width="wrap_content"
44
- android:layout_height="wrap_content"
45
- android:text="Incoming Audio..."
46
- android:textColor="#FFFFFF"
47
- android:textSize="13sp" />
48
- </LinearLayout>
49
-
50
- <ImageButton
51
- android:id="@+id/btn_accept"
52
- android:layout_width="42dp"
53
- android:layout_height="42dp"
54
- android:background="@drawable/circle_green"
55
- android:src="@android:drawable/ic_menu_call"
56
- android:layout_marginEnd="12dp"
57
- android:tint="#FFFFFF" />
58
-
59
- <ImageButton
60
- android:id="@+id/btn_reject"
61
- android:layout_width="42dp"
62
- android:layout_height="42dp"
63
- android:background="@drawable/circle_red"
64
- android:src="@android:drawable/ic_menu_close_clear_cancel"
65
- android:tint="#FFFFFF" />
66
- </LinearLayout>
67
- </FrameLayout>
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <resources>
3
- <style name="CircleImage" parent="">
4
- <item name="cornerSize">50%</item>
5
- </style>
6
-
7
- <style name="Theme.IncomingCall" parent="Theme.AppCompat.Light.NoActionBar">
8
- <item name="android:windowBackground">@android:color/transparent</item>
9
- <item name="android:windowIsTranslucent">true</item>
10
- <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
11
- </style>
12
- </resources>