@stream-io/react-native-callingx 0.1.1-beta.1 → 0.1.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/CHANGELOG.md +15 -0
- package/README.md +45 -357
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +9 -3
- package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +9 -15
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +18 -6
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +41 -18
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +24 -0
- package/package.json +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
|
+
|
|
5
|
+
## [0.1.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/react-native-callingx-0.1.0...@stream-io/react-native-callingx-0.1.1) (2026-04-09)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- callingx docs update ([#2195](https://github.com/GetStream/stream-video-js/issues/2195)) ([7a6b632](https://github.com/GetStream/stream-video-js/commit/7a6b632270ec1187236a0e4e5c5396a98a20fd16))
|
|
10
|
+
|
|
11
|
+
## 0.1.0 (2026-04-09)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- callkit/telecom integration ([#2028](https://github.com/GetStream/stream-video-js/issues/2028)) ([d579acd](https://github.com/GetStream/stream-video-js/commit/d579acd1975fb4945e40452b27e372694c737628))
|
package/README.md
CHANGED
|
@@ -1,395 +1,83 @@
|
|
|
1
|
-
# react-native-callingx
|
|
1
|
+
# @stream-io/react-native-callingx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native native-calling bridge for:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- iOS CallKit
|
|
6
|
+
- Android Telecom/ConnectionService
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- 📲 **Outgoing call registration** — Register outgoing calls with the system
|
|
9
|
-
- 🎛️ **Call controls** — Mute, hold, end calls with native system integration
|
|
10
|
-
- 🔔 **Custom notifications** — Configurable Android notification channels
|
|
11
|
-
- ⚡ **Turbo Module** — Built with the New Architecture for optimal performance
|
|
12
|
-
- 📱 **Background support** — Handle calls when the app is backgrounded or killed
|
|
13
|
-
|
|
14
|
-
## Requirements
|
|
15
|
-
|
|
16
|
-
- React Native 0.73+ (New Architecture / Turbo Modules)
|
|
17
|
-
- iOS 13.0+
|
|
18
|
-
- Android API 26+ (Android 8.0 Oreo)
|
|
19
|
-
|
|
20
|
-
## Installation
|
|
8
|
+
## Install
|
|
21
9
|
|
|
22
10
|
```sh
|
|
23
|
-
npm install @stream-io/react-native-callingx
|
|
24
|
-
# or
|
|
25
11
|
yarn add @stream-io/react-native-callingx
|
|
26
12
|
```
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
1. Add the required background modes to your `Info.plist`:
|
|
31
|
-
|
|
32
|
-
```xml
|
|
33
|
-
<key>UIBackgroundModes</key>
|
|
34
|
-
<array>
|
|
35
|
-
<string>voip</string>
|
|
36
|
-
<string>audio</string>
|
|
37
|
-
</array>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
2. Run pod install:
|
|
14
|
+
Then run iOS pods:
|
|
41
15
|
|
|
42
16
|
```sh
|
|
43
17
|
cd ios && pod install
|
|
44
18
|
```
|
|
45
19
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```objc
|
|
49
|
-
#import <Callingx/CallingxPublic.h>
|
|
50
|
-
|
|
51
|
-
- (void)pushRegistry:(PKPushRegistry *)registry
|
|
52
|
-
didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
|
|
53
|
-
forType:(PKPushType)type
|
|
54
|
-
withCompletionHandler:(void (^)(void))completion {
|
|
55
|
-
|
|
56
|
-
// Extract call information from payload
|
|
57
|
-
NSString *callId = payload.dictionaryPayload[@"call_id"];
|
|
58
|
-
NSString *callerName = payload.dictionaryPayload[@"caller_name"];
|
|
59
|
-
NSString *handle = payload.dictionaryPayload[@"handle"];
|
|
60
|
-
BOOL hasVideo = [payload.dictionaryPayload[@"has_video"] boolValue];
|
|
61
|
-
|
|
62
|
-
[Callingx reportNewIncomingCall:callId
|
|
63
|
-
handle:handle
|
|
64
|
-
handleType:@"generic"
|
|
65
|
-
hasVideo:hasVideo
|
|
66
|
-
localizedCallerName:callerName
|
|
67
|
-
supportsHolding:YES
|
|
68
|
-
supportsDTMF:NO
|
|
69
|
-
supportsGrouping:NO
|
|
70
|
-
supportsUngrouping:NO
|
|
71
|
-
payload:payload.dictionaryPayload
|
|
72
|
-
withCompletionHandler:completion];
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Android Setup
|
|
20
|
+
## Quick start
|
|
77
21
|
|
|
78
|
-
|
|
22
|
+
```ts
|
|
23
|
+
import { CallingxModule } from '@stream-io/react-native-callingx';
|
|
79
24
|
|
|
80
|
-
### Setup
|
|
81
|
-
|
|
82
|
-
Initialize the module with platform-specific configuration:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import { CallingxModule } from 'react-native-callingx';
|
|
86
|
-
|
|
87
|
-
// Setup must be called before any other method
|
|
88
25
|
CallingxModule.setup({
|
|
89
26
|
ios: {
|
|
90
|
-
appName: 'My App',
|
|
91
27
|
supportsVideo: true,
|
|
92
|
-
|
|
93
|
-
maximumCallGroups: 1,
|
|
94
|
-
handleType: 'generic', // 'generic' | 'number' | 'phone' | 'email'
|
|
28
|
+
callsHistory: false,
|
|
95
29
|
},
|
|
96
30
|
android: {
|
|
97
31
|
incomingChannel: {
|
|
98
|
-
id: '
|
|
99
|
-
name: 'Incoming
|
|
100
|
-
sound: 'ringtone', // optional custom sound
|
|
101
|
-
vibration: true,
|
|
32
|
+
id: 'incoming_calls_channel',
|
|
33
|
+
name: 'Incoming calls',
|
|
102
34
|
},
|
|
103
|
-
outgoingChannel: {
|
|
104
|
-
id: 'ongoing_calls',
|
|
105
|
-
name: 'Ongoing Calls',
|
|
106
|
-
},
|
|
107
|
-
// Optional: transform display text
|
|
108
|
-
titleTransformer: (name) => `Call from ${name}`,
|
|
109
|
-
subtitleTransformer: (phoneNumber) => phoneNumber,
|
|
110
35
|
},
|
|
111
36
|
});
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Request Permissions
|
|
115
37
|
|
|
116
|
-
Before displaying calls, request the required permissions:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
const permissions = await CallingxModule.requestPermissions();
|
|
120
|
-
console.log('Audio permission:', permissions.recordAudio);
|
|
121
|
-
console.log('Notification permission:', permissions.postNotifications);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Display Incoming Call
|
|
125
|
-
|
|
126
|
-
Show the native incoming call UI:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
38
|
await CallingxModule.displayIncomingCall(
|
|
130
|
-
'
|
|
131
|
-
'+
|
|
132
|
-
'John Doe', // caller name
|
|
133
|
-
true, // has video
|
|
134
|
-
);
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Start Outgoing Call
|
|
138
|
-
|
|
139
|
-
Register an outgoing call with the system:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
await CallingxModule.startCall(
|
|
143
|
-
'unique-call-id',
|
|
144
|
-
'+1234567890',
|
|
39
|
+
'call-id',
|
|
40
|
+
'+123456789',
|
|
145
41
|
'John Doe',
|
|
146
|
-
|
|
42
|
+
true,
|
|
147
43
|
);
|
|
148
44
|
```
|
|
149
45
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
Answer an incoming call programmatically:
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
await CallingxModule.answerIncomingCall('unique-call-id');
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Activate Call
|
|
159
|
-
|
|
160
|
-
Mark a call as active (connected):
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
await CallingxModule.setCurrentCallActive('unique-call-id');
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### End Call
|
|
167
|
-
|
|
168
|
-
End a call with a specific reason:
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import type { EndCallReason } from 'react-native-callingx';
|
|
172
|
-
|
|
173
|
-
// Available reasons:
|
|
174
|
-
// 'local' | 'remote' | 'rejected' | 'busy' | 'answeredElsewhere' |
|
|
175
|
-
// 'missed' | 'error' | 'canceled' | 'restricted' | 'unknown'
|
|
176
|
-
await CallingxModule.endCallWithReason('unique-call-id', 'remote');
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Mute/Unmute
|
|
180
|
-
|
|
181
|
-
Toggle call mute state:
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
await CallingxModule.setMutedCall('unique-call-id', true); // mute
|
|
185
|
-
await CallingxModule.setMutedCall('unique-call-id', false); // unmute
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Hold/Unhold
|
|
189
|
-
|
|
190
|
-
Toggle call hold state:
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
await CallingxModule.setOnHoldCall('unique-call-id', true); // hold
|
|
194
|
-
await CallingxModule.setOnHoldCall('unique-call-id', false); // unhold
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Update Display
|
|
198
|
-
|
|
199
|
-
Update the caller information during a call:
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
await CallingxModule.updateDisplay(
|
|
203
|
-
'unique-call-id',
|
|
204
|
-
'+1234567890',
|
|
205
|
-
'Updated Name',
|
|
206
|
-
);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Event Listeners
|
|
210
|
-
|
|
211
|
-
Subscribe to call events:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { CallingxModule } from 'react-native-callingx';
|
|
215
|
-
import type { EventName } from 'react-native-callingx';
|
|
216
|
-
|
|
217
|
-
// Answer event - user answered from system UI
|
|
218
|
-
const answerSubscription = CallingxModule.addEventListener(
|
|
219
|
-
'answerCall',
|
|
220
|
-
(params) => {
|
|
221
|
-
console.log('Call answered:', params.callId);
|
|
222
|
-
},
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
// End event - call ended
|
|
226
|
-
const endSubscription = CallingxModule.addEventListener('endCall', (params) => {
|
|
227
|
-
console.log('Call ended:', params.callId, 'Cause:', params.cause);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// Hold toggle event
|
|
231
|
-
const holdSubscription = CallingxModule.addEventListener(
|
|
232
|
-
'didToggleHoldCallAction',
|
|
233
|
-
(params) => {
|
|
234
|
-
console.log('Hold toggled:', params.callId, 'On hold:', params.hold);
|
|
235
|
-
},
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
// Mute toggle event
|
|
239
|
-
const muteSubscription = CallingxModule.addEventListener(
|
|
240
|
-
'didPerformSetMutedCallAction',
|
|
241
|
-
(params) => {
|
|
242
|
-
console.log('Mute toggled:', params.callId, 'Muted:', params.muted);
|
|
243
|
-
},
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
// Start call action (outgoing call initiated from system)
|
|
247
|
-
const startSubscription = CallingxModule.addEventListener(
|
|
248
|
-
'didReceiveStartCallAction',
|
|
249
|
-
(params) => {
|
|
250
|
-
console.log('Start call action:', params.callId);
|
|
251
|
-
},
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// Clean up when done
|
|
255
|
-
answerSubscription.remove();
|
|
256
|
-
endSubscription.remove();
|
|
257
|
-
// ... remove other subscriptions
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Handle Initial Events
|
|
261
|
-
|
|
262
|
-
When the app is launched from a killed state by a call action, retrieve queued events:
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
// Get events that occurred before the module was initialized
|
|
266
|
-
const initialEvents = CallingxModule.getInitialEvents();
|
|
267
|
-
initialEvents.forEach((event) => {
|
|
268
|
-
console.log('Initial event:', event.eventName, event.params);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Clear initial events after processing
|
|
272
|
-
await CallingxModule.clearInitialEvents();
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Background Tasks (Android)
|
|
276
|
-
|
|
277
|
-
Run background tasks for call-related operations:
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
// Start a managed background task
|
|
281
|
-
await CallingxModule.startBackgroundTask(async (taskData, stopTask) => {
|
|
282
|
-
try {
|
|
283
|
-
// Perform background work (e.g., connect to call server)
|
|
284
|
-
await connectToCallServer();
|
|
285
|
-
} finally {
|
|
286
|
-
stopTask(); // Always call when done
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Or stop manually
|
|
291
|
-
await CallingxModule.stopBackgroundTask();
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## API Reference
|
|
295
|
-
|
|
296
|
-
### CallingxModule
|
|
297
|
-
|
|
298
|
-
| Method | Description |
|
|
299
|
-
| ---------------------------------------------------------------- | ---------------------------------------------------- |
|
|
300
|
-
| `setup(options)` | Initialize the module with platform-specific options |
|
|
301
|
-
| `requestPermissions()` | Request required permissions (audio, notifications) |
|
|
302
|
-
| `checkPermissions()` | Check current permission status |
|
|
303
|
-
| `displayIncomingCall(callId, phoneNumber, callerName, hasVideo)` | Display incoming call UI |
|
|
304
|
-
| `answerIncomingCall(callId)` | Answer an incoming call |
|
|
305
|
-
| `startCall(callId, phoneNumber, callerName, hasVideo)` | Register an outgoing call |
|
|
306
|
-
| `setCurrentCallActive(callId)` | Mark call as active/connected |
|
|
307
|
-
| `updateDisplay(callId, phoneNumber, callerName)` | Update caller display info |
|
|
308
|
-
| `endCallWithReason(callId, reason)` | End call with specified reason |
|
|
309
|
-
| `setMutedCall(callId, isMuted)` | Toggle call mute state |
|
|
310
|
-
| `setOnHoldCall(callId, isOnHold)` | Toggle call hold state |
|
|
311
|
-
| `addEventListener(eventName, callback)` | Subscribe to call events |
|
|
312
|
-
| `getInitialEvents()` | Get queued events from app launch |
|
|
313
|
-
| `clearInitialEvents()` | Clear queued initial events |
|
|
314
|
-
| `startBackgroundTask(taskProvider)` | Start Android background task |
|
|
315
|
-
| `stopBackgroundTask()` | Stop Android background task |
|
|
316
|
-
| `log(message, level)` | Log message to native console |
|
|
317
|
-
|
|
318
|
-
### Events
|
|
319
|
-
|
|
320
|
-
| Event | Parameters | Description |
|
|
321
|
-
| ------------------------------ | ------------------- | --------------------------------- |
|
|
322
|
-
| `answerCall` | `{ callId }` | User answered call from system UI |
|
|
323
|
-
| `endCall` | `{ callId, cause }` | Call ended |
|
|
324
|
-
| `didToggleHoldCallAction` | `{ callId, hold }` | Hold state changed |
|
|
325
|
-
| `didPerformSetMutedCallAction` | `{ callId, muted }` | Mute state changed |
|
|
326
|
-
| `didReceiveStartCallAction` | `{ callId }` | Outgoing call action received |
|
|
327
|
-
|
|
328
|
-
### Types
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
type EndCallReason =
|
|
332
|
-
| 'local' // Call ended by the local user (e.g., hanging up)
|
|
333
|
-
| 'remote' // Call ended by the remote party, or outgoing not answered
|
|
334
|
-
| 'rejected' // Call was rejected/declined
|
|
335
|
-
| 'busy' // Remote party was busy
|
|
336
|
-
| 'answeredElsewhere' // Answered on another device
|
|
337
|
-
| 'missed' // No response to an incoming call
|
|
338
|
-
| 'error' // Call failed due to an error (e.g., network issue)
|
|
339
|
-
| 'canceled' // Call canceled before the remote party could answer
|
|
340
|
-
| 'restricted' // Call restricted (e.g., airplane mode)
|
|
341
|
-
| 'unknown'; // Unknown or unspecified disconnect reason
|
|
342
|
-
|
|
343
|
-
type CallingExpiOSOptions = {
|
|
344
|
-
appName: string;
|
|
345
|
-
supportsVideo?: boolean;
|
|
346
|
-
maximumCallsPerCallGroup?: number;
|
|
347
|
-
maximumCallGroups?: number;
|
|
348
|
-
handleType?: 'generic' | 'number' | 'phone' | 'email';
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
type CallingExpAndroidOptions = {
|
|
352
|
-
incomingChannel?: {
|
|
353
|
-
id: string;
|
|
354
|
-
name: string;
|
|
355
|
-
sound?: string;
|
|
356
|
-
vibration?: boolean;
|
|
357
|
-
};
|
|
358
|
-
outgoingChannel?: {
|
|
359
|
-
id: string;
|
|
360
|
-
name: string;
|
|
361
|
-
sound?: string;
|
|
362
|
-
vibration?: boolean;
|
|
363
|
-
};
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
type PermissionsResult = {
|
|
367
|
-
recordAudio: boolean;
|
|
368
|
-
postNotifications: boolean;
|
|
369
|
-
};
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
## Troubleshooting
|
|
373
|
-
|
|
374
|
-
### iOS
|
|
375
|
-
|
|
376
|
-
- **Incoming call not showing**: Ensure `voip` background mode is enabled and VoIP push certificate is configured
|
|
377
|
-
- **CallKit errors**: Check that `appName` is set in setup options
|
|
378
|
-
- **Audio issues**: The module automatically configures the audio session, but ensure no conflicts with other audio libraries
|
|
46
|
+
## Main APIs
|
|
379
47
|
|
|
380
|
-
|
|
48
|
+
- `setup(options)` - required before any call action.
|
|
49
|
+
- `displayIncomingCall(callId, phoneNumber, callerName, hasVideo)`.
|
|
50
|
+
- `startCall(callId, phoneNumber, callerName, hasVideo)`.
|
|
51
|
+
- `setCurrentCallActive(callId)`.
|
|
52
|
+
- `updateDisplay(callId, phoneNumber, callerName, incoming)`.
|
|
53
|
+
- `endCallWithReason(callId, reason)`.
|
|
54
|
+
- `setMutedCall(callId, isMuted)`.
|
|
55
|
+
- `setOnHoldCall(callId, isOnHold)`.
|
|
56
|
+
- `addEventListener(eventName, callback)`.
|
|
57
|
+
- `getInitialEvents()` and `getInitialVoipEvents()`.
|
|
58
|
+
- `registerBackgroundTask(taskProvider)` / `startBackgroundTask()` / `stopBackgroundTask()` (Android).
|
|
381
59
|
|
|
382
|
-
|
|
383
|
-
- **Call not answered on tap**: Ensure `handleCallingIntent` is called in both `onCreate` and `onNewIntent` in your MainActivity
|
|
60
|
+
## Event names
|
|
384
61
|
|
|
385
|
-
|
|
62
|
+
Call events:
|
|
386
63
|
|
|
387
|
-
|
|
64
|
+
- `answerCall`
|
|
65
|
+
- `endCall`
|
|
66
|
+
- `didDisplayIncomingCall`
|
|
67
|
+
- `didToggleHoldCallAction`
|
|
68
|
+
- `didPerformSetMutedCallAction`
|
|
69
|
+
- `didChangeAudioRoute`
|
|
70
|
+
- `didReceiveStartCallAction`
|
|
71
|
+
- `didActivateAudioSession`
|
|
72
|
+
- `didDeactivateAudioSession`
|
|
388
73
|
|
|
389
|
-
|
|
74
|
+
VoIP events:
|
|
390
75
|
|
|
391
|
-
|
|
76
|
+
- `voipNotificationsRegistered`
|
|
77
|
+
- `voipNotificationReceived`
|
|
392
78
|
|
|
393
|
-
|
|
79
|
+
## Notes
|
|
394
80
|
|
|
395
|
-
|
|
81
|
+
- Import from `@stream-io/react-native-callingx`.
|
|
82
|
+
- iOS-only helpers: `registerVoipToken`, `fulfillAnswerCallAction`, `fulfillEndCallAction`.
|
|
83
|
+
- Android helpers: `canPostNotifications`, `isOngoingCallsEnabled`.
|
|
@@ -48,6 +48,8 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
48
48
|
companion object {
|
|
49
49
|
private const val TAG = "[Callingx] CallService"
|
|
50
50
|
|
|
51
|
+
internal const val DEFAULT_DISPLAY_NAME = "Unknown Caller"
|
|
52
|
+
|
|
51
53
|
internal const val EXTRA_CALL_ID = "extra_call_id"
|
|
52
54
|
internal const val EXTRA_NAME = "extra_name"
|
|
53
55
|
internal const val EXTRA_URI = "extra_uri"
|
|
@@ -104,8 +106,12 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
104
106
|
)
|
|
105
107
|
return
|
|
106
108
|
}
|
|
109
|
+
|
|
110
|
+
val createdById = data["created_by_id"]
|
|
111
|
+
val createdName = data["created_by_display_name"].orEmpty()
|
|
112
|
+
val displayName = data["call_display_name"].orEmpty()
|
|
113
|
+
val callDisplayName = displayName.ifEmpty { createdName.ifEmpty { DEFAULT_DISPLAY_NAME } }
|
|
107
114
|
|
|
108
|
-
val callName = data["created_by_display_name"].orEmpty()
|
|
109
115
|
val isVideo = data["video"] == "true"
|
|
110
116
|
|
|
111
117
|
CallRegistrationStore.trackCallRegistration(callCid, null)
|
|
@@ -114,8 +120,8 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
114
120
|
Intent(context, CallService::class.java).apply {
|
|
115
121
|
action = ACTION_INCOMING_CALL
|
|
116
122
|
putExtra(EXTRA_CALL_ID, callCid)
|
|
117
|
-
putExtra(EXTRA_URI,
|
|
118
|
-
putExtra(EXTRA_NAME,
|
|
123
|
+
putExtra(EXTRA_URI, createdById?.toUri() ?: callDisplayName.toUri())
|
|
124
|
+
putExtra(EXTRA_NAME, callDisplayName)
|
|
119
125
|
putExtra(EXTRA_IS_VIDEO, isVideo)
|
|
120
126
|
}
|
|
121
127
|
|
package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt
CHANGED
|
@@ -317,25 +317,19 @@ class CallNotificationManager(
|
|
|
317
317
|
if (call.isIncoming() && !call.isActive && optimisticState == OptimisticState.NONE) {
|
|
318
318
|
return NotificationCompat.CallStyle.forIncomingCall(
|
|
319
319
|
caller,
|
|
320
|
-
NotificationIntentFactory.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
getDisconnectCauseString(DisconnectCause(DisconnectCause.REJECTED))
|
|
328
|
-
)
|
|
329
|
-
putExtra(
|
|
330
|
-
CallingxModuleImpl.EXTRA_SOURCE,
|
|
331
|
-
CallRepository.EventSource.SYS.name.lowercase()
|
|
332
|
-
)
|
|
333
|
-
},
|
|
320
|
+
NotificationIntentFactory.getPendingNotificationIntent(
|
|
321
|
+
context,
|
|
322
|
+
CallingxModuleImpl.CALL_END_ACTION,
|
|
323
|
+
call.id,
|
|
324
|
+
CallRepository.EventSource.SYS.name.lowercase(),
|
|
325
|
+
false
|
|
326
|
+
),
|
|
334
327
|
NotificationIntentFactory.getPendingNotificationIntent(
|
|
335
328
|
context,
|
|
336
329
|
CallingxModuleImpl.CALL_ANSWERED_ACTION,
|
|
337
330
|
call.id,
|
|
338
|
-
CallRepository.EventSource.SYS.name.lowercase()
|
|
331
|
+
CallRepository.EventSource.SYS.name.lowercase(),
|
|
332
|
+
true
|
|
339
333
|
)
|
|
340
334
|
)
|
|
341
335
|
}
|
package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt
CHANGED
|
@@ -22,10 +22,11 @@ object NotificationIntentFactory {
|
|
|
22
22
|
context: Context,
|
|
23
23
|
action: String,
|
|
24
24
|
callId: String,
|
|
25
|
-
source: String
|
|
25
|
+
source: String,
|
|
26
|
+
includeLaunchActivity: Boolean
|
|
26
27
|
): PendingIntent {
|
|
27
28
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
28
|
-
getReceiverActivityIntent(context, action, callId, source)
|
|
29
|
+
getReceiverActivityIntent(context, action, callId, source, includeLaunchActivity)
|
|
29
30
|
} else {
|
|
30
31
|
getPendingServiceIntent(context, action, callId, source)
|
|
31
32
|
}
|
|
@@ -47,7 +48,7 @@ object NotificationIntentFactory {
|
|
|
47
48
|
)
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
fun getReceiverActivityIntent(context: Context, action: String, callId: String, source: String): PendingIntent {
|
|
51
|
+
fun getReceiverActivityIntent(context: Context, action: String, callId: String, source: String, includeLaunchActivity: Boolean): PendingIntent {
|
|
51
52
|
val receiverIntent =
|
|
52
53
|
Intent(context, NotificationReceiverActivity::class.java).apply {
|
|
53
54
|
this.action = action
|
|
@@ -57,14 +58,25 @@ object NotificationIntentFactory {
|
|
|
57
58
|
|
|
58
59
|
val launchActivity = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
59
60
|
val launchActivityIntent =
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
launchActivity?.let { base ->
|
|
62
|
+
Intent(base).apply {
|
|
63
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// intents are started in order and build a synthetic back stack
|
|
68
|
+
// the last intent is the one on top, so the launch activity should come first
|
|
69
|
+
val intents =
|
|
70
|
+
if (includeLaunchActivity && launchActivityIntent != null) {
|
|
71
|
+
arrayOf(launchActivityIntent, receiverIntent)
|
|
72
|
+
} else {
|
|
73
|
+
arrayOf(receiverIntent)
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
return PendingIntent.getActivities(
|
|
65
77
|
context,
|
|
66
78
|
requestCodeFor(callId, REQUEST_CODE_RECEIVER_ACTIVITY),
|
|
67
|
-
|
|
79
|
+
intents,
|
|
68
80
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
69
81
|
)
|
|
70
82
|
}
|
package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt
CHANGED
|
@@ -3,8 +3,10 @@ package io.getstream.rn.callingx.notifications
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.content.Intent
|
|
5
5
|
import android.os.Bundle
|
|
6
|
+
import android.telecom.DisconnectCause
|
|
6
7
|
import io.getstream.rn.callingx.CallingxModuleImpl
|
|
7
8
|
import io.getstream.rn.callingx.debugLog
|
|
9
|
+
import io.getstream.rn.callingx.getDisconnectCauseString
|
|
8
10
|
|
|
9
11
|
// For Android 12+
|
|
10
12
|
class NotificationReceiverActivity : Activity() {
|
|
@@ -21,33 +23,54 @@ class NotificationReceiverActivity : Activity() {
|
|
|
21
23
|
finish()
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
//re-send intent from notification to the turbo module
|
|
25
26
|
private fun handleIntent(intent: Intent?) {
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
val nonNullIntent = intent ?: return
|
|
28
|
+
val action = nonNullIntent.action ?: return
|
|
29
|
+
|
|
30
|
+
when (action) {
|
|
31
|
+
CallingxModuleImpl.CALL_ANSWERED_ACTION -> onCallAnswered(nonNullIntent)
|
|
32
|
+
CallingxModuleImpl.CALL_END_ACTION -> onCallEnded(nonNullIntent)
|
|
28
33
|
}
|
|
34
|
+
}
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (callId != null) {
|
|
36
|
-
Intent(CallingxModuleImpl.CALL_OPTIMISTIC_ACCEPT_ACTION)
|
|
37
|
-
.apply {
|
|
38
|
-
setPackage(packageName)
|
|
39
|
-
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
40
|
-
}
|
|
41
|
-
.also { sendBroadcast(it) }
|
|
42
|
-
}
|
|
36
|
+
private fun onCallAnswered(intent: Intent) {
|
|
37
|
+
debugLog("[Callingx] NotificationReceiverActivity", "[receiver] answered call action")
|
|
38
|
+
val callId = intent.getStringExtra(CallingxModuleImpl.EXTRA_CALL_ID)
|
|
39
|
+
val source = intent.getStringExtra(CallingxModuleImpl.EXTRA_SOURCE)
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
if (callId != null) {
|
|
42
|
+
Intent(CallingxModuleImpl.CALL_OPTIMISTIC_ACCEPT_ACTION)
|
|
45
43
|
.apply {
|
|
46
44
|
setPackage(packageName)
|
|
47
45
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
48
|
-
putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
|
|
49
46
|
}
|
|
50
47
|
.also { sendBroadcast(it) }
|
|
51
48
|
}
|
|
49
|
+
|
|
50
|
+
Intent(CallingxModuleImpl.CALL_ANSWERED_ACTION)
|
|
51
|
+
.apply {
|
|
52
|
+
setPackage(packageName)
|
|
53
|
+
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
54
|
+
putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
|
|
55
|
+
}
|
|
56
|
+
.also { sendBroadcast(it) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun onCallEnded(intent: Intent) {
|
|
60
|
+
debugLog("[Callingx] NotificationReceiverActivity", "[receiver] rejected call action")
|
|
61
|
+
val callId = intent.getStringExtra(CallingxModuleImpl.EXTRA_CALL_ID)
|
|
62
|
+
val source = intent.getStringExtra(CallingxModuleImpl.EXTRA_SOURCE)
|
|
63
|
+
|
|
64
|
+
Intent(CallingxModuleImpl.CALL_END_ACTION)
|
|
65
|
+
.apply {
|
|
66
|
+
setPackage(packageName)
|
|
67
|
+
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
68
|
+
putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
|
|
69
|
+
putExtra(
|
|
70
|
+
CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE,
|
|
71
|
+
getDisconnectCauseString(DisconnectCause(DisconnectCause.REJECTED))
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
.also { sendBroadcast(it) }
|
|
52
75
|
}
|
|
53
76
|
}
|
package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt
CHANGED
|
@@ -3,8 +3,10 @@ package io.getstream.rn.callingx.notifications
|
|
|
3
3
|
import android.app.Service
|
|
4
4
|
import android.content.Intent
|
|
5
5
|
import android.os.IBinder
|
|
6
|
+
import android.telecom.DisconnectCause
|
|
6
7
|
import android.util.Log
|
|
7
8
|
import io.getstream.rn.callingx.CallingxModuleImpl
|
|
9
|
+
import io.getstream.rn.callingx.getDisconnectCauseString
|
|
8
10
|
|
|
9
11
|
class NotificationReceiverService : Service() {
|
|
10
12
|
|
|
@@ -23,6 +25,7 @@ class NotificationReceiverService : Service() {
|
|
|
23
25
|
|
|
24
26
|
when (action) {
|
|
25
27
|
CallingxModuleImpl.CALL_ANSWERED_ACTION -> onCallAnswered(intent)
|
|
28
|
+
CallingxModuleImpl.CALL_END_ACTION -> onCallEnded(intent)
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
stopSelf(startId)
|
|
@@ -60,4 +63,25 @@ class NotificationReceiverService : Service() {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
}
|
|
66
|
+
|
|
67
|
+
/** Mirrors [NotificationReceiverActivity] on API levels below 33 where the service receives notification actions. */
|
|
68
|
+
private fun onCallEnded(intent: Intent) {
|
|
69
|
+
val callId = intent.getStringExtra(CallingxModuleImpl.EXTRA_CALL_ID)
|
|
70
|
+
val source = intent.getStringExtra(CallingxModuleImpl.EXTRA_SOURCE)
|
|
71
|
+
try {
|
|
72
|
+
Intent(CallingxModuleImpl.CALL_END_ACTION)
|
|
73
|
+
.apply {
|
|
74
|
+
setPackage(packageName)
|
|
75
|
+
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
76
|
+
putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
|
|
77
|
+
putExtra(
|
|
78
|
+
CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE,
|
|
79
|
+
getDisconnectCauseString(DisconnectCause(DisconnectCause.REJECTED))
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
.also { sendBroadcast(it) }
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
Log.e(TAG, "Error sending call end intent", e)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
63
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/react-native-callingx",
|
|
3
|
-
"version": "0.1.1
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CallKit and Telecom API capabilities for React Native",
|
|
5
5
|
"main": "./dist/module/index.js",
|
|
6
6
|
"module": "./dist/module/index.js",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@react-native-community/cli": "20.0.1",
|
|
63
63
|
"@react-native/babel-preset": "^0.81.5",
|
|
64
|
-
"@stream-io/react-native-webrtc": "137.1.
|
|
64
|
+
"@stream-io/react-native-webrtc": "137.1.3",
|
|
65
65
|
"@types/react": "^19.1.0",
|
|
66
66
|
"del-cli": "^6.0.0",
|
|
67
67
|
"react": "19.1.0",
|