rns-nativecall 0.7.9 → 0.8.1
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.
- package/android/src/main/java/com/rnsnativecall/AcceptCallActivity.kt +13 -62
- package/android/src/main/java/com/rnsnativecall/CallActionReceiver.kt +46 -25
- package/android/src/main/java/com/rnsnativecall/CallForegroundService.kt +54 -31
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +0 -1
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +26 -5
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +89 -98
- package/android/src/main/java/com/rnsnativecall/NotificationOverlayActivity.kt +26 -0
- package/android/src/main/java/com/rnsnativecall/UnlockReceiver.kt +22 -0
- package/android/src/main/res/drawable/circle_background.xml +4 -0
- package/android/src/main/res/drawable/ic_call_answer_white.xml +9 -0
- package/android/src/main/res/drawable/ic_call_end_white.xml +9 -0
- package/android/src/main/res/layout/activity_incoming_call.xml +139 -0
- package/index.d.ts +6 -0
- package/index.js +15 -0
- package/ios/CallModule.m +56 -26
- package/package.json +1 -1
- package/withNativeCallVoip.js +130 -68
- package/android/src/main/java/com/rnsnativecall/UnlockPromptActivity.kt +0 -55
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package com.rnsnativecall
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.view.WindowManager
|
|
7
|
+
|
|
8
|
+
class NotificationOverlayActivity : Activity() {
|
|
9
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
10
|
+
super.onCreate(savedInstanceState)
|
|
11
|
+
|
|
12
|
+
// These flags allow the notification pill to show over the lockscreen
|
|
13
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
14
|
+
setShowWhenLocked(true)
|
|
15
|
+
setTurnScreenOn(true)
|
|
16
|
+
} else {
|
|
17
|
+
@Suppress("DEPRECATION")
|
|
18
|
+
window.addFlags(
|
|
19
|
+
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
20
|
+
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
|
|
21
|
+
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
finish()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.rnsnativecall
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
|
|
7
|
+
class UnlockReceiver : BroadcastReceiver() {
|
|
8
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
9
|
+
android.util.Log.d("UnlockReceiver", "Device Unlocked! Action: ${intent.action}")
|
|
10
|
+
|
|
11
|
+
if (intent.action == Intent.ACTION_USER_PRESENT) {
|
|
12
|
+
val activeData = NativeCallManager.getCurrentCallData()
|
|
13
|
+
|
|
14
|
+
if (activeData != null) {
|
|
15
|
+
android.util.Log.d("UnlockReceiver", "Re-triggering call for: ${activeData["name"]}")
|
|
16
|
+
NativeCallManager.handleIncomingPush(context, activeData)
|
|
17
|
+
} else {
|
|
18
|
+
android.util.Log.d("UnlockReceiver", "No active call data found to re-trigger.")
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
android:width="24dp"
|
|
3
|
+
android:height="24dp"
|
|
4
|
+
android:viewportWidth="24"
|
|
5
|
+
android:viewportHeight="24">
|
|
6
|
+
<path
|
|
7
|
+
android:fillColor="#FFFFFF"
|
|
8
|
+
android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57a1.02,1.02 0,0 0,-1.02 0.24l-2.2,2.2a15.05,15.05 0,0 1,-6.59 -6.59l2.2,-2.2a1.02,1.02 0,0 0,0.24 -1.02A11.36,11.36 0,0 1,8.5 4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1c0,9.39 7.61,17 17,17c0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z" />
|
|
9
|
+
</vector>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
android:width="24dp"
|
|
3
|
+
android:height="24dp"
|
|
4
|
+
android:viewportWidth="24"
|
|
5
|
+
android:viewportHeight="24">
|
|
6
|
+
<path
|
|
7
|
+
android:fillColor="#FFFFFF"
|
|
8
|
+
android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.65,1.45 -1.65,2.58c0,1.65 1.34,3 3,3h2v-3H8.1c0.01,-0.99 0.87,-1.8 1.85,-1.8c0.51,0 1,-0.21 1.35,-0.56l1.8,-1.8C13.04,9.3 12.55,9 12,9z" />
|
|
9
|
+
</vector>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<androidx.constraintlayout.widget.ConstraintLayout
|
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
5
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
6
|
+
android:layout_width="match_parent"
|
|
7
|
+
android:layout_height="match_parent"
|
|
8
|
+
android:background="#212121">
|
|
9
|
+
|
|
10
|
+
<!-- Profile Section -->
|
|
11
|
+
<ImageView
|
|
12
|
+
android:id="@+id/profileImage"
|
|
13
|
+
android:layout_width="200dp"
|
|
14
|
+
android:layout_height="200dp"
|
|
15
|
+
android:scaleType="centerCrop"
|
|
16
|
+
android:background="@drawable/circle_background"
|
|
17
|
+
app:layout_constraintTop_toTopOf="parent"
|
|
18
|
+
app:layout_constraintStart_toStartOf="parent"
|
|
19
|
+
app:layout_constraintEnd_toEndOf="parent"
|
|
20
|
+
app:layout_constraintBottom_toTopOf="@id/usernameText"
|
|
21
|
+
app:layout_constraintVertical_chainStyle="packed"
|
|
22
|
+
app:layout_constraintVertical_bias="0.35"
|
|
23
|
+
tools:src="@drawable/ic_profile_placeholder" />
|
|
24
|
+
|
|
25
|
+
<!-- Optional: Blur effect for discreet mode (apply programmatically) -->
|
|
26
|
+
<!-- You can use RenderScript or BlurView library for real blur -->
|
|
27
|
+
|
|
28
|
+
<TextView
|
|
29
|
+
android:id="@+id/usernameText"
|
|
30
|
+
android:layout_width="wrap_content"
|
|
31
|
+
android:layout_height="wrap_content"
|
|
32
|
+
android:text="John Doe"
|
|
33
|
+
android:textColor="#FFFFFF"
|
|
34
|
+
android:textSize="32sp"
|
|
35
|
+
android:fontFamily="sans-serif-medium"
|
|
36
|
+
android:layout_marginTop="20dp"
|
|
37
|
+
app:layout_constraintTop_toBottomOf="@id/profileImage"
|
|
38
|
+
app:layout_constraintStart_toStartOf="parent"
|
|
39
|
+
app:layout_constraintEnd_toEndOf="parent"
|
|
40
|
+
tools:text="John Doe" />
|
|
41
|
+
|
|
42
|
+
<TextView
|
|
43
|
+
android:id="@+id/callStatusText"
|
|
44
|
+
android:layout_width="wrap_content"
|
|
45
|
+
android:layout_height="wrap_content"
|
|
46
|
+
android:text="Incoming Video Call..."
|
|
47
|
+
android:textColor="#FFFFFF"
|
|
48
|
+
android:textSize="18sp"
|
|
49
|
+
android:alpha="0.8"
|
|
50
|
+
android:layout_marginTop="10dp"
|
|
51
|
+
app:layout_constraintTop_toBottomOf="@id/usernameText"
|
|
52
|
+
app:layout_constraintStart_toStartOf="parent"
|
|
53
|
+
app:layout_constraintEnd_toEndOf="parent" />
|
|
54
|
+
|
|
55
|
+
<!-- Action Buttons Container -->
|
|
56
|
+
<LinearLayout
|
|
57
|
+
android:id="@+id/buttonContainer"
|
|
58
|
+
android:layout_width="match_parent"
|
|
59
|
+
android:layout_height="wrap_content"
|
|
60
|
+
android:orientation="horizontal"
|
|
61
|
+
android:gravity="center"
|
|
62
|
+
android:paddingBottom="100dp"
|
|
63
|
+
app:layout_constraintBottom_toBottomOf="parent"
|
|
64
|
+
app:layout_constraintStart_toStartOf="parent"
|
|
65
|
+
app:layout_constraintEnd_toEndOf="parent">
|
|
66
|
+
|
|
67
|
+
<!-- Decline Button -->
|
|
68
|
+
<LinearLayout
|
|
69
|
+
android:layout_width="wrap_content"
|
|
70
|
+
android:layout_height="wrap_content"
|
|
71
|
+
android:orientation="vertical"
|
|
72
|
+
android:gravity="center"
|
|
73
|
+
android:layout_marginEnd="60dp">
|
|
74
|
+
|
|
75
|
+
<androidx.cardview.widget.CardView
|
|
76
|
+
android:layout_width="70dp"
|
|
77
|
+
android:layout_height="70dp"
|
|
78
|
+
app:cardCornerRadius="35dp"
|
|
79
|
+
app:cardElevation="8dp"
|
|
80
|
+
app:cardBackgroundColor="#FF3B30">
|
|
81
|
+
|
|
82
|
+
<ImageView
|
|
83
|
+
android:layout_width="match_parent"
|
|
84
|
+
android:layout_height="match_parent"
|
|
85
|
+
android:src="@drawable/ic_call_end_white"
|
|
86
|
+
android:padding="18dp"
|
|
87
|
+
android:tint="#FFFFFF" />
|
|
88
|
+
|
|
89
|
+
</androidx.cardview.widget.CardView>
|
|
90
|
+
|
|
91
|
+
<TextView
|
|
92
|
+
android:layout_width="wrap_content"
|
|
93
|
+
android:layout_height="wrap_content"
|
|
94
|
+
android:text="Decline"
|
|
95
|
+
android:textColor="#FFFFFF"
|
|
96
|
+
android:textSize="16sp"
|
|
97
|
+
android:fontFamily="sans-serif-medium"
|
|
98
|
+
android:layout_marginTop="10dp" />
|
|
99
|
+
|
|
100
|
+
</LinearLayout>
|
|
101
|
+
|
|
102
|
+
<!-- Accept Button -->
|
|
103
|
+
<LinearLayout
|
|
104
|
+
android:layout_width="wrap_content"
|
|
105
|
+
android:layout_height="wrap_content"
|
|
106
|
+
android:orientation="vertical"
|
|
107
|
+
android:gravity="center"
|
|
108
|
+
android:layout_marginStart="60dp">
|
|
109
|
+
|
|
110
|
+
<androidx.cardview.widget.CardView
|
|
111
|
+
android:layout_width="70dp"
|
|
112
|
+
android:layout_height="70dp"
|
|
113
|
+
app:cardCornerRadius="35dp"
|
|
114
|
+
app:cardElevation="8dp"
|
|
115
|
+
app:cardBackgroundColor="#4CD964">
|
|
116
|
+
|
|
117
|
+
<ImageView
|
|
118
|
+
android:layout_width="match_parent"
|
|
119
|
+
android:layout_height="match_parent"
|
|
120
|
+
android:src="@drawable/ic_call_answer_white"
|
|
121
|
+
android:padding="18dp"
|
|
122
|
+
android:tint="#FFFFFF" />
|
|
123
|
+
|
|
124
|
+
</androidx.cardview.widget.CardView>
|
|
125
|
+
|
|
126
|
+
<TextView
|
|
127
|
+
android:layout_width="wrap_content"
|
|
128
|
+
android:layout_height="wrap_content"
|
|
129
|
+
android:text="Accept"
|
|
130
|
+
android:textColor="#FFFFFF"
|
|
131
|
+
android:textSize="16sp"
|
|
132
|
+
android:fontFamily="sans-serif-medium"
|
|
133
|
+
android:layout_marginTop="10dp" />
|
|
134
|
+
|
|
135
|
+
</LinearLayout>
|
|
136
|
+
|
|
137
|
+
</LinearLayout>
|
|
138
|
+
|
|
139
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
package/index.d.ts
CHANGED
|
@@ -78,6 +78,12 @@ export interface CallHandlerType {
|
|
|
78
78
|
*/
|
|
79
79
|
checkCallStatus(uuid: string): Promise<CallStatus>;
|
|
80
80
|
|
|
81
|
+
requestOverlayPermission(): Promise<boolean>;
|
|
82
|
+
|
|
83
|
+
reportRemoteEnded(uuid: string, reason: number): void;
|
|
84
|
+
|
|
85
|
+
checkOverlayPermission(): Promise<boolean>;
|
|
86
|
+
|
|
81
87
|
/**
|
|
82
88
|
* Forcefully dismisses the native call UI, stops the ringtone, and clears state.
|
|
83
89
|
* Use this when the call is hung up or timed out.
|
package/index.js
CHANGED
|
@@ -47,6 +47,21 @@ export const CallHandler = {
|
|
|
47
47
|
},
|
|
48
48
|
// ------------------------------------
|
|
49
49
|
|
|
50
|
+
reportRemoteEnded: async (uuid, endReason) => {
|
|
51
|
+
if (!CallModule?.reportRemoteEnded) return;
|
|
52
|
+
await CallModule.reportRemoteEnded(uuid.toLowerCase().trim(), endReason);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
requestOverlayPermission: async () => {
|
|
56
|
+
if (!CallModule?.requestOverlayPermission) return false;
|
|
57
|
+
return await CallModule.requestOverlayPermission();
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
checkOverlayPermission: async () => {
|
|
61
|
+
if (!CallModule?.checkOverlayPermission) return false;
|
|
62
|
+
return await CallModule.checkOverlayPermission();
|
|
63
|
+
},
|
|
64
|
+
|
|
50
65
|
displayCall: async (uuid, name, callType = "audio") => {
|
|
51
66
|
if (!CallModule) return false;
|
|
52
67
|
return await CallModule.displayIncomingCall(uuid.toLowerCase().trim(), name, callType);
|
package/ios/CallModule.m
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
@interface CallModule ()
|
|
5
5
|
@property (nonatomic, strong) NSString *pendingCallUuid;
|
|
6
|
+
@property (nonatomic, strong) NSMutableDictionary *pendingEvents;
|
|
6
7
|
@end
|
|
7
8
|
|
|
8
9
|
@implementation CallModule
|
|
@@ -20,6 +21,7 @@ RCT_EXPORT_MODULE();
|
|
|
20
21
|
- (instancetype)init {
|
|
21
22
|
self = [super init];
|
|
22
23
|
if (self) {
|
|
24
|
+
self.pendingEvents = [NSMutableDictionary new];
|
|
23
25
|
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
|
|
24
26
|
|
|
25
27
|
CXProviderConfiguration *config = [[CXProviderConfiguration alloc] initWithLocalizedName:appName];
|
|
@@ -35,6 +37,34 @@ RCT_EXPORT_MODULE();
|
|
|
35
37
|
return self;
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// MARK: - New Parity Methods (Synced with Android)
|
|
41
|
+
|
|
42
|
+
RCT_EXPORT_METHOD(checkCallValidity:(NSString *)uuidString resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
43
|
+
BOOL isCurrent = [uuidString isEqualToString:self.currentCallUUID.UUIDString];
|
|
44
|
+
resolve(@{
|
|
45
|
+
@"isValid": @(isCurrent),
|
|
46
|
+
@"isCanceled": @(!isCurrent)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
RCT_EXPORT_METHOD(getInitialCallData:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
51
|
+
NSMutableDictionary *result = [NSMutableDictionary new];
|
|
52
|
+
|
|
53
|
+
if (self.pendingCallUuid) {
|
|
54
|
+
result[@"default"] = @{@"callUuid": self.pendingCallUuid};
|
|
55
|
+
self.pendingCallUuid = nil;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (self.pendingEvents.count > 0) {
|
|
59
|
+
[result addEntriesFromDictionary:self.pendingEvents];
|
|
60
|
+
[self.pendingEvents removeAllObjects];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
resolve(result.count > 0 ? result : [NSNull null]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - Core Logic
|
|
67
|
+
|
|
38
68
|
RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
|
39
69
|
name:(NSString *)name
|
|
40
70
|
callType:(NSString *)callType
|
|
@@ -50,9 +80,9 @@ RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
|
|
50
80
|
self.currentCallUUID = uuid;
|
|
51
81
|
|
|
52
82
|
AVAudioSession *session = [AVAudioSession sharedInstance];
|
|
53
|
-
[session setCategory:AVAudioSessionCategoryPlayAndRecord
|
|
54
|
-
mode:AVAudioSessionModeVoiceChat
|
|
55
|
-
options:
|
|
83
|
+
[session setCategory:AVAudioSessionCategoryPlayAndRecord
|
|
84
|
+
mode:AVAudioSessionModeVoiceChat
|
|
85
|
+
options:AVAudioSessionCategoryOptionAllowBluetoothHFP | AVAudioSessionCategoryOptionDefaultToSpeaker
|
|
56
86
|
error:nil];
|
|
57
87
|
|
|
58
88
|
CXCallUpdate *update = [[CXCallUpdate alloc] init];
|
|
@@ -68,38 +98,35 @@ RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
|
|
68
98
|
}];
|
|
69
99
|
}
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
{
|
|
101
|
+
// Handles programmatic ending (e.g., via FCM "CANCEL")
|
|
102
|
+
RCT_EXPORT_METHOD(reportRemoteEnded:(NSString *)uuidString reason:(NSInteger)reason) {
|
|
73
103
|
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
74
104
|
if (!uuid) return;
|
|
75
105
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
resolve(@{@"callUuid": self.pendingCallUuid});
|
|
86
|
-
self.pendingCallUuid = nil;
|
|
87
|
-
} else {
|
|
88
|
-
resolve([NSNull null]);
|
|
106
|
+
// Report to CallKit that the remote user ended the call
|
|
107
|
+
[self.provider reportCallWithUUID:uuid
|
|
108
|
+
endedAtDate:[NSDate date]
|
|
109
|
+
reason:(CXCallEndedReason)reason];
|
|
110
|
+
|
|
111
|
+
// Clear local tracking
|
|
112
|
+
if ([uuid isEqual:self.currentCallUUID]) {
|
|
113
|
+
self.currentCallUUID = nil;
|
|
114
|
+
self.pendingCallUuid = nil;
|
|
89
115
|
}
|
|
90
116
|
}
|
|
91
117
|
|
|
92
|
-
RCT_EXPORT_METHOD(
|
|
93
|
-
|
|
118
|
+
RCT_EXPORT_METHOD(endNativeCall:(NSString *)uuidString)
|
|
119
|
+
{
|
|
120
|
+
[self reportRemoteEnded:uuidString reason:CXCallEndedReasonRemoteEnded];
|
|
94
121
|
}
|
|
95
122
|
|
|
96
123
|
// MARK: - CXProviderDelegate
|
|
97
124
|
|
|
98
125
|
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
|
|
99
126
|
AVAudioSession *session = [AVAudioSession sharedInstance];
|
|
100
|
-
[session setCategory:AVAudioSessionCategoryPlayAndRecord
|
|
101
|
-
mode:AVAudioSessionModeVoiceChat
|
|
102
|
-
options:
|
|
127
|
+
[session setCategory:AVAudioSessionCategoryPlayAndRecord
|
|
128
|
+
mode:AVAudioSessionModeVoiceChat
|
|
129
|
+
options:AVAudioSessionCategoryOptionAllowBluetoothHFP | AVAudioSessionCategoryOptionDefaultToSpeaker
|
|
103
130
|
error:nil];
|
|
104
131
|
[session setActive:YES error:nil];
|
|
105
132
|
|
|
@@ -108,9 +135,12 @@ RCT_EXPORT_METHOD(checkTelecomPermissions:(RCTPromiseResolveBlock)resolve reject
|
|
|
108
135
|
NSString *uuidStr = [action.callUUID.UUIDString lowercaseString];
|
|
109
136
|
self.pendingCallUuid = uuidStr;
|
|
110
137
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
// Switch from system UI to App UI
|
|
139
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
140
|
+
[self.provider reportCallWithUUID:action.callUUID
|
|
141
|
+
endedAtDate:[NSDate date]
|
|
142
|
+
reason:CXCallEndedReasonAnsweredElsewhere];
|
|
143
|
+
|
|
114
144
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
115
145
|
[[[UIApplication sharedApplication] keyWindow] makeKeyAndVisible];
|
|
116
146
|
});
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -5,35 +5,54 @@ function withMainActivityDataFix(config) {
|
|
|
5
5
|
return withMainActivity(config, (config) => {
|
|
6
6
|
let contents = config.modResults.contents;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const imports = [
|
|
9
|
+
'import android.content.Intent',
|
|
10
|
+
'import android.os.Bundle',
|
|
11
|
+
'import android.view.WindowManager',
|
|
12
|
+
'import android.os.Build'
|
|
13
|
+
];
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// Add imports if they don't exist
|
|
16
|
+
imports.forEach(imp => {
|
|
17
|
+
if (!contents.includes(imp)) {
|
|
18
|
+
contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
19
21
|
|
|
20
22
|
const onCreateCode = `
|
|
21
23
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
22
24
|
super.onCreate(savedInstanceState)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
26
|
+
setShowWhenLocked(false)
|
|
27
|
+
setTurnScreenOn(true)
|
|
28
|
+
} else {
|
|
29
|
+
window.addFlags(
|
|
30
|
+
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
31
|
+
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
|
|
32
|
+
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
|
33
|
+
)
|
|
34
|
+
}
|
|
25
35
|
if (intent.getBooleanExtra("background_wake", false)) {
|
|
26
36
|
moveTaskToBack(true)
|
|
27
37
|
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
}`;
|
|
39
|
+
|
|
40
|
+
const onNewIntentCode = `
|
|
41
|
+
override fun onNewIntent(intent: Intent) {
|
|
42
|
+
super.onNewIntent(intent)
|
|
43
|
+
setIntent(intent)
|
|
44
|
+
}`;
|
|
45
|
+
|
|
46
|
+
// Use a more flexible regex for the class definition
|
|
47
|
+
const classRegex = /class MainActivity\s*:\s*ReactActivity\(\)\s*\{/;
|
|
48
|
+
|
|
34
49
|
if (!contents.includes('override fun onCreate')) {
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
contents = contents.replace(classRegex, (match) => `${match}${onCreateCode}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!contents.includes('override fun onNewIntent')) {
|
|
54
|
+
// Re-run match check because contents might have changed from onCreate injection
|
|
55
|
+
contents = contents.replace(classRegex, (match) => `${match}${onNewIntentCode}`);
|
|
37
56
|
}
|
|
38
57
|
|
|
39
58
|
config.modResults.contents = contents;
|
|
@@ -47,79 +66,115 @@ function withAndroidConfig(config) {
|
|
|
47
66
|
const manifest = config.modResults;
|
|
48
67
|
const application = manifest.manifest.application[0];
|
|
49
68
|
|
|
50
|
-
//
|
|
69
|
+
// Ensure MainActivity flags
|
|
70
|
+
const mainActivity = application.activity.find((a) => a.$['android:name'] === '.MainActivity');
|
|
71
|
+
if (mainActivity) {
|
|
72
|
+
mainActivity.$['android:launchMode'] = 'singleTop';
|
|
73
|
+
mainActivity.$['android:showWhenLocked'] = 'false';
|
|
74
|
+
mainActivity.$['android:turnScreenOn'] = 'true';
|
|
75
|
+
}
|
|
76
|
+
|
|
51
77
|
const permissions = [
|
|
52
78
|
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
53
79
|
'android.permission.VIBRATE',
|
|
54
80
|
'android.permission.FOREGROUND_SERVICE',
|
|
55
81
|
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
|
|
56
82
|
'android.permission.POST_NOTIFICATIONS',
|
|
57
|
-
'android.permission.SYSTEM_ALERT_WINDOW',
|
|
58
83
|
'android.permission.WAKE_LOCK',
|
|
59
84
|
'android.permission.DISABLE_KEYGUARD',
|
|
60
|
-
'android.permission.
|
|
85
|
+
'android.permission.SYSTEM_ALERT_WINDOW'
|
|
61
86
|
];
|
|
62
87
|
|
|
88
|
+
// Initialize permissions array if missing
|
|
63
89
|
manifest.manifest['uses-permission'] = manifest.manifest['uses-permission'] || [];
|
|
90
|
+
|
|
64
91
|
permissions.forEach((perm) => {
|
|
65
92
|
if (!manifest.manifest['uses-permission'].some((p) => p.$['android:name'] === perm)) {
|
|
66
93
|
manifest.manifest['uses-permission'].push({ $: { 'android:name': perm } });
|
|
67
94
|
}
|
|
68
95
|
});
|
|
69
96
|
|
|
97
|
+
// Initialize components if missing
|
|
98
|
+
application.service = application.service || [];
|
|
70
99
|
application.activity = application.activity || [];
|
|
71
|
-
|
|
72
|
-
if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
73
|
-
application.activity.push({
|
|
74
|
-
$: {
|
|
75
|
-
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
76
|
-
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
77
|
-
'android:excludeFromRecents': 'true',
|
|
78
|
-
'android:noHistory': 'true',
|
|
79
|
-
'android:exported': 'false',
|
|
80
|
-
'android:launchMode': 'singleInstance',
|
|
81
|
-
'android:showWhenLocked': 'true',
|
|
82
|
-
'android:turnScreenOn': 'true'
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
100
|
+
application.receiver = application.receiver || [];
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
// 1. Services
|
|
103
|
+
const services = [
|
|
104
|
+
{ name: 'com.rnsnativecall.CallMessagingService', exported: 'false', filter: 'com.google.firebase.MESSAGING_EVENT' },
|
|
105
|
+
{ name: 'com.rnsnativecall.CallForegroundService', type: 'phoneCall' },
|
|
106
|
+
{ name: 'com.rnsnativecall.CallHeadlessTask' }
|
|
107
|
+
];
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
services.forEach(svc => {
|
|
110
|
+
if (!application.service.some(s => s.$['android:name'] === svc.name)) {
|
|
111
|
+
const entry = { $: { 'android:name': svc.name, 'android:exported': svc.exported || 'false' } };
|
|
112
|
+
if (svc.type) entry.$['android:foregroundServiceType'] = svc.type;
|
|
113
|
+
if (svc.filter) {
|
|
114
|
+
entry['intent-filter'] = [{ action: [{ $: { 'android:name': svc.filter } }] }];
|
|
115
|
+
}
|
|
116
|
+
application.service.push(entry);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
97
119
|
|
|
98
|
-
// 2.
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
// 2. Activities (Trampoline & Overlay)
|
|
121
|
+
const activities = [
|
|
122
|
+
{
|
|
123
|
+
name: 'com.rnsnativecall.AcceptCallActivity',
|
|
124
|
+
theme: '@android:style/Theme.Translucent.NoTitleBar',
|
|
125
|
+
launchMode: 'singleInstance'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'com.rnsnativecall.NotificationOverlayActivity',
|
|
129
|
+
theme: '@android:style/Theme.NoTitleBar.Fullscreen',
|
|
130
|
+
launchMode: 'singleInstance'
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
activities.forEach(act => {
|
|
135
|
+
if (!application.activity.some(a => a.$['android:name'] === act.name)) {
|
|
136
|
+
application.activity.push({
|
|
137
|
+
$: {
|
|
138
|
+
'android:name': act.name,
|
|
139
|
+
'android:theme': act.theme,
|
|
140
|
+
'android:exported': 'false',
|
|
141
|
+
'android:showWhenLocked': 'true',
|
|
142
|
+
'android:turnScreenOn': 'true',
|
|
143
|
+
'android:excludeFromRecents': 'true',
|
|
144
|
+
'android:noHistory': 'true',
|
|
145
|
+
'android:launchMode': act.launchMode
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
105
150
|
|
|
106
|
-
// 3.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
application.service.push({
|
|
151
|
+
// 3. Receiver
|
|
152
|
+
if (!application.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
|
|
153
|
+
application.receiver.push({
|
|
110
154
|
$: {
|
|
111
|
-
'android:name':
|
|
112
|
-
'android:foregroundServiceType': 'phoneCall',
|
|
155
|
+
'android:name': 'com.rnsnativecall.CallActionReceiver',
|
|
113
156
|
'android:exported': 'false'
|
|
114
157
|
}
|
|
115
158
|
});
|
|
116
159
|
}
|
|
117
160
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!application.receiver.some(r => r.$['android:name'] === receiverName)) {
|
|
161
|
+
// ADD THIS: UnlockReceiver with USER_PRESENT filter
|
|
162
|
+
if (!application.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.UnlockReceiver')) {
|
|
121
163
|
application.receiver.push({
|
|
122
|
-
$: {
|
|
164
|
+
$: {
|
|
165
|
+
'android:name': 'com.rnsnativecall.UnlockReceiver',
|
|
166
|
+
'android:exported': 'true',
|
|
167
|
+
'android:enabled': 'true',
|
|
168
|
+
'android:directBootAware': 'true', // Helps bypass some background restrictions
|
|
169
|
+
},
|
|
170
|
+
'intent-filter': [
|
|
171
|
+
{
|
|
172
|
+
$: { 'android:priority': '1000' },
|
|
173
|
+
action: [
|
|
174
|
+
{ $: { 'android:name': 'android.intent.action.USER_PRESENT' } }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
123
178
|
});
|
|
124
179
|
}
|
|
125
180
|
|
|
@@ -132,13 +187,20 @@ function withIosConfig(config) {
|
|
|
132
187
|
return withInfoPlist(config, (config) => {
|
|
133
188
|
const infoPlist = config.modResults;
|
|
134
189
|
if (!infoPlist.UIBackgroundModes) infoPlist.UIBackgroundModes = [];
|
|
135
|
-
|
|
136
|
-
|
|
190
|
+
|
|
191
|
+
['voip', 'audio', 'remote-notification'].forEach(mode => {
|
|
192
|
+
if (!infoPlist.UIBackgroundModes.includes(mode)) {
|
|
193
|
+
infoPlist.UIBackgroundModes.push(mode);
|
|
194
|
+
}
|
|
137
195
|
});
|
|
138
196
|
return config;
|
|
139
197
|
});
|
|
140
198
|
}
|
|
141
199
|
|
|
142
200
|
module.exports = (config) => {
|
|
143
|
-
return withPlugins(config, [
|
|
144
|
-
|
|
201
|
+
return withPlugins(config, [
|
|
202
|
+
withAndroidConfig,
|
|
203
|
+
withMainActivityDataFix,
|
|
204
|
+
withIosConfig
|
|
205
|
+
]);
|
|
206
|
+
};
|