├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── ISSUE_TEMPLATE
├── LICENSE
├── README.md
├── RNTwilioVoice.podspec
├── android
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── hoxfon
│ │ └── react
│ │ └── RNTwilioVoice
│ │ ├── CallNotificationManager.java
│ │ ├── EventManager.java
│ │ ├── HeadsetManager.java
│ │ ├── ProximityManager.java
│ │ ├── SoundPoolManager.java
│ │ ├── TwilioVoiceModule.java
│ │ ├── TwilioVoicePackage.java
│ │ └── fcm
│ │ └── VoiceFirebaseMessagingService.java
│ └── res
│ └── drawable
│ ├── ic_call_black_24dp.xml
│ ├── ic_call_missed_black_24dp.xml
│ ├── ic_call_missed_white_24dp.xml
│ └── ic_call_white_24dp.xml
├── images
└── vjeux_tweet.png
├── index.js
├── ios
├── RNTwilioVoice.xcodeproj
│ └── project.pbxproj
└── RNTwilioVoice
│ ├── RNTwilioVoice.h
│ └── RNTwilioVoice.m
├── logs
└── android
│ └── beta5
│ ├── in-answered-foreground
│ └── in-rejected-foreground
├── package.json
└── react-native.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | local.properties
4 | .idea
5 | .idea/workspace.xml
6 | .idea/libraries
7 | .DS_Store
8 | .externalNativeBuild
9 |
10 | # Built application files
11 | *.apk
12 | *.ap_
13 |
14 | # Files for the ART/Dalvik VM
15 | *.dex
16 |
17 | # Java class files
18 | *.class
19 |
20 | # Generated files
21 | bin/
22 | gen/
23 | out/
24 |
25 | # Gradle files
26 | .gradle/
27 | build/
28 |
29 | # Local configuration file (sdk path, etc)
30 | local.properties
31 |
32 | # Proguard folder generated by Eclipse
33 | proguard/
34 |
35 | # Log Files
36 | *.log
37 |
38 | # Android Studio Navigation editor temp files
39 | .navigation/
40 |
41 | # Android Studio captures folder
42 | captures/
43 |
44 | # Intellij
45 | *.iml
46 | .idea/workspace.xml
47 |
48 | # Keystore files
49 | *.jks
50 |
51 | xcuserdata
52 |
53 | # ios
54 |
55 | *.ipa
56 |
57 | ios/Pods
58 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | android/.idea
3 | node_modules
4 | build
5 |
6 | *.iml
7 | .gradle
8 | local.properties
9 | .idea/workspace.xml
10 | .idea/libraries
11 | .DS_Store
12 | .externalNativeBuild
13 |
14 | # Built application files
15 | *.apk
16 | *.ap_
17 |
18 | # Files for the ART/Dalvik VM
19 | *.dex
20 |
21 | # Java class files
22 | *.class
23 |
24 | # Generated files
25 | bin/
26 | gen/
27 | out/
28 |
29 | # Gradle files
30 | .gradle/
31 | build/
32 |
33 | # Local configuration file (sdk path, etc)
34 | local.properties
35 |
36 | # Proguard folder generated by Eclipse
37 | proguard/
38 |
39 | # Log Files
40 | *.log
41 |
42 | # Android Studio Navigation editor temp files
43 | .navigation/
44 |
45 | # Android Studio captures folder
46 | captures/
47 |
48 | # Intellij
49 | *.iml
50 | .idea/workspace.xml
51 |
52 | # Keystore files
53 | *.jks
54 |
55 | xcuserdata
56 |
57 | images/
58 |
59 | # project logs
60 | logs/
61 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | [Release Section](https://github.com/hoxfon/react-native-twilio-programmable-voice/releases)
2 |
3 | ## 4.4.0
4 |
5 | - iOS:
6 | - project.pbxproj aligned to Xcode 12.4
7 | - amend podspec
8 | - remove not necessary files
9 | - improve package.json syntax
10 | - add peerDependency on react-native 0.60.0
11 |
12 | ## 4.3.1
13 |
14 | - Android: ensure that intent action is not null
15 |
16 | ## 4.3.0
17 |
18 | - iOS add handler to hold/unhold call
19 |
20 | ## 4.2.0
21 |
22 | - iOS
23 | - Added ability to pass caller's name to CallKit in push notification
24 |
25 | ## 4.1.0
26 |
27 | - iOS
28 | - Adds ability to handle DTMF codes in CallKit
29 |
30 | ## 4.0.2
31 |
32 | - iOS
33 | - fix: always disable proximityMonitor on iOS when the call is disconnected
34 |
35 | ## 4.0.0
36 |
37 | - Android
38 | - implement new autolinking react native API
39 | - update Firebase Messaging to 17.3.4 which simplifies how to obtain the FCM token
40 | - Android X migration
41 | - use gradle 5.4.1
42 | - use API 28
43 | - upgrade com.twilio:voice-android to 4.3.0
44 | - implement `hold` to hold a call
45 | - new event `callInviteCancelled`
46 | - new event `callStateRinging`
47 | - new method `getCallInvite`
48 | - implement call ringing Twilio event
49 | - remove `call_state` from CallInvite
50 | - iOS
51 | - implement new autolinking react native API
52 | - update Twilio Voice SDK to v5.2.0
53 | - remove method `initWithAccessTokenUrl`, please use `initWithAccessToken` instead
54 | - event parity with Android `deviceDidReceiveIncoming`
55 | - new event `callInviteCancelled`
56 | - new event `callStateRinging`
57 | - new event `connectionIsReconnecting`
58 | - new event `connectionDidReconnect`
59 | - convert params for `connectionDidConnect` to => `call_to`, from => `call_from`
60 | - convert params for `connectionDidDisconnect` to => `call_to`, from => `call_from`, `error` => `err`
61 |
62 | - throw an error when listening to events that do not exist
63 |
64 | ## 3.21.3
65 |
66 | - iOS: Upgrade TwilioVoice pod to version 2.1
67 |
68 | ## 3.21.2
69 |
70 | - Switch from rnpm to react-native.config.js
71 |
72 | ## 3.21.1
73 |
74 | - Android: fix crash when asking for microphone permission before an activity is displayed
75 |
76 | ## 3.21.0
77 |
78 | - Android: allow to pass arbitrary parameters to call voice.call() as it is on iOS
79 |
80 | ## 3.20.1
81 |
82 | - iOS: fix crash when callSid is nil for CallInviteCanceled
83 |
84 | ## 3.20.0
85 |
86 | - Android: option to opt out microphone permission request
87 |
88 | ## 3.19.0
89 |
90 | - upgrade com.twilio:voice-android to 2.0.7
91 | - upgrade firebase-messaging to 17.+
92 |
93 | ## 3.18.1
94 |
95 | - Validate token type before calling native module
96 |
97 | ## 3.18.0
98 |
99 | - avoid keeping the screen on when a call is received with a locked device
100 | - remove PowerManager wakelock pattern
101 |
102 | ## 3.17.0
103 |
104 | - disconnect any existing calls when the app is terminated
105 | - Android: Twilio Voice SDK 2.0.6
106 | - iOS: Twilio Voice SDK 2.0.4
107 |
108 | ## 3.16.0
109 |
110 | - Android: Twilio Voice SDK 2.0.5
111 |
112 | ## 3.15.0
113 |
114 | - init notifications channel before showing call in progress notification
115 | - add cocoapods support to install iOS native package
116 |
117 | ## 3.14.0
118 |
119 | - Android: start up the app in fullscreen for incoming calls
120 | - Android: pass call params to disconnect event when ignoring a call
121 | - Android: pass call params to disconnect event when rejecting a call
122 |
123 | ## 3.13.1
124 |
125 | - Android: Twilio Voice SDK 2.0.4
126 |
127 | ## 3.13.0
128 |
129 | - iOS: the library is compatible with Twilio Voice SDK 2.0.2
130 |
131 | ## 3.12.0
132 |
133 | - iOS: the library is compatible with Twilio Voice SDK 2.0.0-beta21
134 | - iOS: handle events when a call is put on hold
135 |
136 | ## 3.11.0
137 |
138 | - iOS: the library is compatible with Twilio Voice SDK 2.0.0-beta20
139 |
140 | ## 3.10.0
141 |
142 | - fix crash on Oreo abandonAudioFocusRequest()
143 |
144 | ## 3.9.0
145 |
146 | - update com.google.gms:google-services to 3.1.2
147 | - use latest API 26 support library
148 |
149 | ## 3.8.0
150 |
151 | - Android: Twilio Voice SDK 2.0.2
152 |
153 | ## 3.7.0
154 |
155 | - Android: use proximity sensor to lock screen during calls
156 | - Android: send event to JavaScript for headset plugged in
157 | - Android: fix unset audio focus for Android O
158 |
159 | ## 3.6.0
160 |
161 | - Android: Twilio Voice SDK 2.0.0-beta24
162 | - Implement Android O notification channels
163 | - Android: ensure that audio settings are set back to normal when the app destroys
164 | - Android:fix audio settings when set speakers on/off
165 | - Android: prevent other apps to emit sound when a call is in progress
166 |
167 | ## 3.5.0
168 |
169 | - Android: Twilio Voice SDK 2.0.0-beta20
170 | - Implement Call.Listener onConnectFailure()
171 |
172 | ## 3.4.0
173 |
174 | - Fix iOS HEADER_SEARCH_PATHS
175 |
176 | ## 3.3.0
177 |
178 | - Android: Twilio Voice SDK 2.0.0-beta18
179 | - Adapt setAudioFocus() for Android O
180 |
181 | ## 3.2.0
182 |
183 | - Android: add compatibility for react native >= 0.47
184 |
185 | ## 3.1.1
186 |
187 | - iOS: ensure the proximity sensor is enabled when starting a call
188 |
189 | ## 3.1.0
190 |
191 | Make the iOS initialization process the same as Android
192 |
193 | - iOS: call event `deviceReady` only when the accessToken registration is successful
194 | - iOS: implement event `deviceNotReady` called when the accessToken registration is not successful
195 |
196 | ## 3.0.0
197 |
198 | Breaking changes:
199 |
200 | - initWitToken returns an object with a property `initialized` instead of `initilized`
201 | - iOS event `connectionDidConnect` returns the same properties as Android
202 | move property `to` => `call_to`
203 | move property `from` => `call_from`
204 |
205 | New iOS
206 |
207 | - iOS: the library is compatible with Twilio Voice SDK 2.0.0-beta15
208 | - iOS use CallKit reportOutgoingCallWithUUID when initializing calls
209 |
210 | New Android
211 |
212 | - add properties `call_to` and `call_from` to Android event `connectionDidConnect`
213 |
214 | ## 2.11.2
215 |
216 | - Make sure CallKit session is ended when the call is terminated by the callee - @SimonRobinson
217 |
218 | ## 2.11.1
219 |
220 | - Make sure CallKit session is ended on fail - @Pagebakers
221 |
222 | ## 2.11.0
223 |
224 | - Android: Twilio Voice SDK 2.0.0-beta17
225 |
226 | ## 2.10.0
227 |
228 | - Android: Twilio Voice SDK 2.0.0-beta16
229 |
230 | ## 2.9.0
231 |
232 | - make sure the Android build uses the latest version 10 of firebase.messaging to avoid dependencies conflicts crashes
233 |
234 | ## 2.8.0
235 |
236 | - iOS: prevent CallKit to be initialised more than once
237 |
238 | ## 2.7.0
239 |
240 | - iOS: correct handling of calls disconnection
241 |
242 | ## 2.6.0
243 |
244 | - iOS: implementing getActiveCall()
245 |
246 | ## 2.5.2
247 |
248 | - iOS: initWithToken() now returns the same value as Android
249 |
250 | ## 2.5.1
251 |
252 | - iOS: handle call failure and pass to JS the most descriptive error
253 |
254 | ## 2.5.0
255 |
256 | - iOS: Twilio Voice SDK 2.0.0-beta13
257 |
258 | ## 2.4.0
259 |
260 | - Android: Twilio Voice SDK 2.0.0-beta15
261 | - use buildToolsVersion "25.0.2"
262 | - use targetSdkVersion 25
263 | - fix registerActionReceiver() called twice
264 | - fix reject() intent not sending CONNECTION_STOP to JavaScript
265 | - fix ingore() not not sending CONNECTION_STOP to JavaScript when there is not activeInviteCall
266 |
267 | ## 2.3.2
268 |
269 | - Android: Twilio Voice SDK 2.0.0-beta14
270 |
271 | ## 2.3.1
272 |
273 | - iOS: call TwilioVoice audioSessionDeactivated on didDeactivateAudioSession
274 | - iOS: performEndCallActionWithUUID when call is disconnected from the app
275 |
276 | ## 2.3.0
277 |
278 | - Android: Twilio Voice SDK 2.0.0-beta13
279 | - iOS: Twilio Voice SDK 2.0.0-beta11
280 |
281 | ## 2.2.0
282 |
283 | - iOS: Twilio Voice SDK 2.0.0-beta10
284 |
285 | ## 2.1.0
286 |
287 | - Android: Twilio Voice SDK 2.0.0-beta11
288 |
289 | ## 2.0.2
290 |
291 | - Android: fix library for RN 0.45.1
292 |
293 | ## 2.0.1
294 |
295 | - ios: send connectionDidDisconnect when the call invite terminates
296 |
297 | ## 2.0.0
298 |
299 | - ios implementation with CallKit
300 |
301 | ## 1.1.0
302 |
303 | - use Twilio Voice SDK 2.0.0-beta8
304 |
305 | ## 1.0.1
306 |
307 | - Android: use incoming call notification full screen
308 |
309 | ## 1.0.0
310 |
311 | - use Twilio beta 5
312 | - removed requestPermissions, react-native API should be used instead
313 | - renamed getIncomingCall() to getActiveCall()
314 | - set the audio of the call as MODE_IN_COMMUNICATION
315 |
316 | ## 0.6.3
317 |
318 | - fix crash when activityManager is null
319 | - add call_from and call_to to the event connectionDidDisconnect
320 |
321 | ## 0.6.2
322 |
323 | - Android: fix. Clear callInvite when the caller hangs up
324 |
325 | ## 0.6.1
326 |
327 | - Android: fix gradle import beta4
328 |
329 | ## 0.6.0
330 |
331 | - Android: use Twilio Voice SDK Beta4
332 |
333 | ## 0.5.5
334 |
335 | - improve logic for starting the MainActivity when receiving a call. The Intent flags depends on the App importance (fixes the 0.5.3 for Android 6.0)
336 | - make sure all wakelock are released after being acquired, and the keyguard re-enabled
337 |
338 | ## 0.5.4
339 |
340 | - set incoming call Intent flag depending on App importance (App status)
341 |
342 | ## 0.5.3
343 |
344 | - Prevent incoming call from starting a new task: use (Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP) for the Intent
345 | - Prevent the incoming call Intent to be broadcast when the app is in the foreground
346 |
347 | ## 0.5.2
348 |
349 | - allow custom notification icons
350 |
351 | ## 0.5.1
352 |
353 | - fix showing incoming call
354 | - add pendingIntent to clear missed calls number
355 | - prevent double incoming call cancelled message
356 |
357 | ## 0.5.0
358 |
359 | - start activity as soon as a call notification arrives
360 | - wakes up the device when a call arrives
361 | - use ringtone, removing notification sound
362 | - handle gracefully when a call is accepted twice
363 | - don't create a missed calls when the call is rejected or ignored manually
364 |
365 | ## 0.4.1
366 |
367 | - simplify life-cycle of local hangup notification
368 | - remove crash on disconnect
369 |
370 | ## 0.4.0
371 |
372 | - add notification for missed calls
373 | - add chronometer for ongoing calls
374 | - use FLAG_UPDATE_CURRENT rather than FLAG_ONE_SHOT for pending intents
375 |
376 | ## 0.3.5
377 |
378 | - add protections from null callSid
379 |
380 | ## 0.3.4
381 |
382 | - fix wrong boolean, from the previous refactoring
383 |
384 | ## 0.3.3
385 |
386 | - add null checks on all `call` variables before invoking getCallSid()
387 | - remove unused import
388 | - follow Android Studio linting instructions
389 |
390 | ## 0.3.2
391 |
392 | - fix typo in setMuted()
393 |
394 | ## 0.3.1
395 |
396 | - avoid registering the same event listener multiple times
397 |
398 | ## 0.3.0
399 |
400 | - Check if Google Play Services are available before initialising Twilio for receiving calls.
401 | - Method initWithToken returns a Promise to let the application know if the initialisation did succeed.
402 |
403 | ## 0.2.2
404 |
405 | - fix the instruction to setup the `AndroidManifest.xml` file
406 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE:
--------------------------------------------------------------------------------
1 | Please include the following information for better support
2 |
3 | The more context you provide around this issue the faster the community can help you
4 |
5 | Did you follow all the instructions as specified in the Twilio Quickstart repositories?
6 | What version of React Native are you running?
7 | What version of react-native-twilio-programmable-voice are you running?
8 | What device are you using? (e.g iOS10 simulator, Android 7 device)?
9 | Is your app running in foreground, background or not running?
10 | Is there any relevant message in the log?
11 | If using iOS, which pod version are you using?
12 |
13 | Step to reproduce
14 |
15 | Advanced:
16 | Have you tried adding break point using AndroidStudio or XCode and analyse the logs?
17 | can share a project with issue?
18 | Did you try to reinstall the pods completely?
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 hoxfon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-twilio-programmable-voice
2 |
3 | This is a react-native wrapper around [Twilio Programmable Voice SDK](https://www.twilio.com/voice), which allows your react-native app to make and receive calls.
4 |
5 | This module is not affiliated with nor officially maintained by Twilio. It is maintained by open source contributors working during their nights and weekends.
6 |
7 | Tested with:
8 |
9 | - react-native 0.62.2
10 | - Android 11
11 | - iOS 14
12 |
13 | ## Roadmap
14 |
15 | ### Project 1
16 |
17 | The most updated branch is [feat/twilio-android-sdk-5](https://github.com/hoxfon/react-native-twilio-programmable-voice/tree/feat/twilio-android-sdk-5) which is aligned with:
18 |
19 | - Android 5.0.2
20 | - iOS 5.2.0
21 |
22 | It contains breaking changes from `react-native-twilio-programmable-voice` v4, and it will be released as v5.
23 |
24 | You can install it with:
25 | ```bash
26 | # Yarn
27 | yarn add https://github.com/hoxfon/react-native-twilio-programmable-voice#feat/twilio-android-sdk-5
28 |
29 | # NPM
30 | npm install git+https://github.com/hoxfon/react-native-twilio-programmable-voice#feat/twilio-android-sdk-5
31 | ```
32 |
33 | I am currently updating the library to catchup with all changes published on the latest Android and iOS Twilio Voice SDK:
34 |
35 | [iOS changelog](https://www.twilio.com/docs/voice/voip-sdk/ios/changelog)
36 | [Android changelog](https://www.twilio.com/docs/voice/voip-sdk/android/3x-changelog)
37 |
38 | My plan is to use the following links as a reference, and follow Twilio commit by commit.
39 |
40 | - https://github.com/twilio/voice-quickstart-android
41 | - https://github.com/twilio/voice-quickstart-ios
42 |
43 | _If you want to contribute please consider helping on this project. [Click here for more information](https://github.com/hoxfon/react-native-twilio-programmable-voice/issues/158)._
44 |
45 | ### Project 2
46 |
47 | Allow Android to use the built in Android telephony service to make and receive calls.
48 |
49 | ## Stable release
50 |
51 | ### Twilio Programmable Voice SDK
52 |
53 | - Android 4.5.0
54 | - iOS 5.2.0
55 |
56 | ### Breaking changes in v4.0.0
57 |
58 | The module implements [react-native autolinking](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) as many other native libraries > react-native 0.60.0, therefore it doesn't need to be linked manually.
59 |
60 | Android: update Firebase Messaging to 17.6.+. Remove the following block from your application's `AndroidManifest.xml` if you are migrating from v3.
61 |
62 | ```xml
63 |
64 |
67 |
68 |
69 |
70 |
71 |
72 | ```
73 |
74 | Android X is supported.
75 |
76 | Data passed to the event `deviceDidReceiveIncoming` does not contain the key `call_state`, because state of Call Invites was removed in Twilio Android and iOS SDK v3.0.0
77 |
78 | - iOS: params changes for `connectionDidConnect` and `connectionDidDisconnect`
79 |
80 | to => call_to
81 | from => call_from
82 | error => err
83 |
84 | New features
85 |
86 | Twilio Programmable Voice SDK v3.0.0 handles call invites directly and makes it easy to distinguish a call invites from an active call, which previously was confusing.
87 | To ensure that an active call is displayed when the app comes to foreground you should use the promise `getActiveCall()`.
88 | To ensure that a call invite is displayed when the app comes to foreground use the promise `getCallInvite()`. Please note that call invites don't have a `call_state` field.
89 |
90 | You should use `hold()` to put a call on hold.
91 |
92 | You can be notified when a call is `ringing` by listening for `callStateRinging` events.
93 |
94 | iOS application can now receive the following events, that in v3 where only dispatched to Android:
95 |
96 | - deviceDidReceiveIncoming
97 | - callInviteCancelled
98 | - callStateRinging
99 | - connectionIsReconnecting
100 | - connectionDidReconnect
101 |
102 | ### Breaking changes in v3.0.0
103 |
104 | - initWitToken returns an object with a property `initialized` instead of `initilized`
105 | - iOS event `connectionDidConnect` returns the same properties as Android
106 | move property `to` => `call_to`
107 | move property `from` => `call_from`
108 |
109 | ### Installation
110 |
111 | Before starting, we recommend you get familiar with [Twilio Programmable Voice SDK](https://www.twilio.com/docs/api/voice-sdk).
112 | It's easier to integrate this module into your react-native app if you follow the Quick start tutorial from Twilio, because it makes very clear which setup steps are required.
113 |
114 | ```bash
115 | npm install react-native-twilio-programmable-voice --save
116 | ```
117 |
118 | - **React Native 0.60+**
119 |
120 | [CLI autolink feature](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) links the module while building the app.
121 |
122 | - **React Native <= 0.59**
123 |
124 | ```bash
125 | react-native link react-native-twilio-programmable-voice
126 | ```
127 |
128 | ### iOS Installation
129 |
130 | If you can't or don't want to use autolink, you can also manually link the library using the instructions below (click on the arrow to show them):
131 |
132 |
133 | Manually link the library on iOS
134 |
135 | Follow the [instructions in the React Native documentation](https://facebook.github.io/react-native/docs/linking-libraries-ios#manual-linking) to manually link the framework
136 |
137 | After you have linked the library with `react-native link react-native-twilio-programmable-voice`
138 | check that `libRNTwilioVoice.a` is present under YOUR_TARGET > Build Phases > Link Binaries With Libraries. If it is not present you can add it using the + sign at the bottom of that list.
139 |
140 |
141 | ```bash
142 | cd ios && pod install
143 | ```
144 |
145 | #### CallKit
146 |
147 | The iOS library works through [CallKit](https://developer.apple.com/reference/callkit) and handling calls is much simpler than the Android implementation as CallKit handles the inbound calls answering, ignoring, or rejecting. Outbound calls must be controlled by custom React-Native screens and controls.
148 |
149 | To pass caller's name to CallKit via Voip push notification add custom parameter 'CallerName' to Twilio Dial verb.
150 |
151 | ```xml
152 |
153 |
154 | Client
155 | NAME TO DISPLAY
156 |
157 |
158 | ```
159 |
160 | #### VoIP Service Certificate
161 |
162 | Twilio Programmable Voice for iOS utilizes Apple's VoIP Services and VoIP "Push Notifications" instead of FCM. You will need a VoIP Service Certificate from Apple to receive calls. Follow [the official Twilio instructions](https://github.com/twilio/voice-quickstart-ios#7-create-voip-service-certificate) to complete this step.
163 |
164 | ### Android Installation
165 |
166 | Setup FCM
167 |
168 | You must download the file `google-services.json` from the Firebase console.
169 | It contains keys and settings for all your applications under Firebase. This library obtains the resource `senderID` for registering for remote GCM from that file.
170 |
171 | #### `android/build.gradle`
172 |
173 | ```groovy
174 | buildscript {
175 | dependencies {
176 | // override the google-service version if needed
177 | // https://developers.google.com/android/guides/google-services-plugin
178 | classpath 'com.google.gms:google-services:4.3.4'
179 | }
180 | }
181 |
182 | // this plugin looks for google-services.json in your project
183 | apply plugin: 'com.google.gms.google-services'
184 | ```
185 |
186 | #### `AndroidManifest.xml`
187 |
188 | ```xml
189 |
190 |
191 |
192 |
193 |
194 |
196 |
197 |
198 |
199 |
200 |
201 | ```
202 |
203 | If you can't or don't want to use autolink, you can also manually link the library using the instructions below (click on the arrow to show them):
204 |
205 |
206 | Manually link the library on Android
207 |
208 | Make the following changes:
209 |
210 | #### `android/settings.gradle`
211 |
212 | ```groovy
213 | include ':react-native-twilio-programmable-voice'
214 | project(':react-native-twilio-programmable-voice').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-twilio-programmable-voice/android')
215 | ```
216 |
217 | #### `android/app/build.gradle`
218 |
219 | ```groovy
220 | dependencies {
221 | implementation project(':react-native-twilio-programmable-voice')
222 | }
223 | ```
224 |
225 | #### `android/app/src/main/.../MainApplication.java`
226 | On top, where imports are:
227 |
228 | ```java
229 | import com.hoxfon.react.RNTwilioVoice.TwilioVoicePackage; // <--- Import Package
230 | ```
231 |
232 | Add the `TwilioVoicePackage` class to your list of exported packages.
233 |
234 | ```java
235 | @Override
236 | protected List getPackages() {
237 | return Arrays.asList(
238 | new MainReactPackage(),
239 | new TwilioVoicePackage() // <---- Add the package
240 | // new TwilioVoicePackage(false) // <---- pass false if you don't want to ask for microphone permissions
241 | );
242 | }
243 | ```
244 |
245 |
246 | ## Usage
247 |
248 | ```javascript
249 | import TwilioVoice from 'react-native-twilio-programmable-voice'
250 |
251 | // ...
252 |
253 | // initialize the Programmable Voice SDK passing an access token obtained from the server.
254 | // Listen to deviceReady and deviceNotReady events to see whether the initialization succeeded.
255 | async function initTelephony() {
256 | try {
257 | const accessToken = await getAccessTokenFromServer()
258 | const success = await TwilioVoice.initWithToken(accessToken)
259 | } catch (err) {
260 | console.err(err)
261 | }
262 | }
263 |
264 | function initTelephonyWithToken(token) {
265 | TwilioVoice.initWithAccessToken(token)
266 |
267 | // iOS only, configure CallKit
268 | try {
269 | TwilioVoice.configureCallKit({
270 | appName: 'TwilioVoiceExample', // Required param
271 | imageName: 'my_image_name_in_bundle', // OPTIONAL
272 | ringtoneSound: 'my_ringtone_sound_filename_in_bundle' // OPTIONAL
273 | })
274 | } catch (err) {
275 | console.err(err)
276 | }
277 | }
278 | ```
279 |
280 | ## Events
281 |
282 | ```javascript
283 | // add listeners (flowtype notation)
284 | TwilioVoice.addEventListener('deviceReady', function() {
285 | // no data
286 | })
287 | TwilioVoice.addEventListener('deviceNotReady', function(data) {
288 | // {
289 | // err: string
290 | // }
291 | })
292 | TwilioVoice.addEventListener('connectionDidConnect', function(data) {
293 | // {
294 | // call_sid: string, // Twilio call sid
295 | // call_state: 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' | 'RINGING' | 'DISCONNECTED' | 'CANCELLED',
296 | // call_from: string, // "+441234567890"
297 | // call_to: string, // "client:bob"
298 | // }
299 | })
300 | TwilioVoice.addEventListener('connectionIsReconnecting', function(data) {
301 | // {
302 | // call_sid: string, // Twilio call sid
303 | // call_from: string, // "+441234567890"
304 | // call_to: string, // "client:bob"
305 | // }
306 | })
307 | TwilioVoice.addEventListener('connectionDidReconnect', function(data) {
308 | // {
309 | // call_sid: string, // Twilio call sid
310 | // call_from: string, // "+441234567890"
311 | // call_to: string, // "client:bob"
312 | // }
313 | })
314 | TwilioVoice.addEventListener('connectionDidDisconnect', function(data: mixed) {
315 | // | null
316 | // | {
317 | // err: string
318 | // }
319 | // | {
320 | // call_sid: string, // Twilio call sid
321 | // call_state: 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' | 'RINGING' | 'DISCONNECTED' | 'CANCELLED',
322 | // call_from: string, // "+441234567890"
323 | // call_to: string, // "client:bob"
324 | // err?: string,
325 | // }
326 | })
327 | TwilioVoice.addEventListener('callStateRinging', function(data: mixed) {
328 | // {
329 | // call_sid: string, // Twilio call sid
330 | // call_state: 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' | 'RINGING' | 'DISCONNECTED' | 'CANCELLED',
331 | // call_from: string, // "+441234567890"
332 | // call_to: string, // "client:bob"
333 | // }
334 | })
335 | TwilioVoice.addEventListener('callInviteCancelled', function(data: mixed) {
336 | // {
337 | // call_sid: string, // Twilio call sid
338 | // call_from: string, // "+441234567890"
339 | // call_to: string, // "client:bob"
340 | // }
341 | })
342 |
343 | // iOS Only
344 | TwilioVoice.addEventListener('callRejected', function(value: 'callRejected') {})
345 |
346 | TwilioVoice.addEventListener('deviceDidReceiveIncoming', function(data) {
347 | // {
348 | // call_sid: string, // Twilio call sid
349 | // call_from: string, // "+441234567890"
350 | // call_to: string, // "client:bob"
351 | // }
352 | })
353 |
354 | // Android Only
355 | TwilioVoice.addEventListener('proximity', function(data) {
356 | // {
357 | // isNear: boolean
358 | // }
359 | })
360 |
361 | // Android Only
362 | TwilioVoice.addEventListener('wiredHeadset', function(data) {
363 | // {
364 | // isPlugged: boolean,
365 | // hasMic: boolean,
366 | // deviceName: string
367 | // }
368 | })
369 |
370 | // ...
371 |
372 | // start a call
373 | TwilioVoice.connect({To: '+61234567890'})
374 |
375 | // hangup
376 | TwilioVoice.disconnect()
377 |
378 | // accept an incoming call (Android only, in iOS CallKit provides the UI for this)
379 | TwilioVoice.accept()
380 |
381 | // reject an incoming call (Android only, in iOS CallKit provides the UI for this)
382 | TwilioVoice.reject()
383 |
384 | // ignore an incoming call (Android only)
385 | TwilioVoice.ignore()
386 |
387 | // mute or un-mute the call
388 | // mutedValue must be a boolean
389 | TwilioVoice.setMuted(mutedValue)
390 |
391 | // put a call on hold
392 | TwilioVoice.hold(holdValue)
393 |
394 | // send digits
395 | TwilioVoice.sendDigits(digits)
396 |
397 | // Ensure that an active call is displayed when the app comes to foreground
398 | TwilioVoice.getActiveCall()
399 | .then(activeCall => {
400 | if (activeCall){
401 | _displayActiveCall(activeCall)
402 | }
403 | })
404 |
405 | // Ensure that call invites are displayed when the app comes to foreground
406 | TwilioVoice.getCallInvite()
407 | .then(callInvite => {
408 | if (callInvite){
409 | _handleCallInvite(callInvite)
410 | }
411 | })
412 |
413 | // Unregister device with Twilio (iOS only)
414 | TwilioVoice.unregister()
415 | ```
416 |
417 | ## Help wanted
418 |
419 | There is no need to ask permissions to contribute. Just open an issue or provide a PR. Everybody is welcome to contribute.
420 |
421 | ReactNative success is directly linked to its module ecosystem. One way to make an impact is helping contributing to this module or another of the many community lead ones.
422 |
423 | 
424 |
425 | ## Twilio Voice SDK reference
426 |
427 | [iOS changelog](https://www.twilio.com/docs/voice/voip-sdk/ios/changelog)
428 | [Android changelog](https://www.twilio.com/docs/voice/voip-sdk/android/3x-changelog)
429 |
430 | ## Credits
431 |
432 | [voice-quickstart-android](https://github.com/twilio/voice-quickstart-android)
433 |
434 | [voice-quickstart-ios](https://github.com/twilio/voice-quickstart-ios)
435 |
436 | [react-native-push-notification](https://github.com/zo0r/react-native-push-notification)
437 |
438 | ## License
439 |
440 | MIT
441 |
--------------------------------------------------------------------------------
/RNTwilioVoice.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | package = JSON.load(File.read(File.expand_path("./package.json", __dir__)))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = "RNTwilioVoice"
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.author = package['contributors'][0]['name']
10 | s.homepage = package['homepage']
11 | s.license = package['license']
12 | s.platform = :ios, "11.0"
13 | s.requires_arc = true
14 |
15 | s.source_files = 'ios/RNTwilioVoice/*.{h,m}'
16 | s.source = { git: 'https://github.com/hoxfon/react-native-twilio-programmable-voice', tag: s.version }
17 |
18 | s.dependency 'React-Core'
19 | s.dependency 'TwilioVoice', '~> 5.2.0'
20 | s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '${PODS_ROOT}/TwilioVoice/Build/iOS' }
21 | s.frameworks = 'TwilioVoice'
22 | s.preserve_paths = 'LICENSE', 'README.md', 'package.json', 'index.js'
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.4'
10 | classpath 'com.google.gms:google-services:4.3.4'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | apply plugin: 'com.android.library'
25 |
26 | def DEFAULT_MIN_SDK_VERSION = 23
27 | def DEFAULT_COMPILE_SDK_VERSION = 30
28 | def DEFAULT_BUILD_TOOLS_VERSION = "29.0.3"
29 | def DEFAULT_TARGET_SDK_VERSION = 29
30 | def DEFAULT_SUPPORT_LIB_VERSION = "29.0.3"
31 |
32 | android {
33 | compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
34 | buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
35 | compileOptions {
36 | sourceCompatibility 1.8
37 | targetCompatibility 1.8
38 | }
39 | defaultConfig {
40 | minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_MIN_SDK_VERSION
41 | targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
42 | versionCode 1
43 | versionName "1.0"
44 | vectorDrawables.useSupportLibrary = true
45 | }
46 | buildTypes {
47 | release {
48 | minifyEnabled false
49 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
50 | }
51 | }
52 | }
53 |
54 | dependencies {
55 | def supportLibVersion = rootProject.hasProperty('supportLibVersion') ? rootProject.supportLibVersion : DEFAULT_SUPPORT_LIB_VERSION
56 |
57 | implementation fileTree(include: ['*.jar'], dir: 'libs')
58 | implementation 'com.twilio:voice-android:4.5.0'
59 | implementation "com.android.support:appcompat-v7:$supportLibVersion"
60 | implementation 'com.facebook.react:react-native:+'
61 | implementation 'com.google.firebase:firebase-messaging:17.6.+'
62 | testImplementation 'junit:junit:4.12'
63 | }
64 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 | android.useAndroidX=true
19 | android.enableJetifier=true
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoxfon/react-native-twilio-programmable-voice/ff6b897676ce7ed99c75b177ec4e3bca19d88875/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Disabling obfuscation is useful if you collect stack traces from production crashes
20 | # (unless you are using a system that supports de-obfuscate the stack traces).
21 | -dontobfuscate
22 |
23 | # Twilio Programmable Voice
24 | -keep class com.twilio.voice.** { *; }
25 | -keep class com.hoxfon.** { *; }
26 |
27 | -assumenosideeffects class android.util.Log {
28 | public static *** d(...);
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/CallNotificationManager.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice;
2 |
3 | import android.app.ActivityManager;
4 | import android.app.Notification;
5 | import android.app.NotificationChannel;
6 | import android.app.NotificationManager;
7 | import android.app.PendingIntent;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.SharedPreferences;
11 | import android.content.res.Resources;
12 | import android.graphics.Bitmap;
13 | import android.graphics.BitmapFactory;
14 | import android.graphics.Color;
15 | import android.os.Build;
16 | import android.os.Bundle;
17 | import android.service.notification.StatusBarNotification;
18 | import androidx.core.app.NotificationCompat;
19 | import android.util.Log;
20 | import android.view.WindowManager;
21 |
22 | import com.facebook.react.bridge.ReactApplicationContext;
23 | import com.twilio.voice.CallInvite;
24 | import com.twilio.voice.CancelledCallInvite;
25 |
26 | import java.util.List;
27 |
28 | import static android.content.Context.ACTIVITY_SERVICE;
29 |
30 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
31 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_ANSWER_CALL;
32 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_REJECT_CALL;
33 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_HANGUP_CALL;
34 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_INCOMING_CALL;
35 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_MISSED_CALL;
36 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_CALL_INVITE;
37 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_CALL_NOTIFICATION_ID;
38 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.NOTIFICATION_TYPE;
39 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.CALL_SID_KEY;
40 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_NOTIFICATION_PREFIX;
41 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.MISSED_CALLS_GROUP;
42 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.MISSED_CALLS_NOTIFICATION_ID;
43 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.HANGUP_NOTIFICATION_ID;
44 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.PREFERENCE_KEY;
45 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_CLEAR_MISSED_CALLS_COUNT;
46 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.CLEAR_MISSED_CALLS_NOTIFICATION_ID;
47 |
48 |
49 | public class CallNotificationManager {
50 |
51 | private static final String VOICE_CHANNEL = "default";
52 |
53 | private NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
54 |
55 | public CallNotificationManager() {}
56 |
57 | public int getApplicationImportance(ReactApplicationContext context) {
58 | ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
59 | if (activityManager == null) {
60 | return 0;
61 | }
62 | List processInfos = activityManager.getRunningAppProcesses();
63 | if (processInfos == null) {
64 | return 0;
65 | }
66 |
67 | for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
68 | if (processInfo.processName.equals(context.getApplicationInfo().packageName)) {
69 | return processInfo.importance;
70 | }
71 | }
72 | return 0;
73 | }
74 |
75 | public Class getMainActivityClass(ReactApplicationContext context) {
76 | String packageName = context.getPackageName();
77 | Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
78 | String className = launchIntent.getComponent().getClassName();
79 | try {
80 | return Class.forName(className);
81 | } catch (ClassNotFoundException e) {
82 | e.printStackTrace();
83 | return null;
84 | }
85 | }
86 |
87 | public Intent getLaunchIntent(ReactApplicationContext context,
88 | int notificationId,
89 | CallInvite callInvite,
90 | Boolean shouldStartNewTask,
91 | int appImportance
92 | ) {
93 | Intent launchIntent = new Intent(context, getMainActivityClass(context));
94 |
95 | int launchFlag = Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP;
96 | if (shouldStartNewTask || appImportance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
97 | launchFlag = Intent.FLAG_ACTIVITY_NEW_TASK;
98 | }
99 |
100 | launchIntent.setAction(ACTION_INCOMING_CALL)
101 | .putExtra(INCOMING_CALL_NOTIFICATION_ID, notificationId)
102 | .addFlags(
103 | launchFlag +
104 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED +
105 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD +
106 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON +
107 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
108 | );
109 |
110 | if (callInvite != null) {
111 | launchIntent.putExtra(INCOMING_CALL_INVITE, callInvite);
112 | }
113 | return launchIntent;
114 | }
115 |
116 | public void createIncomingCallNotification(ReactApplicationContext context,
117 | CallInvite callInvite,
118 | int notificationId,
119 | Intent launchIntent)
120 | {
121 | if (BuildConfig.DEBUG) {
122 | Log.d(TAG, "createIncomingCallNotification intent "+launchIntent.getFlags());
123 | }
124 | PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
125 |
126 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
127 |
128 | /*
129 | * Pass the notification id and call sid to use as an identifier to cancel the
130 | * notification later
131 | */
132 | Bundle extras = new Bundle();
133 | extras.putInt(INCOMING_CALL_NOTIFICATION_ID, notificationId);
134 | extras.putString(CALL_SID_KEY, callInvite.getCallSid());
135 | extras.putString(NOTIFICATION_TYPE, ACTION_INCOMING_CALL);
136 | /*
137 | * Create the notification shown in the notification drawer
138 | */
139 | initCallNotificationsChannel(notificationManager);
140 |
141 | NotificationCompat.Builder notificationBuilder =
142 | new NotificationCompat.Builder(context, VOICE_CHANNEL)
143 | .setPriority(NotificationCompat.PRIORITY_HIGH)
144 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
145 | .setCategory(NotificationCompat.CATEGORY_CALL)
146 | .setSmallIcon(R.drawable.ic_call_white_24dp)
147 | .setContentTitle("Incoming call")
148 | .setContentText(callInvite.getFrom() + " is calling")
149 | .setOngoing(true)
150 | .setAutoCancel(true)
151 | .setExtras(extras)
152 | .setFullScreenIntent(pendingIntent, true);
153 |
154 | // build notification large icon
155 | Resources res = context.getResources();
156 | int largeIconResId = res.getIdentifier("ic_launcher", "mipmap", context.getPackageName());
157 | Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
158 |
159 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
160 | if (largeIconResId != 0) {
161 | notificationBuilder.setLargeIcon(largeIconBitmap);
162 | }
163 | }
164 |
165 | // Reject action
166 | Intent rejectIntent = new Intent(ACTION_REJECT_CALL)
167 | .putExtra(INCOMING_CALL_NOTIFICATION_ID, notificationId)
168 | .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
169 | PendingIntent pendingRejectIntent = PendingIntent.getBroadcast(context, 1, rejectIntent,
170 | PendingIntent.FLAG_UPDATE_CURRENT);
171 | notificationBuilder.addAction(0, "DISMISS", pendingRejectIntent);
172 |
173 | // Answer action
174 | Intent answerIntent = new Intent(ACTION_ANSWER_CALL);
175 | answerIntent
176 | .putExtra(INCOMING_CALL_NOTIFICATION_ID, notificationId)
177 | .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
178 | PendingIntent pendingAnswerIntent = PendingIntent.getBroadcast(context, 0, answerIntent,
179 | PendingIntent.FLAG_UPDATE_CURRENT);
180 | notificationBuilder.addAction(R.drawable.ic_call_white_24dp, "ANSWER", pendingAnswerIntent);
181 |
182 | notificationManager.notify(notificationId, notificationBuilder.build());
183 | TwilioVoiceModule.callNotificationMap.put(INCOMING_NOTIFICATION_PREFIX+callInvite.getCallSid(), notificationId);
184 | }
185 |
186 | public void initCallNotificationsChannel(NotificationManager notificationManager) {
187 | if (Build.VERSION.SDK_INT < 26) {
188 | return;
189 | }
190 | NotificationChannel channel = new NotificationChannel(VOICE_CHANNEL,
191 | "Primary Voice Channel", NotificationManager.IMPORTANCE_DEFAULT);
192 | channel.setLightColor(Color.GREEN);
193 | channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
194 | notificationManager.createNotificationChannel(channel);
195 | }
196 |
197 | public void createMissedCallNotification(ReactApplicationContext context, CallInvite callInvite) {
198 | SharedPreferences sharedPref = context.getSharedPreferences(PREFERENCE_KEY, Context.MODE_PRIVATE);
199 | SharedPreferences.Editor sharedPrefEditor = sharedPref.edit();
200 |
201 | /*
202 | * Create a PendingIntent to specify the action when the notification is
203 | * selected in the notification drawer
204 | */
205 | Intent intent = new Intent(context, getMainActivityClass(context));
206 | intent.setAction(ACTION_MISSED_CALL)
207 | .putExtra(INCOMING_CALL_NOTIFICATION_ID, MISSED_CALLS_NOTIFICATION_ID)
208 | .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
209 | PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
210 |
211 | Intent clearMissedCallsCountIntent = new Intent(ACTION_CLEAR_MISSED_CALLS_COUNT)
212 | .putExtra(INCOMING_CALL_NOTIFICATION_ID, CLEAR_MISSED_CALLS_NOTIFICATION_ID);
213 | PendingIntent clearMissedCallsCountPendingIntent = PendingIntent.getBroadcast(context, 0, clearMissedCallsCountIntent, 0);
214 | /*
215 | * Pass the notification id and call sid to use as an identifier to open the notification
216 | */
217 | Bundle extras = new Bundle();
218 | extras.putInt(INCOMING_CALL_NOTIFICATION_ID, MISSED_CALLS_NOTIFICATION_ID);
219 | extras.putString(CALL_SID_KEY, callInvite.getCallSid());
220 | extras.putString(NOTIFICATION_TYPE, ACTION_MISSED_CALL);
221 |
222 | /*
223 | * Create the notification shown in the notification drawer
224 | */
225 | NotificationCompat.Builder notification =
226 | new NotificationCompat.Builder(context, VOICE_CHANNEL)
227 | .setGroup(MISSED_CALLS_GROUP)
228 | .setGroupSummary(true)
229 | .setPriority(NotificationCompat.PRIORITY_DEFAULT)
230 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
231 | .setCategory(NotificationCompat.CATEGORY_MESSAGE)
232 | .setSmallIcon(R.drawable.ic_call_missed_white_24dp)
233 | .setContentTitle("Missed call")
234 | .setContentText(callInvite.getFrom() + " called")
235 | .setAutoCancel(true)
236 | .setShowWhen(true)
237 | .setExtras(extras)
238 | .setDeleteIntent(clearMissedCallsCountPendingIntent)
239 | .setContentIntent(pendingIntent);
240 |
241 | int missedCalls = sharedPref.getInt(MISSED_CALLS_GROUP, 0);
242 | missedCalls++;
243 | if (missedCalls == 1) {
244 | inboxStyle = new NotificationCompat.InboxStyle();
245 | inboxStyle.setBigContentTitle("Missed call");
246 | } else {
247 | inboxStyle.setBigContentTitle(String.valueOf(missedCalls) + " missed calls");
248 | }
249 | inboxStyle.addLine("from: " +callInvite.getFrom());
250 | sharedPrefEditor.putInt(MISSED_CALLS_GROUP, missedCalls);
251 | sharedPrefEditor.commit();
252 |
253 | notification.setStyle(inboxStyle);
254 |
255 | // build notification large icon
256 | Resources res = context.getResources();
257 | int largeIconResId = res.getIdentifier("ic_launcher", "mipmap", context.getPackageName());
258 | Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
259 |
260 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && largeIconResId != 0) {
261 | notification.setLargeIcon(largeIconBitmap);
262 | }
263 |
264 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
265 | notificationManager.notify(MISSED_CALLS_NOTIFICATION_ID, notification.build());
266 | }
267 |
268 | public void createHangupLocalNotification(ReactApplicationContext context, String callSid, String caller) {
269 | PendingIntent pendingHangupIntent = PendingIntent.getBroadcast(
270 | context,
271 | 0,
272 | new Intent(ACTION_HANGUP_CALL).putExtra(INCOMING_CALL_NOTIFICATION_ID, HANGUP_NOTIFICATION_ID),
273 | PendingIntent.FLAG_UPDATE_CURRENT
274 | );
275 | Intent launchIntent = new Intent(context, getMainActivityClass(context));
276 | launchIntent.setAction(ACTION_INCOMING_CALL)
277 | .putExtra(INCOMING_CALL_NOTIFICATION_ID, HANGUP_NOTIFICATION_ID)
278 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
279 |
280 | PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
281 |
282 | /*
283 | * Pass the notification id and call sid to use as an identifier to cancel the
284 | * notification later
285 | */
286 | Bundle extras = new Bundle();
287 | extras.putInt(INCOMING_CALL_NOTIFICATION_ID, HANGUP_NOTIFICATION_ID);
288 | extras.putString(CALL_SID_KEY, callSid);
289 | extras.putString(NOTIFICATION_TYPE, ACTION_HANGUP_CALL);
290 |
291 | NotificationCompat.Builder notification = new NotificationCompat.Builder(context, VOICE_CHANNEL)
292 | .setContentTitle("Call in progress")
293 | .setContentText(caller)
294 | .setSmallIcon(R.drawable.ic_call_white_24dp)
295 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
296 | .setPriority(NotificationCompat.PRIORITY_HIGH)
297 | .setCategory(NotificationCompat.CATEGORY_CALL)
298 | .setOngoing(true)
299 | .setUsesChronometer(true)
300 | .setExtras(extras)
301 | .setContentIntent(activityPendingIntent);
302 |
303 | notification.addAction(0, "HANG UP", pendingHangupIntent);
304 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
305 | // Create notifications channel (required for API > 25)
306 | initCallNotificationsChannel(notificationManager);
307 | notificationManager.notify(HANGUP_NOTIFICATION_ID, notification.build());
308 | }
309 |
310 | public void removeIncomingCallNotification(ReactApplicationContext context,
311 | CancelledCallInvite callInvite,
312 | int notificationId) {
313 | if (BuildConfig.DEBUG) {
314 | Log.d(TAG, "removeIncomingCallNotification");
315 | }
316 | if (context == null) {
317 | Log.e(TAG, "Context is null");
318 | return;
319 | }
320 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
321 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
322 | if (callInvite != null) {
323 | /*
324 | * If the incoming call message was cancelled then remove the notification by matching
325 | * it with the call sid from the list of notifications in the notification drawer.
326 | */
327 | StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();
328 | for (StatusBarNotification statusBarNotification : activeNotifications) {
329 | Notification notification = statusBarNotification.getNotification();
330 | String notificationType = notification.extras.getString(NOTIFICATION_TYPE);
331 | if (callInvite.getCallSid().equals(notification.extras.getString(CALL_SID_KEY)) &&
332 | notificationType != null && notificationType.equals(ACTION_INCOMING_CALL)) {
333 | notificationManager.cancel(notification.extras.getInt(INCOMING_CALL_NOTIFICATION_ID));
334 | }
335 | }
336 | } else if (notificationId != 0) {
337 | notificationManager.cancel(notificationId);
338 | }
339 | } else {
340 | if (notificationId != 0) {
341 | notificationManager.cancel(notificationId);
342 | } else if (callInvite != null) {
343 | String notificationKey = INCOMING_NOTIFICATION_PREFIX+callInvite.getCallSid();
344 | if (TwilioVoiceModule.callNotificationMap.containsKey(notificationKey)) {
345 | notificationId = TwilioVoiceModule.callNotificationMap.get(notificationKey);
346 | notificationManager.cancel(notificationId);
347 | TwilioVoiceModule.callNotificationMap.remove(notificationKey);
348 | }
349 | }
350 | }
351 | }
352 |
353 | public void removeHangupNotification(ReactApplicationContext context) {
354 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
355 | notificationManager.cancel(HANGUP_NOTIFICATION_ID);
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/EventManager.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice;
2 |
3 | import androidx.annotation.Nullable;
4 | import android.util.Log;
5 |
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.bridge.WritableMap;
8 | import com.facebook.react.modules.core.DeviceEventManagerModule;
9 |
10 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
11 |
12 | public class EventManager {
13 |
14 | private ReactApplicationContext mContext;
15 |
16 | public static final String EVENT_PROXIMITY = "proximity";
17 | public static final String EVENT_WIRED_HEADSET = "wiredHeadset";
18 |
19 | public static final String EVENT_DEVICE_READY = "deviceReady";
20 | public static final String EVENT_DEVICE_NOT_READY = "deviceNotReady";
21 | public static final String EVENT_CONNECTION_DID_CONNECT = "connectionDidConnect";
22 | public static final String EVENT_CONNECTION_DID_DISCONNECT = "connectionDidDisconnect";
23 | public static final String EVENT_DEVICE_DID_RECEIVE_INCOMING = "deviceDidReceiveIncoming";
24 | public static final String EVENT_CALL_STATE_RINGING = "callStateRinging";
25 | public static final String EVENT_CALL_INVITE_CANCELLED = "callInviteCancelled";
26 | public static final String EVENT_CONNECTION_IS_RECONNECTING = "connectionIsReconnecting";
27 | public static final String EVENT_CONNECTION_DID_RECONNECT = "connectionDidReconnect";
28 |
29 |
30 | public EventManager(ReactApplicationContext context) {
31 | mContext = context;
32 | }
33 |
34 | public void sendEvent(String eventName, @Nullable WritableMap params) {
35 | if (BuildConfig.DEBUG) {
36 | Log.d(TAG, "sendEvent "+eventName+" params "+params);
37 | }
38 | if (mContext.hasActiveCatalystInstance()) {
39 | mContext
40 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
41 | .emit(eventName, params);
42 | } else {
43 | if (BuildConfig.DEBUG) {
44 | Log.d(TAG, "failed Catalyst instance not active");
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/HeadsetManager.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.media.AudioManager;
8 | import android.util.Log;
9 |
10 | import com.facebook.react.bridge.Arguments;
11 | import com.facebook.react.bridge.ReactApplicationContext;
12 | import com.facebook.react.bridge.WritableMap;
13 |
14 | import static com.hoxfon.react.RNTwilioVoice.EventManager.EVENT_WIRED_HEADSET;
15 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
16 |
17 | public class HeadsetManager {
18 |
19 | private BroadcastReceiver wiredHeadsetReceiver;
20 | private static final String ACTION_HEADSET_PLUG = (android.os.Build.VERSION.SDK_INT >= 21)
21 | ? AudioManager.ACTION_HEADSET_PLUG
22 | : Intent.ACTION_HEADSET_PLUG;
23 |
24 | private EventManager eventManager;
25 |
26 | public HeadsetManager(EventManager em) {
27 | eventManager = em;
28 | }
29 |
30 | public void startWiredHeadsetEvent(ReactApplicationContext context) {
31 | if (wiredHeadsetReceiver != null) {
32 | return;
33 | }
34 | if (context == null) {
35 | if (BuildConfig.DEBUG) {
36 | Log.d(TAG, "startWiredHeadsetEvent() reactContext is null");
37 | }
38 | return;
39 | }
40 | if (BuildConfig.DEBUG) {
41 | Log.d(TAG, "startWiredHeadsetEvent()");
42 | }
43 | wiredHeadsetReceiver = new BroadcastReceiver() {
44 | @Override
45 | public void onReceive(Context context, Intent intent) {
46 | if (ACTION_HEADSET_PLUG.equals(intent.getAction())) {
47 | String deviceName = intent.getStringExtra("name");
48 | if (deviceName == null) {
49 | deviceName = "";
50 | }
51 | WritableMap data = Arguments.createMap();
52 | data.putBoolean("isPlugged", (intent.getIntExtra("state", 0) == 1) ? true : false);
53 | data.putBoolean("hasMic", (intent.getIntExtra("microphone", 0) == 1) ? true : false);
54 | data.putString("deviceName", deviceName);
55 | eventManager.sendEvent(EVENT_WIRED_HEADSET, data);
56 | }
57 | }
58 | };
59 | context.registerReceiver(wiredHeadsetReceiver, new IntentFilter(ACTION_HEADSET_PLUG));
60 | }
61 |
62 | public void stopWiredHeadsetEvent(ReactApplicationContext context) {
63 | if (wiredHeadsetReceiver == null) {
64 | return;
65 | }
66 | if (context == null) {
67 | if (BuildConfig.DEBUG) {
68 | Log.d(TAG, "stopWiredHeadsetEvent() reactContext is null");
69 | }
70 | return;
71 | }
72 | if (BuildConfig.DEBUG) {
73 | Log.d(TAG, "stopWiredHeadsetEvent()");
74 | }
75 | context.unregisterReceiver(wiredHeadsetReceiver);
76 | wiredHeadsetReceiver = null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/ProximityManager.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice;
2 |
3 | import android.content.Context;
4 | import android.hardware.Sensor;
5 | import android.hardware.SensorEvent;
6 | import android.hardware.SensorEventListener;
7 | import android.hardware.SensorManager;
8 | import android.os.PowerManager;
9 | import android.os.PowerManager.WakeLock;
10 | import android.util.Log;
11 |
12 | import com.facebook.react.bridge.Arguments;
13 | import com.facebook.react.bridge.ReactApplicationContext;
14 | import com.facebook.react.bridge.WritableMap;
15 |
16 | import java.lang.reflect.Field;
17 | import java.lang.reflect.Method;
18 |
19 | import static com.hoxfon.react.RNTwilioVoice.EventManager.EVENT_PROXIMITY;
20 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
21 |
22 | public class ProximityManager {
23 |
24 | private static final String ERROR_PROXIMITY_SENSOR_NOT_SUPPORTED = "Proximity sensor is not supported.";
25 | private static final String ERROR_PROXIMITY_LOCK_NOT_SUPPORTED = "Proximity lock is not supported.";
26 |
27 | private SensorManager sensorManager;
28 |
29 | private Sensor proximitySensor;
30 | private SensorEventListener proximityListener;
31 |
32 | private WakeLock proximityWakeLock = null;
33 | private PowerManager powerManager;
34 |
35 | private EventManager eventManager;
36 |
37 | public ProximityManager(ReactApplicationContext context, EventManager em) {
38 | eventManager = em;
39 | powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
40 | sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
41 | proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
42 | initProximityWakeLock();
43 | }
44 |
45 | private void initProximityWakeLock() {
46 | // Check if PROXIMITY_SCREEN_OFF_WAKE_LOCK is implemented, not part of public api.
47 | // PROXIMITY_SCREEN_OFF_WAKE_LOCK and isWakeLockLevelSupported are available from api 21
48 | try {
49 | boolean isSupported;
50 | int proximityScreenOffWakeLock;
51 | if (android.os.Build.VERSION.SDK_INT < 21) {
52 | Field field = PowerManager.class.getDeclaredField("PROXIMITY_SCREEN_OFF_WAKE_LOCK");
53 | proximityScreenOffWakeLock = (Integer) field.get(null);
54 |
55 | Method method = powerManager.getClass().getDeclaredMethod("getSupportedWakeLockFlags");
56 | int powerManagerSupportedFlags = (Integer) method.invoke(powerManager);
57 | isSupported = ((powerManagerSupportedFlags & proximityScreenOffWakeLock) != 0x0);
58 | } else {
59 | proximityScreenOffWakeLock = powerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK;
60 | isSupported = powerManager.isWakeLockLevelSupported(proximityScreenOffWakeLock);
61 | }
62 | if (isSupported) {
63 | proximityWakeLock = powerManager.newWakeLock(proximityScreenOffWakeLock, TAG);
64 | proximityWakeLock.setReferenceCounted(false);
65 | }
66 | } catch (Exception e) {
67 | Log.e(TAG, "Failed to get proximity screen locker.");
68 | }
69 | }
70 |
71 | private void turnScreenOn() {
72 | if (proximityWakeLock == null) {
73 | if (BuildConfig.DEBUG) {
74 | Log.d(TAG, ERROR_PROXIMITY_LOCK_NOT_SUPPORTED);
75 | }
76 | return;
77 | }
78 | synchronized (proximityWakeLock) {
79 | if (proximityWakeLock.isHeld()) {
80 | if (BuildConfig.DEBUG) {
81 | Log.d(TAG, "turnScreenOn()");
82 | }
83 | if (android.os.Build.VERSION.SDK_INT >= 21) {
84 | proximityWakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
85 | }
86 | }
87 | }
88 | }
89 |
90 | private void turnScreenOff() {
91 | if (proximityWakeLock == null) {
92 | if (BuildConfig.DEBUG) {
93 | Log.d(TAG, ERROR_PROXIMITY_LOCK_NOT_SUPPORTED);
94 | }
95 | return;
96 | }
97 | synchronized (proximityWakeLock) {
98 | if (!proximityWakeLock.isHeld()) {
99 | if (BuildConfig.DEBUG) {
100 | Log.d(TAG, "turnScreenOff()");
101 | }
102 | proximityWakeLock.acquire();
103 | }
104 | }
105 | }
106 |
107 | private void initProximitySensorEventListener() {
108 | if (proximityListener != null) {
109 | return;
110 | }
111 | if (BuildConfig.DEBUG) {
112 | Log.d(TAG, "initProximitySensorEventListener()");
113 | }
114 | proximityListener = new SensorEventListener() {
115 | @Override
116 | public void onSensorChanged(SensorEvent sensorEvent) {
117 | if (sensorEvent.sensor.getType() == Sensor.TYPE_PROXIMITY) {
118 | boolean isNear = false;
119 | if (sensorEvent.values[0] < proximitySensor.getMaximumRange()) {
120 | isNear = true;
121 | }
122 | if (isNear) {
123 | turnScreenOff();
124 | } else {
125 | turnScreenOn();
126 | }
127 | WritableMap data = Arguments.createMap();
128 | data.putBoolean("isNear", isNear);
129 | eventManager.sendEvent(EVENT_PROXIMITY, data);
130 | }
131 | }
132 |
133 | @Override
134 | public void onAccuracyChanged(Sensor sensor, int accuracy) {
135 | }
136 | };
137 | }
138 |
139 | public void startProximitySensor() {
140 | if (proximitySensor == null) {
141 | Log.e(TAG, ERROR_PROXIMITY_SENSOR_NOT_SUPPORTED);
142 | return;
143 | }
144 | initProximitySensorEventListener();
145 | if (proximityListener != null) {
146 | if (BuildConfig.DEBUG) {
147 | Log.d(TAG, "register proximity listener");
148 | }
149 | // SensorManager.SENSOR_DELAY_FASTEST(0 ms),
150 | // SensorManager.SENSOR_DELAY_GAME(20 ms),
151 | // SensorManager.SENSOR_DELAY_UI(60 ms),
152 | // SensorManager.SENSOR_DELAY_NORMAL(200 ms)
153 | sensorManager.registerListener(
154 | proximityListener,
155 | proximitySensor,
156 | SensorManager.SENSOR_DELAY_NORMAL
157 | );
158 | }
159 | }
160 |
161 | public void stopProximitySensor() {
162 | if (proximitySensor == null) {
163 | Log.e(TAG, ERROR_PROXIMITY_SENSOR_NOT_SUPPORTED);
164 | return;
165 | }
166 | if (proximityListener != null) {
167 | if (BuildConfig.DEBUG) {
168 | Log.d(TAG, "unregister proximity listener");
169 | }
170 | sensorManager.unregisterListener(proximityListener);
171 | proximityListener = null;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/SoundPoolManager.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice;
2 |
3 | import android.content.Context;
4 | import android.media.Ringtone;
5 | import android.media.RingtoneManager;
6 | import android.net.Uri;
7 |
8 | public class SoundPoolManager {
9 |
10 | private boolean playing = false;
11 | private static SoundPoolManager instance;
12 | private Ringtone ringtone = null;
13 |
14 | private SoundPoolManager(Context context) {
15 | Uri ringtoneSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
16 | ringtone = RingtoneManager.getRingtone(context, ringtoneSound);
17 | }
18 |
19 | public static SoundPoolManager getInstance(Context context) {
20 | if (instance == null) {
21 | instance = new SoundPoolManager(context);
22 | }
23 | return instance;
24 | }
25 |
26 | public void playRinging() {
27 | if (!playing) {
28 | ringtone.play();
29 | playing = true;
30 | }
31 | }
32 |
33 | public void stopRinging() {
34 | if (playing) {
35 | ringtone.stop();
36 | playing = false;
37 | }
38 | }
39 |
40 | public void playDisconnect() {
41 | if (!playing) {
42 | ringtone.stop();
43 | playing = false;
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/TwilioVoicePackage.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.JavaScriptModule;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.uimanager.ViewManager;
8 |
9 | import java.util.Collections;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class TwilioVoicePackage implements ReactPackage {
14 |
15 | private boolean mShouldAskForPermission;
16 | public TwilioVoicePackage() {
17 | mShouldAskForPermission = true;
18 | }
19 |
20 | public TwilioVoicePackage(boolean shouldAskForPermissions) {
21 | mShouldAskForPermission = shouldAskForPermissions;
22 | }
23 | // Deprecated in RN 0.47.0
24 | public List> createJSModules() {
25 | return Collections.emptyList();
26 | }
27 |
28 | @Override
29 | public List createViewManagers(ReactApplicationContext reactContext) {
30 | return Collections.emptyList();
31 | }
32 |
33 | @Override
34 | public List createNativeModules(ReactApplicationContext reactContext) {
35 | List modules = new ArrayList<>();
36 | modules.add(new TwilioVoiceModule(reactContext, mShouldAskForPermission));
37 | return modules;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseMessagingService.java:
--------------------------------------------------------------------------------
1 | package com.hoxfon.react.RNTwilioVoice.fcm;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Intent;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import androidx.localbroadcastmanager.content.LocalBroadcastManager;
8 | import android.util.Log;
9 |
10 | import com.facebook.react.ReactApplication;
11 | import com.facebook.react.ReactInstanceManager;
12 | import com.facebook.react.bridge.ReactApplicationContext;
13 | import com.facebook.react.bridge.ReactContext;
14 |
15 | import com.google.firebase.messaging.FirebaseMessagingService;
16 | import com.google.firebase.messaging.RemoteMessage;
17 | import com.hoxfon.react.RNTwilioVoice.BuildConfig;
18 | import com.hoxfon.react.RNTwilioVoice.CallNotificationManager;
19 | import com.twilio.voice.CallInvite;
20 | import com.twilio.voice.CancelledCallInvite;
21 | import com.twilio.voice.MessageListener;
22 | import com.twilio.voice.Voice;
23 |
24 | import java.util.Map;
25 | import java.util.Random;
26 |
27 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
28 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_FCM_TOKEN;
29 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_INCOMING_CALL;
30 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_CANCEL_CALL_INVITE;
31 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_CALL_INVITE;
32 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.CANCELLED_CALL_INVITE;
33 | import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_CALL_NOTIFICATION_ID;
34 | import com.hoxfon.react.RNTwilioVoice.SoundPoolManager;
35 |
36 | public class VoiceFirebaseMessagingService extends FirebaseMessagingService {
37 |
38 | private CallNotificationManager callNotificationManager;
39 |
40 | @Override
41 | public void onCreate() {
42 | super.onCreate();
43 | callNotificationManager = new CallNotificationManager();
44 | }
45 |
46 | @Override
47 | public void onNewToken(String token) {
48 | Log.d(TAG, "Refreshed token: " + token);
49 |
50 | // Notify Activity of FCM token
51 | Intent intent = new Intent(ACTION_FCM_TOKEN);
52 | LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
53 | }
54 |
55 | /**
56 | * Called when message is received.
57 | *
58 | * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
59 | */
60 | @Override
61 | public void onMessageReceived(RemoteMessage remoteMessage) {
62 | if (BuildConfig.DEBUG) {
63 | Log.d(TAG, "Bundle data: " + remoteMessage.getData());
64 | }
65 |
66 | // Check if message contains a data payload.
67 | if (remoteMessage.getData().size() > 0) {
68 | Map data = remoteMessage.getData();
69 |
70 | // If notification ID is not provided by the user for push notification, generate one at random
71 | Random randomNumberGenerator = new Random(System.currentTimeMillis());
72 | final int notificationId = randomNumberGenerator.nextInt();
73 |
74 | boolean valid = Voice.handleMessage(data, new MessageListener() {
75 | @Override
76 | public void onCallInvite(final CallInvite callInvite) {
77 |
78 | // We need to run this on the main thread, as the React code assumes that is true.
79 | // Namely, DevServerHelper constructs a Handler() without a Looper, which triggers:
80 | // "Can't create handler inside thread that has not called Looper.prepare()"
81 | Handler handler = new Handler(Looper.getMainLooper());
82 | handler.post(new Runnable() {
83 | public void run() {
84 | // Construct and load our normal React JS code bundle
85 | ReactInstanceManager mReactInstanceManager = ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager();
86 | ReactContext context = mReactInstanceManager.getCurrentReactContext();
87 | // If it's constructed, send a notification
88 | if (context != null) {
89 | int appImportance = callNotificationManager.getApplicationImportance((ReactApplicationContext)context);
90 | if (BuildConfig.DEBUG) {
91 | Log.d(TAG, "CONTEXT present appImportance = " + appImportance);
92 | }
93 | Intent launchIntent = callNotificationManager.getLaunchIntent(
94 | (ReactApplicationContext)context,
95 | notificationId,
96 | callInvite,
97 | false,
98 | appImportance
99 | );
100 | // app is not in foreground
101 | if (appImportance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
102 | context.startActivity(launchIntent);
103 | }
104 | Intent intent = new Intent(ACTION_INCOMING_CALL);
105 | intent.putExtra(INCOMING_CALL_NOTIFICATION_ID, notificationId);
106 | intent.putExtra(INCOMING_CALL_INVITE, callInvite);
107 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
108 | } else {
109 | // Otherwise wait for construction, then handle the incoming call
110 | mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
111 | public void onReactContextInitialized(ReactContext context) {
112 | int appImportance = callNotificationManager.getApplicationImportance((ReactApplicationContext)context);
113 | if (BuildConfig.DEBUG) {
114 | Log.d(TAG, "CONTEXT not present appImportance = " + appImportance);
115 | }
116 | Intent launchIntent = callNotificationManager.getLaunchIntent((ReactApplicationContext)context, notificationId, callInvite, true, appImportance);
117 | context.startActivity(launchIntent);
118 | Intent intent = new Intent(ACTION_INCOMING_CALL);
119 | intent.putExtra(INCOMING_CALL_NOTIFICATION_ID, notificationId);
120 | intent.putExtra(INCOMING_CALL_INVITE, callInvite);
121 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
122 | callNotificationManager.createIncomingCallNotification(
123 | (ReactApplicationContext) context, callInvite, notificationId,
124 | launchIntent);
125 | }
126 | });
127 | if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
128 | // Construct it in the background
129 | mReactInstanceManager.createReactContextInBackground();
130 | }
131 | }
132 | }
133 | });
134 | }
135 |
136 | @Override
137 | public void onCancelledCallInvite(final CancelledCallInvite cancelledCallInvite) {
138 | Handler handler = new Handler(Looper.getMainLooper());
139 | handler.post(new Runnable() {
140 | public void run() {
141 | VoiceFirebaseMessagingService.this.sendCancelledCallInviteToActivity(cancelledCallInvite);
142 | }
143 | });
144 | }
145 | });
146 |
147 | if (!valid) {
148 | Log.e(TAG, "The message was not a valid Twilio Voice SDK payload: " + remoteMessage.getData());
149 | }
150 | }
151 |
152 | // Check if message contains a notification payload.
153 | if (remoteMessage.getNotification() != null) {
154 | Log.e(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
155 | }
156 | }
157 |
158 | /*
159 | * Send the CancelledCallInvite to the TwilioVoiceModule
160 | */
161 | private void sendCancelledCallInviteToActivity(CancelledCallInvite cancelledCallInvite) {
162 | SoundPoolManager.getInstance((this)).stopRinging();
163 | Intent intent = new Intent(ACTION_CANCEL_CALL_INVITE);
164 | intent.putExtra(CANCELLED_CALL_INVITE, cancelledCallInvite);
165 | LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_call_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_call_missed_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_call_missed_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_call_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/images/vjeux_tweet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoxfon/react-native-twilio-programmable-voice/ff6b897676ce7ed99c75b177ec4e3bca19d88875/images/vjeux_tweet.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | NativeModules,
3 | NativeEventEmitter,
4 | Platform,
5 | } from 'react-native'
6 |
7 | const ANDROID = 'android'
8 | const IOS = 'ios'
9 |
10 | const TwilioVoice = NativeModules.RNTwilioVoice
11 |
12 | const NativeAppEventEmitter = new NativeEventEmitter(TwilioVoice)
13 |
14 | const _eventHandlers = {
15 | deviceReady: new Map(),
16 | deviceNotReady: new Map(),
17 | deviceDidReceiveIncoming: new Map(),
18 | connectionDidConnect: new Map(),
19 | connectionIsReconnecting: new Map(),
20 | connectionDidReconnect: new Map(),
21 | connectionDidDisconnect: new Map(),
22 | callStateRinging: new Map(),
23 | callInviteCancelled: new Map(),
24 | callRejected: new Map(),
25 | }
26 |
27 | const Twilio = {
28 | // initialize the library with Twilio access token
29 | // return {initialized: true} when the initialization started
30 | // Listen to deviceReady and deviceNotReady events to see whether
31 | // the initialization succeeded
32 | async initWithToken(token) {
33 | if (typeof token !== 'string') {
34 | return {
35 | initialized: false,
36 | err: 'Invalid token, token must be a string'
37 | }
38 | };
39 |
40 | const result = await TwilioVoice.initWithAccessToken(token)
41 | // native react promise present only for Android
42 | // iOS initWithAccessToken doesn't return
43 | if (Platform.OS === IOS) {
44 | return {
45 | initialized: true,
46 | }
47 | }
48 | return result
49 | },
50 | connect(params = {}) {
51 | TwilioVoice.connect(params)
52 | },
53 | disconnect: TwilioVoice.disconnect,
54 | accept() {
55 | if (Platform.OS === IOS) {
56 | return
57 | }
58 | TwilioVoice.accept()
59 | },
60 | reject() {
61 | if (Platform.OS === IOS) {
62 | return
63 | }
64 | TwilioVoice.reject()
65 | },
66 | ignore() {
67 | if (Platform.OS === IOS) {
68 | return
69 | }
70 | TwilioVoice.ignore()
71 | },
72 | setMuted: TwilioVoice.setMuted,
73 | setSpeakerPhone: TwilioVoice.setSpeakerPhone,
74 | sendDigits: TwilioVoice.sendDigits,
75 | hold: TwilioVoice.setOnHold,
76 | requestPermissions(senderId) {
77 | if (Platform.OS === ANDROID) {
78 | TwilioVoice.requestPermissions(senderId)
79 | }
80 | },
81 | getActiveCall: TwilioVoice.getActiveCall,
82 | getCallInvite: TwilioVoice.getCallInvite,
83 | configureCallKit(params = {}) {
84 | if (Platform.OS === IOS) {
85 | TwilioVoice.configureCallKit(params)
86 | }
87 | },
88 | unregister() {
89 | if (Platform.OS === IOS) {
90 | TwilioVoice.unregister()
91 | }
92 | },
93 | addEventListener(type, handler) {
94 | if (!_eventHandlers.hasOwnProperty(type)) {
95 | throw new Error('Event handler not found: ' + type)
96 | }
97 | if (_eventHandlers[type])
98 | if (_eventHandlers[type].has(handler)) {
99 | return
100 | }
101 | _eventHandlers[type].set(handler, NativeAppEventEmitter.addListener(type, rtn => { handler(rtn) }))
102 | },
103 | removeEventListener(type, handler) {
104 | if (!_eventHandlers[type].has(handler)) {
105 | return
106 | }
107 | _eventHandlers[type].get(handler).remove()
108 | _eventHandlers[type].delete(handler)
109 | }
110 | }
111 |
112 | export default Twilio
113 |
--------------------------------------------------------------------------------
/ios/RNTwilioVoice.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 38F71DFA1E9C333F0067E86F /* RNTwilioVoice.m in Sources */ = {isa = PBXBuildFile; fileRef = 38F71DF91E9C333F0067E86F /* RNTwilioVoice.m */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXFileReference section */
14 | 38F71DF51E9C333F0067E86F /* libRNTwilioVoice.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNTwilioVoice.a; sourceTree = BUILT_PRODUCTS_DIR; };
15 | 38F71DF81E9C333F0067E86F /* RNTwilioVoice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNTwilioVoice.h; sourceTree = ""; };
16 | 38F71DF91E9C333F0067E86F /* RNTwilioVoice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNTwilioVoice.m; sourceTree = ""; };
17 | /* End PBXFileReference section */
18 |
19 | /* Begin PBXFrameworksBuildPhase section */
20 | 016DD9B31ECCA22F00315CD4 /* Frameworks */ = {
21 | isa = PBXFrameworksBuildPhase;
22 | buildActionMask = 2147483647;
23 | files = (
24 | );
25 | runOnlyForDeploymentPostprocessing = 0;
26 | };
27 | /* End PBXFrameworksBuildPhase section */
28 |
29 | /* Begin PBXGroup section */
30 | 016DD9801ECC9B4B00315CD4 /* Frameworks */ = {
31 | isa = PBXGroup;
32 | children = (
33 | );
34 | name = Frameworks;
35 | sourceTree = "";
36 | };
37 | 38F71DEC1E9C333E0067E86F = {
38 | isa = PBXGroup;
39 | children = (
40 | 38F71DF71E9C333F0067E86F /* RNTwilioVoice */,
41 | 38F71DF61E9C333F0067E86F /* Products */,
42 | 016DD9801ECC9B4B00315CD4 /* Frameworks */,
43 | );
44 | sourceTree = "";
45 | };
46 | 38F71DF61E9C333F0067E86F /* Products */ = {
47 | isa = PBXGroup;
48 | children = (
49 | 38F71DF51E9C333F0067E86F /* libRNTwilioVoice.a */,
50 | );
51 | name = Products;
52 | sourceTree = "";
53 | };
54 | 38F71DF71E9C333F0067E86F /* RNTwilioVoice */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 38F71DF81E9C333F0067E86F /* RNTwilioVoice.h */,
58 | 38F71DF91E9C333F0067E86F /* RNTwilioVoice.m */,
59 | );
60 | path = RNTwilioVoice;
61 | sourceTree = "";
62 | };
63 | /* End PBXGroup section */
64 |
65 | /* Begin PBXNativeTarget section */
66 | 38F71DF41E9C333F0067E86F /* RNTwilioVoice */ = {
67 | isa = PBXNativeTarget;
68 | buildConfigurationList = 38F71DFE1E9C333F0067E86F /* Build configuration list for PBXNativeTarget "RNTwilioVoice" */;
69 | buildPhases = (
70 | 38F71DF11E9C333F0067E86F /* Sources */,
71 | 016DD9B31ECCA22F00315CD4 /* Frameworks */,
72 | );
73 | buildRules = (
74 | );
75 | dependencies = (
76 | );
77 | name = RNTwilioVoice;
78 | productName = TwilioVoice;
79 | productReference = 38F71DF51E9C333F0067E86F /* libRNTwilioVoice.a */;
80 | productType = "com.apple.product-type.library.static";
81 | };
82 | /* End PBXNativeTarget section */
83 |
84 | /* Begin PBXProject section */
85 | 38F71DED1E9C333E0067E86F /* Project object */ = {
86 | isa = PBXProject;
87 | attributes = {
88 | LastUpgradeCheck = 1240;
89 | ORGANIZATIONNAME = Erva;
90 | TargetAttributes = {
91 | 38F71DF41E9C333F0067E86F = {
92 | CreatedOnToolsVersion = 8.3.1;
93 | ProvisioningStyle = Automatic;
94 | };
95 | };
96 | };
97 | buildConfigurationList = 38F71DF01E9C333E0067E86F /* Build configuration list for PBXProject "RNTwilioVoice" */;
98 | compatibilityVersion = "Xcode 3.2";
99 | developmentRegion = en;
100 | hasScannedForEncodings = 0;
101 | knownRegions = (
102 | en,
103 | Base,
104 | );
105 | mainGroup = 38F71DEC1E9C333E0067E86F;
106 | productRefGroup = 38F71DF61E9C333F0067E86F /* Products */;
107 | projectDirPath = "";
108 | projectRoot = "";
109 | targets = (
110 | 38F71DF41E9C333F0067E86F /* RNTwilioVoice */,
111 | );
112 | };
113 | /* End PBXProject section */
114 |
115 | /* Begin PBXSourcesBuildPhase section */
116 | 38F71DF11E9C333F0067E86F /* Sources */ = {
117 | isa = PBXSourcesBuildPhase;
118 | buildActionMask = 2147483647;
119 | files = (
120 | 38F71DFA1E9C333F0067E86F /* RNTwilioVoice.m in Sources */,
121 | );
122 | runOnlyForDeploymentPostprocessing = 0;
123 | };
124 | /* End PBXSourcesBuildPhase section */
125 |
126 | /* Begin XCBuildConfiguration section */
127 | 38F71DFC1E9C333F0067E86F /* Debug */ = {
128 | isa = XCBuildConfiguration;
129 | buildSettings = {
130 | ALWAYS_SEARCH_USER_PATHS = NO;
131 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
132 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
133 | CLANG_ANALYZER_NONNULL = YES;
134 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
135 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
136 | CLANG_CXX_LIBRARY = "libc++";
137 | CLANG_ENABLE_MODULES = YES;
138 | CLANG_ENABLE_OBJC_ARC = YES;
139 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
140 | CLANG_WARN_BOOL_CONVERSION = YES;
141 | CLANG_WARN_COMMA = YES;
142 | CLANG_WARN_CONSTANT_CONVERSION = YES;
143 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
144 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
145 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
146 | CLANG_WARN_EMPTY_BODY = YES;
147 | CLANG_WARN_ENUM_CONVERSION = YES;
148 | CLANG_WARN_INFINITE_RECURSION = YES;
149 | CLANG_WARN_INT_CONVERSION = YES;
150 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
151 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
152 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
153 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
154 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
155 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
156 | CLANG_WARN_STRICT_PROTOTYPES = YES;
157 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
158 | CLANG_WARN_UNREACHABLE_CODE = YES;
159 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
160 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
161 | COPY_PHASE_STRIP = NO;
162 | DEBUG_INFORMATION_FORMAT = dwarf;
163 | ENABLE_STRICT_OBJC_MSGSEND = YES;
164 | ENABLE_TESTABILITY = YES;
165 | FRAMEWORK_SEARCH_PATHS = "";
166 | GCC_C_LANGUAGE_STANDARD = gnu99;
167 | GCC_DYNAMIC_NO_PIC = NO;
168 | GCC_NO_COMMON_BLOCKS = YES;
169 | GCC_OPTIMIZATION_LEVEL = 0;
170 | GCC_PREPROCESSOR_DEFINITIONS = (
171 | "DEBUG=1",
172 | "$(inherited)",
173 | );
174 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
175 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
176 | GCC_WARN_UNDECLARED_SELECTOR = YES;
177 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
178 | GCC_WARN_UNUSED_FUNCTION = YES;
179 | GCC_WARN_UNUSED_VARIABLE = YES;
180 | HEADER_SEARCH_PATHS = (
181 | "$(SRCROOT)/../../React/**",
182 | "$(SRCROOT)/../../react-native/React/**",
183 | "${SRCROOT}/../../../ios/Pods/Headers/Public",
184 | "${SRCROOT}/../../../ios/Pods/Headers/Public/TwilioVoice",
185 | );
186 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
187 | MTL_ENABLE_DEBUG_INFO = YES;
188 | ONLY_ACTIVE_ARCH = YES;
189 | SDKROOT = iphoneos;
190 | };
191 | name = Debug;
192 | };
193 | 38F71DFD1E9C333F0067E86F /* Release */ = {
194 | isa = XCBuildConfiguration;
195 | buildSettings = {
196 | ALWAYS_SEARCH_USER_PATHS = NO;
197 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
198 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
199 | CLANG_ANALYZER_NONNULL = YES;
200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
202 | CLANG_CXX_LIBRARY = "libc++";
203 | CLANG_ENABLE_MODULES = YES;
204 | CLANG_ENABLE_OBJC_ARC = YES;
205 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
206 | CLANG_WARN_BOOL_CONVERSION = YES;
207 | CLANG_WARN_COMMA = YES;
208 | CLANG_WARN_CONSTANT_CONVERSION = YES;
209 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
212 | CLANG_WARN_EMPTY_BODY = YES;
213 | CLANG_WARN_ENUM_CONVERSION = YES;
214 | CLANG_WARN_INFINITE_RECURSION = YES;
215 | CLANG_WARN_INT_CONVERSION = YES;
216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
217 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
220 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
222 | CLANG_WARN_STRICT_PROTOTYPES = YES;
223 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
224 | CLANG_WARN_UNREACHABLE_CODE = YES;
225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
226 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
227 | COPY_PHASE_STRIP = NO;
228 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
229 | ENABLE_NS_ASSERTIONS = NO;
230 | ENABLE_STRICT_OBJC_MSGSEND = YES;
231 | FRAMEWORK_SEARCH_PATHS = "";
232 | GCC_C_LANGUAGE_STANDARD = gnu99;
233 | GCC_NO_COMMON_BLOCKS = YES;
234 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
235 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
236 | GCC_WARN_UNDECLARED_SELECTOR = YES;
237 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
238 | GCC_WARN_UNUSED_FUNCTION = YES;
239 | GCC_WARN_UNUSED_VARIABLE = YES;
240 | HEADER_SEARCH_PATHS = (
241 | "$(SRCROOT)/../../React/**",
242 | "$(SRCROOT)/../../react-native/React/**",
243 | "${SRCROOT}/../../../ios/Pods/Headers/Public",
244 | "${SRCROOT}/../../../ios/Pods/Headers/Public/TwilioVoice",
245 | );
246 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
247 | MTL_ENABLE_DEBUG_INFO = NO;
248 | SDKROOT = iphoneos;
249 | VALIDATE_PRODUCT = YES;
250 | };
251 | name = Release;
252 | };
253 | 38F71DFF1E9C333F0067E86F /* Debug */ = {
254 | isa = XCBuildConfiguration;
255 | buildSettings = {
256 | FRAMEWORK_SEARCH_PATHS = (
257 | "$(inherited)",
258 | "$(PROJECT_DIR)",
259 | "$(SRCROOT)/../../../ios/Pods/TwilioVoice",
260 | );
261 | OTHER_LDFLAGS = "-ObjC";
262 | PRODUCT_NAME = "$(TARGET_NAME)";
263 | SKIP_INSTALL = YES;
264 | };
265 | name = Debug;
266 | };
267 | 38F71E001E9C333F0067E86F /* Release */ = {
268 | isa = XCBuildConfiguration;
269 | buildSettings = {
270 | FRAMEWORK_SEARCH_PATHS = (
271 | "$(inherited)",
272 | "$(PROJECT_DIR)",
273 | "$(SRCROOT)/../../../ios/Pods/TwilioVoice",
274 | );
275 | OTHER_LDFLAGS = "-ObjC";
276 | PRODUCT_NAME = "$(TARGET_NAME)";
277 | SKIP_INSTALL = YES;
278 | };
279 | name = Release;
280 | };
281 | /* End XCBuildConfiguration section */
282 |
283 | /* Begin XCConfigurationList section */
284 | 38F71DF01E9C333E0067E86F /* Build configuration list for PBXProject "RNTwilioVoice" */ = {
285 | isa = XCConfigurationList;
286 | buildConfigurations = (
287 | 38F71DFC1E9C333F0067E86F /* Debug */,
288 | 38F71DFD1E9C333F0067E86F /* Release */,
289 | );
290 | defaultConfigurationIsVisible = 0;
291 | defaultConfigurationName = Release;
292 | };
293 | 38F71DFE1E9C333F0067E86F /* Build configuration list for PBXNativeTarget "RNTwilioVoice" */ = {
294 | isa = XCConfigurationList;
295 | buildConfigurations = (
296 | 38F71DFF1E9C333F0067E86F /* Debug */,
297 | 38F71E001E9C333F0067E86F /* Release */,
298 | );
299 | defaultConfigurationIsVisible = 0;
300 | defaultConfigurationName = Release;
301 | };
302 | /* End XCConfigurationList section */
303 | };
304 | rootObject = 38F71DED1E9C333E0067E86F /* Project object */;
305 | }
306 |
--------------------------------------------------------------------------------
/ios/RNTwilioVoice/RNTwilioVoice.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface RNTwilioVoice : RCTEventEmitter
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/RNTwilioVoice/RNTwilioVoice.m:
--------------------------------------------------------------------------------
1 | #import "RNTwilioVoice.h"
2 | #import
3 |
4 | @import AVFoundation;
5 | @import PushKit;
6 | @import CallKit;
7 | @import TwilioVoice;
8 |
9 | NSString * const kCachedDeviceToken = @"CachedDeviceToken";
10 | NSString * const kCallerNameCustomParameter = @"CallerName";
11 |
12 | @interface RNTwilioVoice ()
13 |
14 | @property (nonatomic, strong) PKPushRegistry *voipRegistry;
15 | @property (nonatomic, strong) void(^incomingPushCompletionCallback)(void);
16 | @property (nonatomic, strong) TVOCallInvite *callInvite;
17 | @property (nonatomic, strong) void(^callKitCompletionCallback)(BOOL);
18 | @property (nonatomic, strong) TVODefaultAudioDevice *audioDevice;
19 | @property (nonatomic, strong) NSMutableDictionary *activeCallInvites;
20 | @property (nonatomic, strong) NSMutableDictionary *activeCalls;
21 |
22 | // activeCall represents the last connected call
23 | @property (nonatomic, strong) TVOCall *activeCall;
24 | @property (nonatomic, strong) CXProvider *callKitProvider;
25 | @property (nonatomic, strong) CXCallController *callKitCallController;
26 | @property (nonatomic, assign) BOOL userInitiatedDisconnect;
27 |
28 | @end
29 |
30 | @implementation RNTwilioVoice {
31 | NSMutableDictionary *_settings;
32 | NSMutableDictionary *_callParams;
33 | NSString *_tokenUrl;
34 | NSString *_token;
35 | }
36 |
37 | NSString * const StateConnecting = @"CONNECTING";
38 | NSString * const StateConnected = @"CONNECTED";
39 | NSString * const StateDisconnected = @"DISCONNECTED";
40 | NSString * const StateRejected = @"REJECTED";
41 |
42 | - (dispatch_queue_t)methodQueue
43 | {
44 | return dispatch_get_main_queue();
45 | }
46 |
47 | RCT_EXPORT_MODULE()
48 |
49 | - (NSArray *)supportedEvents
50 | {
51 | return @[@"connectionDidConnect", @"connectionDidDisconnect", @"callRejected", @"deviceReady", @"deviceNotReady", @"deviceDidReceiveIncoming", @"callInviteCancelled", @"callStateRinging", @"connectionIsReconnecting", @"connectionDidReconnect"];
52 | }
53 |
54 | @synthesize bridge = _bridge;
55 |
56 | - (void)dealloc {
57 | if (self.callKitProvider) {
58 | [self.callKitProvider invalidate];
59 | }
60 |
61 | [[NSNotificationCenter defaultCenter] removeObserver:self];
62 | }
63 |
64 | RCT_EXPORT_METHOD(initWithAccessToken:(NSString *)token) {
65 | _token = token;
66 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppTerminateNotification) name:UIApplicationWillTerminateNotification object:nil];
67 | [self initPushRegistry];
68 | }
69 |
70 | RCT_EXPORT_METHOD(configureCallKit: (NSDictionary *)params) {
71 | if (self.callKitCallController == nil) {
72 | /*
73 | * The important thing to remember when providing a TVOAudioDevice is that the device must be set
74 | * before performing any other actions with the SDK (such as connecting a Call, or accepting an incoming Call).
75 | * In this case we've already initialized our own `TVODefaultAudioDevice` instance which we will now set.
76 | */
77 | self.audioDevice = [TVODefaultAudioDevice audioDevice];
78 | TwilioVoice.audioDevice = self.audioDevice;
79 |
80 | self.activeCallInvites = [NSMutableDictionary dictionary];
81 | self.activeCalls = [NSMutableDictionary dictionary];
82 |
83 | _settings = [[NSMutableDictionary alloc] initWithDictionary:params];
84 | CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:params[@"appName"]];
85 | configuration.maximumCallGroups = 1;
86 | configuration.maximumCallsPerCallGroup = 1;
87 | if (_settings[@"imageName"]) {
88 | configuration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:_settings[@"imageName"]]);
89 | }
90 | if (_settings[@"ringtoneSound"]) {
91 | configuration.ringtoneSound = _settings[@"ringtoneSound"];
92 | }
93 |
94 | _callKitProvider = [[CXProvider alloc] initWithConfiguration:configuration];
95 | [_callKitProvider setDelegate:self queue:nil];
96 |
97 | NSLog(@"CallKit Initialized");
98 |
99 | self.callKitCallController = [[CXCallController alloc] init];
100 | }
101 | }
102 |
103 | RCT_EXPORT_METHOD(connect: (NSDictionary *)params) {
104 | NSLog(@"Calling phone number %@", [params valueForKey:@"To"]);
105 |
106 | UIDevice* device = [UIDevice currentDevice];
107 | device.proximityMonitoringEnabled = YES;
108 |
109 | if (self.activeCall && self.activeCall.state == TVOCallStateConnected) {
110 | [self performEndCallActionWithUUID:self.activeCall.uuid];
111 | } else {
112 | NSUUID *uuid = [NSUUID UUID];
113 | NSString *handle = [params valueForKey:@"To"];
114 | _callParams = [[NSMutableDictionary alloc] initWithDictionary:params];
115 | [self performStartCallActionWithUUID:uuid handle:handle];
116 | }
117 | }
118 |
119 | RCT_EXPORT_METHOD(disconnect) {
120 | NSLog(@"Disconnecting call. UUID %@", self.activeCall.uuid.UUIDString);
121 | self.userInitiatedDisconnect = YES;
122 | [self performEndCallActionWithUUID:self.activeCall.uuid];
123 | }
124 |
125 | RCT_EXPORT_METHOD(setMuted: (BOOL *)muted) {
126 | NSLog(@"Mute/UnMute call");
127 | self.activeCall.muted = muted ? YES : NO;
128 | }
129 |
130 | RCT_EXPORT_METHOD(setOnHold: (BOOL *)isOnHold) {
131 | NSLog(@"Hold/Unhold call");
132 | self.activeCall.onHold = isOnHold ? YES : NO;
133 | }
134 |
135 | RCT_EXPORT_METHOD(setSpeakerPhone: (BOOL *)speaker) {
136 | [self toggleAudioRoute: speaker ? YES : NO];
137 | }
138 |
139 | RCT_EXPORT_METHOD(sendDigits: (NSString *)digits) {
140 | if (self.activeCall && self.activeCall.state == TVOCallStateConnected) {
141 | NSLog(@"SendDigits %@", digits);
142 | [self.activeCall sendDigits:digits];
143 | }
144 | }
145 |
146 | RCT_EXPORT_METHOD(unregister) {
147 | NSLog(@"unregister");
148 | NSString *accessToken = [self fetchAccessToken];
149 | NSString *cachedDeviceToken = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedDeviceToken];
150 | if ([cachedDeviceToken length] > 0) {
151 | [TwilioVoice unregisterWithAccessToken:accessToken
152 | deviceToken:cachedDeviceToken
153 | completion:^(NSError * _Nullable error) {
154 | if (error) {
155 | NSLog(@"An error occurred while unregistering: %@", [error localizedDescription]);
156 | } else {
157 | NSLog(@"Successfully unregistered for VoIP push notifications.");
158 | }
159 | }];
160 | }
161 | }
162 |
163 | RCT_REMAP_METHOD(getActiveCall,
164 | activeCallResolver:(RCTPromiseResolveBlock)resolve
165 | activeCallRejecter:(RCTPromiseRejectBlock)reject) {
166 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
167 | if (self.activeCall) {
168 | if (self.activeCall.sid) {
169 | [params setObject:self.activeCall.sid forKey:@"call_sid"];
170 | }
171 | if (self.activeCall.to) {
172 | [params setObject:self.activeCall.to forKey:@"call_to"];
173 | }
174 | if (self.activeCall.from) {
175 | [params setObject:self.activeCall.from forKey:@"call_from"];
176 | }
177 | if (self.activeCall.state == TVOCallStateConnected) {
178 | [params setObject:StateConnected forKey:@"call_state"];
179 | } else if (self.activeCall.state == TVOCallStateConnecting) {
180 | [params setObject:StateConnecting forKey:@"call_state"];
181 | } else if (self.activeCall.state == TVOCallStateDisconnected) {
182 | [params setObject:StateDisconnected forKey:@"call_state"];
183 | }
184 | }
185 | resolve(params);
186 | }
187 |
188 | RCT_REMAP_METHOD(getCallInvite,
189 | callInvieteResolver:(RCTPromiseResolveBlock)resolve
190 | callInviteRejecter:(RCTPromiseRejectBlock)reject) {
191 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
192 | if (self.activeCallInvites.count) {
193 | // considering only the first call invite
194 | TVOCallInvite *callInvite = [self.activeCallInvites valueForKey:[self.activeCallInvites allKeys][self.activeCallInvites.count-1]];
195 | if (callInvite.callSid) {
196 | [params setObject:callInvite.callSid forKey:@"call_sid"];
197 | }
198 | if (callInvite.from) {
199 | [params setObject:callInvite.from forKey:@"call_from"];
200 | }
201 | if (callInvite.to) {
202 | [params setObject:callInvite.to forKey:@"call_to"];
203 | }
204 | }
205 | resolve(params);
206 | }
207 |
208 | - (void)initPushRegistry {
209 | self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
210 | self.voipRegistry.delegate = self;
211 | self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
212 | }
213 |
214 | - (NSString *)fetchAccessToken {
215 | if (_tokenUrl) {
216 | NSString *accessToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:_tokenUrl]
217 | encoding:NSUTF8StringEncoding
218 | error:nil];
219 | return accessToken;
220 | } else {
221 | return _token;
222 | }
223 | }
224 |
225 | #pragma mark - PKPushRegistryDelegate
226 | - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type {
227 | NSLog(@"pushRegistry:didUpdatePushCredentials:forType");
228 |
229 | if ([type isEqualToString:PKPushTypeVoIP]) {
230 | const unsigned *tokenBytes = [credentials.token bytes];
231 | NSString *deviceTokenString = [NSString stringWithFormat:@"<%08x %08x %08x %08x %08x %08x %08x %08x>",
232 | ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
233 | ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
234 | ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
235 | NSString *accessToken = [self fetchAccessToken];
236 | NSString *cachedDeviceToken = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedDeviceToken];
237 | if (![cachedDeviceToken isEqualToString:deviceTokenString]) {
238 | cachedDeviceToken = deviceTokenString;
239 |
240 | /*
241 | * Perform registration if a new device token is detected.
242 | */
243 | [TwilioVoice registerWithAccessToken:accessToken
244 | deviceToken:cachedDeviceToken
245 | completion:^(NSError *error) {
246 | if (error) {
247 | NSLog(@"An error occurred while registering: %@", [error localizedDescription]);
248 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
249 | [params setObject:[error localizedDescription] forKey:@"err"];
250 |
251 | [self sendEventWithName:@"deviceNotReady" body:params];
252 | }
253 | else {
254 | NSLog(@"Successfully registered for VoIP push notifications.");
255 |
256 | /*
257 | * Save the device token after successfully registered.
258 | */
259 | [[NSUserDefaults standardUserDefaults] setObject:cachedDeviceToken forKey:kCachedDeviceToken];
260 | [self sendEventWithName:@"deviceReady" body:nil];
261 | }
262 | }];
263 | }
264 | }
265 | }
266 |
267 | - (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type {
268 | NSLog(@"pushRegistry:didInvalidatePushTokenForType");
269 |
270 | if ([type isEqualToString:PKPushTypeVoIP]) {
271 | NSString *accessToken = [self fetchAccessToken];
272 |
273 | NSString *cachedDeviceToken = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedDeviceToken];
274 | if ([cachedDeviceToken length] > 0) {
275 | [TwilioVoice unregisterWithAccessToken:accessToken
276 | deviceToken:cachedDeviceToken
277 | completion:^(NSError * _Nullable error) {
278 | if (error) {
279 | NSLog(@"An error occurred while unregistering: %@", [error localizedDescription]);
280 | } else {
281 | NSLog(@"Successfully unregistered for VoIP push notifications.");
282 | }
283 | }];
284 | }
285 | }
286 | }
287 |
288 | /**
289 | * Try using the `pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:` method if
290 | * your application is targeting iOS 11. According to the docs, this delegate method is deprecated by Apple.
291 | */
292 | - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
293 | NSLog(@"pushRegistry:didReceiveIncomingPushWithPayload:forType");
294 | if ([type isEqualToString:PKPushTypeVoIP]) {
295 | // The Voice SDK will use main queue to invoke `cancelledCallInviteReceived:error` when delegate queue is not passed
296 | if (![TwilioVoice handleNotification:payload.dictionaryPayload delegate:self delegateQueue: nil]) {
297 | NSLog(@"This is not a valid Twilio Voice notification.");
298 | }
299 | }
300 | }
301 |
302 | /**
303 | * This delegate method is available on iOS 11 and above. Call the completion handler once the
304 | * notification payload is passed to the `TwilioVoice.handleNotification()` method.
305 | */
306 | - (void)pushRegistry:(PKPushRegistry *)registry
307 | didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
308 | forType:(PKPushType)type
309 | withCompletionHandler:(void (^)(void))completion {
310 | NSLog(@"pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler");
311 |
312 | // Save for later when the notification is properly handled.
313 | self.incomingPushCompletionCallback = completion;
314 |
315 |
316 | if ([type isEqualToString:PKPushTypeVoIP]) {
317 | // The Voice SDK will use main queue to invoke `cancelledCallInviteReceived:error` when delegate queue is not passed
318 | if (![TwilioVoice handleNotification:payload.dictionaryPayload delegate:self delegateQueue: nil]) {
319 | NSLog(@"This is not a valid Twilio Voice notification.");
320 | }
321 | }
322 | if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
323 | // Save for later when the notification is properly handled.
324 | self.incomingPushCompletionCallback = completion;
325 | } else {
326 | /**
327 | * The Voice SDK processes the call notification and returns the call invite synchronously. Report the incoming call to
328 | * CallKit and fulfill the completion before exiting this callback method.
329 | */
330 | completion();
331 | }
332 | }
333 |
334 | - (void)incomingPushHandled {
335 | if (self.incomingPushCompletionCallback) {
336 | self.incomingPushCompletionCallback();
337 | self.incomingPushCompletionCallback = nil;
338 | }
339 | }
340 |
341 | #pragma mark - TVONotificationDelegate
342 | - (void)callInviteReceived:(TVOCallInvite *)callInvite {
343 | /**
344 | * Calling `[TwilioVoice handleNotification:delegate:]` will synchronously process your notification payload and
345 | * provide you a `TVOCallInvite` object. Report the incoming call to CallKit upon receiving this callback.
346 | */
347 | NSLog(@"callInviteReceived");
348 | NSString *from = @"Unknown";
349 | if (callInvite.from) {
350 | from = [callInvite.from stringByReplacingOccurrencesOfString:@"client:" withString:@""];
351 | }
352 | if (callInvite.customParameters[kCallerNameCustomParameter]) {
353 | from = callInvite.customParameters[kCallerNameCustomParameter];
354 | }
355 | // Always report to CallKit
356 | [self reportIncomingCallFrom:from withUUID:callInvite.uuid];
357 | self.activeCallInvites[[callInvite.uuid UUIDString]] = callInvite;
358 | if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
359 | [self incomingPushHandled];
360 | }
361 |
362 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
363 | if (callInvite.callSid) {
364 | [params setObject:callInvite.callSid forKey:@"call_sid"];
365 | }
366 | if (callInvite.from) {
367 | [params setObject:callInvite.from forKey:@"call_from"];
368 | }
369 | if (callInvite.to) {
370 | [params setObject:callInvite.to forKey:@"call_to"];
371 | }
372 | [self sendEventWithName:@"deviceDidReceiveIncoming" body:params];
373 | }
374 |
375 | - (void)cancelledCallInviteReceived:(nonnull TVOCancelledCallInvite *)cancelledCallInvite {
376 | /**
377 | * The SDK may call `[TVONotificationDelegate callInviteReceived:error:]` asynchronously on the dispatch queue
378 | * with a `TVOCancelledCallInvite` if the caller hangs up or the client encounters any other error before the called
379 | * party could answer or reject the call.
380 | */
381 | NSLog(@"cancelledCallInviteReceived");
382 | TVOCallInvite *callInvite;
383 | for (NSString *activeCallInviteId in self.activeCallInvites) {
384 | TVOCallInvite *activeCallInvite = [self.activeCallInvites objectForKey:activeCallInviteId];
385 | if ([cancelledCallInvite.callSid isEqualToString:activeCallInvite.callSid]) {
386 | callInvite = activeCallInvite;
387 | break;
388 | }
389 | }
390 | if (callInvite) {
391 | [self performEndCallActionWithUUID:callInvite.uuid];
392 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
393 | if (callInvite.callSid) {
394 | [params setObject:callInvite.callSid forKey:@"call_sid"];
395 | }
396 | if (callInvite.from) {
397 | [params setObject:callInvite.from forKey:@"call_from"];
398 | }
399 | if (callInvite.to) {
400 | [params setObject:callInvite.to forKey:@"call_to"];
401 | }
402 | [self sendEventWithName:@"callInviteCancelled" body:params];
403 | }
404 | }
405 |
406 |
407 | - (void)cancelledCallInviteReceived:(TVOCancelledCallInvite *)cancelledCallInvite error:(NSError *)error {
408 | /**
409 | * The SDK may call `[TVONotificationDelegate callInviteReceived:error:]` asynchronously on the dispatch queue
410 | * with a `TVOCancelledCallInvite` if the caller hangs up or the client encounters any other error before the called
411 | * party could answer or reject the call.
412 | */
413 | NSLog(@"cancelledCallInviteReceived with error");
414 | TVOCallInvite *callInvite;
415 | for (NSString *activeCallInviteId in self.activeCallInvites) {
416 | TVOCallInvite *activeCallInvite = [self.activeCallInvites objectForKey:activeCallInviteId];
417 | if ([cancelledCallInvite.callSid isEqualToString:activeCallInvite.callSid]) {
418 | callInvite = activeCallInvite;
419 | break;
420 | }
421 | }
422 | if (callInvite) {
423 | [self performEndCallActionWithUUID:callInvite.uuid];
424 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
425 | if (callInvite.callSid) {
426 | [params setObject:callInvite.callSid forKey:@"call_sid"];
427 | }
428 | if (callInvite.from) {
429 | [params setObject:callInvite.from forKey:@"call_from"];
430 | }
431 | if (callInvite.to) {
432 | [params setObject:callInvite.to forKey:@"call_to"];
433 | }
434 | [self sendEventWithName:@"callInviteCancelled" body:params];
435 | }
436 | }
437 |
438 | - (void)notificationError:(NSError *)error {
439 | NSLog(@"notificationError: %@", [error localizedDescription]);
440 | }
441 |
442 | #pragma mark - TVOCallDelegate
443 | - (void)callDidStartRinging:(TVOCall *)call {
444 | NSLog(@"callDidStartRinging");
445 |
446 | /*
447 | When [answerOnBridge](https://www.twilio.com/docs/voice/twiml/dial#answeronbridge) is enabled in the
448 | TwiML verb, the caller will not hear the ringback while the call is ringing and awaiting to be
449 | accepted on the callee's side. The application can use the `AVAudioPlayer` to play custom audio files
450 | between the `[TVOCallDelegate callDidStartRinging:]` and the `[TVOCallDelegate callDidConnect:]` callbacks.
451 | */
452 | NSMutableDictionary *callParams = [[NSMutableDictionary alloc] init];
453 | [callParams setObject:call.sid forKey:@"call_sid"];
454 | if (call.from) {
455 | [callParams setObject:call.from forKey:@"call_from"];
456 | }
457 | [self sendEventWithName:@"callStateRinging" body:callParams];
458 | }
459 |
460 | #pragma mark - TVOCallDelegate
461 | - (void)callDidConnect:(TVOCall *)call {
462 | NSLog(@"callDidConnect");
463 | self.callKitCompletionCallback(YES);
464 |
465 | NSMutableDictionary *callParams = [[NSMutableDictionary alloc] init];
466 | [callParams setObject:call.sid forKey:@"call_sid"];
467 | if (call.state == TVOCallStateConnecting) {
468 | [callParams setObject:StateConnecting forKey:@"call_state"];
469 | } else if (call.state == TVOCallStateConnected) {
470 | [callParams setObject:StateConnected forKey:@"call_state"];
471 | }
472 |
473 | if (call.from) {
474 | [callParams setObject:call.from forKey:@"call_from"];
475 | }
476 | if (call.to) {
477 | [callParams setObject:call.to forKey:@"call_to"];
478 | }
479 | [self sendEventWithName:@"connectionDidConnect" body:callParams];
480 | }
481 |
482 | - (void)call:(TVOCall *)call isReconnectingWithError:(NSError *)error {
483 | NSLog(@"Call is reconnecting");
484 | NSMutableDictionary *callParams = [[NSMutableDictionary alloc] init];
485 | [callParams setObject:call.sid forKey:@"call_sid"];
486 | if (call.from) {
487 | [callParams setObject:call.from forKey:@"call_from"];
488 | }
489 | if (call.to) {
490 | [callParams setObject:call.to forKey:@"call_to"];
491 | }
492 | [self sendEventWithName:@"connectionIsReconnecting" body:callParams];
493 | }
494 |
495 | - (void)callDidReconnect:(TVOCall *)call {
496 | NSLog(@"Call reconnected");
497 | NSMutableDictionary *callParams = [[NSMutableDictionary alloc] init];
498 | [callParams setObject:call.sid forKey:@"call_sid"];
499 | if (call.from) {
500 | [callParams setObject:call.from forKey:@"call_from"];
501 | }
502 | if (call.to) {
503 | [callParams setObject:call.to forKey:@"call_to"];
504 | }
505 | [self sendEventWithName:@"connectionDidReconnect" body:callParams];
506 | }
507 |
508 | - (void)call:(TVOCall *)call didFailToConnectWithError:(NSError *)error {
509 | NSLog(@"Call failed to connect: %@", error);
510 |
511 | self.callKitCompletionCallback(NO);
512 | [self performEndCallActionWithUUID:call.uuid];
513 | [self callDisconnected:call error:error];
514 | }
515 |
516 | - (void)call:(TVOCall *)call didDisconnectWithError:(NSError *)error {
517 | if (error) {
518 | NSLog(@"didDisconnectWithError: %@", error);
519 | } else {
520 | NSLog(@"didDisconnect");
521 | }
522 |
523 | UIDevice* device = [UIDevice currentDevice];
524 | device.proximityMonitoringEnabled = NO;
525 |
526 | if (!self.userInitiatedDisconnect) {
527 | CXCallEndedReason reason = CXCallEndedReasonRemoteEnded;
528 | if (error) {
529 | reason = CXCallEndedReasonFailed;
530 | }
531 | [self.callKitProvider reportCallWithUUID:call.uuid endedAtDate:[NSDate date] reason:reason];
532 | }
533 | [self callDisconnected:call error:error];
534 | }
535 |
536 | - (void)callDisconnected:(TVOCall *)call error:(NSError *)error {
537 | NSLog(@"callDisconnect");
538 | if ([call isEqual:self.activeCall]) {
539 | self.activeCall = nil;
540 | }
541 | [self.activeCalls removeObjectForKey:call.uuid.UUIDString];
542 |
543 | self.userInitiatedDisconnect = NO;
544 |
545 | NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
546 | if (error) {
547 | NSString* errMsg = [error localizedDescription];
548 | if (error.localizedFailureReason) {
549 | errMsg = [error localizedFailureReason];
550 | }
551 | [params setObject:errMsg forKey:@"err"];
552 | }
553 | if (call.sid) {
554 | [params setObject:call.sid forKey:@"call_sid"];
555 | }
556 | if (call.to) {
557 | [params setObject:call.to forKey:@"call_to"];
558 | }
559 | if (call.from) {
560 | [params setObject:call.from forKey:@"call_from"];
561 | }
562 | if (call.state == TVOCallStateDisconnected) {
563 | [params setObject:StateDisconnected forKey:@"call_state"];
564 | }
565 | [self sendEventWithName:@"connectionDidDisconnect" body:params];
566 | }
567 |
568 | #pragma mark - AVAudioSession
569 | - (void)toggleAudioRoute:(BOOL)toSpeaker {
570 | // The mode set by the Voice SDK is "VoiceChat" so the default audio route is the built-in receiver.
571 | // Use port override to switch the route.
572 | self.audioDevice.block = ^ {
573 | // We will execute `kDefaultAVAudioSessionConfigurationBlock` first.
574 | kTVODefaultAVAudioSessionConfigurationBlock();
575 |
576 | // Overwrite the audio route
577 | AVAudioSession *session = [AVAudioSession sharedInstance];
578 | NSError *error = nil;
579 | if (toSpeaker) {
580 | if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]) {
581 | NSLog(@"Unable to reroute audio: %@", [error localizedDescription]);
582 | }
583 | } else {
584 | if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error]) {
585 | NSLog(@"Unable to reroute audio: %@", [error localizedDescription]);
586 | }
587 | }
588 | };
589 | self.audioDevice.block();
590 | }
591 |
592 | #pragma mark - CXProviderDelegate
593 | - (void)providerDidReset:(CXProvider *)provider {
594 | NSLog(@"providerDidReset");
595 | self.audioDevice.enabled = YES;
596 | }
597 |
598 | - (void)providerDidBegin:(CXProvider *)provider {
599 | NSLog(@"providerDidBegin");
600 | }
601 |
602 | - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession {
603 | NSLog(@"provider:didActivateAudioSession");
604 | self.audioDevice.enabled = YES;
605 | }
606 |
607 | - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession {
608 | NSLog(@"provider:didDeactivateAudioSession");
609 | self.audioDevice.enabled = NO;
610 | }
611 |
612 | - (void)provider:(CXProvider *)provider timedOutPerformingAction:(CXAction *)action {
613 | NSLog(@"provider:timedOutPerformingAction");
614 | }
615 |
616 | - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
617 | NSLog(@"provider:performStartCallAction");
618 |
619 | self.audioDevice.enabled = NO;
620 | self.audioDevice.block();
621 |
622 | [self.callKitProvider reportOutgoingCallWithUUID:action.callUUID startedConnectingAtDate:[NSDate date]];
623 |
624 | __weak typeof(self) weakSelf = self;
625 | [self performVoiceCallWithUUID:action.callUUID client:nil completion:^(BOOL success) {
626 | __strong typeof(self) strongSelf = weakSelf;
627 | if (success) {
628 | [strongSelf.callKitProvider reportOutgoingCallWithUUID:action.callUUID connectedAtDate:[NSDate date]];
629 | [action fulfill];
630 | } else {
631 | [action fail];
632 | }
633 | }];
634 | }
635 |
636 | - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
637 | NSLog(@"provider:performAnswerCallAction");
638 |
639 | self.audioDevice.enabled = NO;
640 | self.audioDevice.block();
641 | [self performAnswerVoiceCallWithUUID:action.callUUID completion:^(BOOL success) {
642 | if (success) {
643 | [action fulfill];
644 | } else {
645 | [action fail];
646 | }
647 | }];
648 |
649 | [action fulfill];
650 | }
651 |
652 | - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
653 | NSLog(@"provider:performEndCallAction");
654 |
655 | TVOCallInvite *callInvite = self.activeCallInvites[action.callUUID.UUIDString];
656 | TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
657 |
658 | if (callInvite) {
659 | [callInvite reject];
660 | [self sendEventWithName:@"callRejected" body:@"callRejected"];
661 | [self.activeCallInvites removeObjectForKey:callInvite.uuid.UUIDString];
662 | } else if (call) {
663 | [call disconnect];
664 | } else {
665 | NSLog(@"Unknown UUID to perform end-call action with");
666 | }
667 |
668 | self.audioDevice.enabled = YES;
669 | [action fulfill];
670 | }
671 |
672 | - (void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action {
673 | TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
674 | if (call) {
675 | [call setOnHold:action.isOnHold];
676 | [action fulfill];
677 | } else {
678 | [action fail];
679 | }
680 | }
681 |
682 | - (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
683 | TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
684 | if (call) {
685 | [call setMuted:action.isMuted];
686 | [action fulfill];
687 | } else {
688 | [action fail];
689 | }
690 | }
691 |
692 | - (void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCallAction *)action {
693 | TVOCall *call = self.activeCalls[action.callUUID.UUIDString];
694 | if (call && call.state == TVOCallStateConnected) {
695 | NSLog(@"SendDigits %@", action.digits);
696 | [call sendDigits:action.digits];
697 | }
698 | }
699 |
700 | #pragma mark - CallKit Actions
701 | - (void)performStartCallActionWithUUID:(NSUUID *)uuid handle:(NSString *)handle {
702 | if (uuid == nil || handle == nil) {
703 | return;
704 | }
705 |
706 | CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
707 | CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];
708 | CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction];
709 |
710 | [self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
711 | if (error) {
712 | NSLog(@"StartCallAction transaction request failed: %@", [error localizedDescription]);
713 | } else {
714 | NSLog(@"StartCallAction transaction request successful");
715 |
716 | CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
717 | callUpdate.remoteHandle = callHandle;
718 | callUpdate.supportsDTMF = YES;
719 | callUpdate.supportsHolding = YES;
720 | callUpdate.supportsGrouping = NO;
721 | callUpdate.supportsUngrouping = NO;
722 | callUpdate.hasVideo = NO;
723 |
724 | [self.callKitProvider reportCallWithUUID:uuid updated:callUpdate];
725 | }
726 | }];
727 | }
728 |
729 | - (void)reportIncomingCallFrom:(NSString *)from withUUID:(NSUUID *)uuid {
730 | CXHandleType type = [[from substringToIndex:1] isEqual:@"+"] ? CXHandleTypePhoneNumber : CXHandleTypeGeneric;
731 | // lets replace 'client:' with ''
732 | CXHandle *callHandle = [[CXHandle alloc] initWithType:type value:[from stringByReplacingOccurrencesOfString:@"client:" withString:@""]];
733 |
734 | CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
735 | callUpdate.remoteHandle = callHandle;
736 | callUpdate.supportsDTMF = YES;
737 | callUpdate.supportsHolding = YES;
738 | callUpdate.supportsGrouping = NO;
739 | callUpdate.supportsUngrouping = NO;
740 | callUpdate.hasVideo = NO;
741 |
742 | [self.callKitProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError *error) {
743 | if (!error) {
744 | NSLog(@"Incoming call successfully reported");
745 | } else {
746 | NSLog(@"Failed to report incoming call successfully: %@.", [error localizedDescription]);
747 | }
748 | }];
749 | }
750 |
751 | - (void)performEndCallActionWithUUID:(NSUUID *)uuid {
752 | if (uuid == nil) {
753 | return;
754 | }
755 |
756 | CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid];
757 | CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction];
758 |
759 | [self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
760 | if (error) {
761 | NSLog(@"EndCallAction transaction request failed: %@", [error localizedDescription]);
762 | }
763 | }];
764 | }
765 |
766 | - (void)performVoiceCallWithUUID:(NSUUID *)uuid
767 | client:(NSString *)client
768 | completion:(void(^)(BOOL success))completionHandler {
769 | __weak typeof(self) weakSelf = self;
770 | TVOConnectOptions *connectOptions = [TVOConnectOptions optionsWithAccessToken:[self fetchAccessToken] block:^(TVOConnectOptionsBuilder *builder) {
771 | __strong typeof(self) strongSelf = weakSelf;
772 | builder.params = strongSelf->_callParams;
773 | builder.uuid = uuid;
774 | }];
775 | TVOCall *call = [TwilioVoice connectWithOptions:connectOptions delegate:self];
776 | if (call) {
777 | self.activeCall = call;
778 | self.activeCalls[call.uuid.UUIDString] = call;
779 | }
780 | self.callKitCompletionCallback = completionHandler;
781 | }
782 |
783 | - (void)performAnswerVoiceCallWithUUID:(NSUUID *)uuid
784 | completion:(void(^)(BOOL success))completionHandler {
785 |
786 | TVOCallInvite *callInvite = self.activeCallInvites[uuid.UUIDString];
787 | NSAssert(callInvite, @"No CallInvite matches the UUID");
788 | TVOAcceptOptions *acceptOptions = [TVOAcceptOptions optionsWithCallInvite:callInvite block:^(TVOAcceptOptionsBuilder *builder) {
789 | builder.uuid = callInvite.uuid;
790 | }];
791 |
792 | TVOCall *call = [callInvite acceptWithOptions:acceptOptions delegate:self];
793 |
794 | if (!call) {
795 | completionHandler(NO);
796 | } else {
797 | self.callKitCompletionCallback = completionHandler;
798 | self.activeCall = call;
799 | self.activeCalls[call.uuid.UUIDString] = call;
800 | }
801 |
802 | [self.activeCallInvites removeObjectForKey:callInvite.uuid.UUIDString];
803 |
804 | if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 13) {
805 | [self incomingPushHandled];
806 | }
807 | }
808 |
809 | - (void)handleAppTerminateNotification {
810 | NSLog(@"handleAppTerminateNotification called");
811 |
812 | if (self.activeCall) {
813 | NSLog(@"handleAppTerminateNotification disconnecting an active call");
814 | [self.activeCall disconnect];
815 | }
816 | }
817 |
818 | @end
819 |
--------------------------------------------------------------------------------
/logs/android/beta5/in-answered-foreground:
--------------------------------------------------------------------------------
1 | Emulator Samsung Galaxy 8
2 | lib: com.twilio:voice-android:2.0.0-beta5
3 | firebase: com.google.firebase:firebase-messaging:10.2.4
4 | Android 7.0.0
5 |
6 |
7 | Incoming call Answered app in foreground
8 |
9 | call 1
10 |
11 | 05-08 04:44:05.974 9396-9503/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
12 | 05-08 04:44:05.974 9396-9503/ I/TwilioVoice: Registering with FCM
13 | 05-08 04:44:06.528 9396-9503/ D/TwilioVoice: Successfully registered FCM
14 | 05-08 04:44:06.528 9396-9503/ D/TwilioVoice: sendEvent deviceReady params null
15 | 05-08 04:46:21.481 9396-10559/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=, twi_message_type=twilio.voice.call, twi_call_sid=CA474665effb308d95f2a9042366a9340d, twi_message_id=FCM0eaa554fd6fa47afa3b3f31e86051308, twi_from=+}
16 | 05-08 04:46:21.489 9396-9396/ D/TwilioVoice: showNotification callInvite state: PENDING
17 | 05-08 04:46:21.499 9396-9396/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-415437527, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@4191057}]
18 | 05-08 04:46:21.499 9396-9396/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
19 | 05-08 04:46:21.531 9396-9396/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA474665effb308d95f2a9042366a9340d"} }
20 | 05-08 04:46:23.302 9396-9503/ D/TwilioVoice: accept()
21 | 05-08 04:46:23.309 9396-9503/ D/TwilioVoice: accept() activeCallInvite
22 | 05-08 04:46:23.309 9396-9503/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
23 | 05-08 04:46:23.320 9396-9503/ I/PJSIP: 04:46:23.320 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
24 | 05-08 04:46:23.320 9396-9503/ I/PJSIP: 04:46:23.320 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
25 | 05-08 04:46:28.392 9396-10688/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA474665effb308d95f2a9042366a9340d, twi_message_id=FCM6bc02cd8f98b44e08c3e54645d291dfd, twi_from=+}
26 | 05-08 04:46:28.393 9396-9396/ D/TwilioVoice: showNotification callInvite state: CANCELED
27 | 05-08 04:46:28.393 9396-9396/ D/TwilioVoice: removeIncomingCallNotification
28 | 05-08 04:46:28.393 9396-9396/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-415430616, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@fbbbdb7}]
29 | 05-08 04:46:28.393 9396-9396/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
30 | 05-08 04:46:28.393 9396-9396/ D/TwilioVoice: activeCallInvite was cancelled by +
31 | 05-08 04:46:28.402 9396-9396/ D/TwilioVoice: removeIncomingCallNotification
32 | 05-08 04:46:36.378 9396-9503/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA474665effb308d95f2a9042366a9340d"} }
33 | 05-08 04:46:48.431 9396-9503/ D/TwilioVoice: call disconnected
34 | 05-08 04:46:48.432 9396-9503/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA474665effb308d95f2a9042366a9340d"} }
35 |
36 | call 2
37 |
38 | 05-08 05:28:42.722 2816-2816/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 1164997672
39 | 05-08 05:28:42.724 2816-2816/ D/TwilioVoice: showNotification() callInvite PENDING
40 | 05-08 05:28:42.741 2816-2816/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1164997672, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@80bc0c7}]
41 | 05-08 05:28:42.741 2816-2816/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
42 | 05-08 05:28:42.925 2816-2816/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA241f40ba75c1a20a1bc2782057ac92bb"} }
43 | 05-08 05:28:42.925 2816-2816/ D/TwilioVoice: failed Catalyst instance not active
44 | 05-08 05:28:42.925 2816-2816/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1164997672, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@80bc0c7}]
45 | 05-08 05:28:42.926 2816-2816/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
46 | 05-08 05:28:42.930 2816-2816/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA241f40ba75c1a20a1bc2782057ac92bb"} }
47 | 05-08 05:28:42.930 2816-2816/ D/TwilioVoice: failed Catalyst instance not active
48 | 05-08 05:28:42.938 2816-2816/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1164997672, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@80bc0c7}]
49 | 05-08 05:28:42.938 2816-2816/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
50 | 05-08 05:28:42.940 2816-2816/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA241f40ba75c1a20a1bc2782057ac92bb"} }
51 | 05-08 05:28:45.039 2816-7103/ D/TwilioVoice: accept()
52 | 05-08 05:28:45.053 2816-7103/ D/TwilioVoice: accept() activeCallInvite
53 | 05-08 05:28:45.053 2816-7103/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
54 | 05-08 05:28:45.070 2816-7103/ I/PJSIP: 05:28:45.070 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
55 | 05-08 05:28:45.070 2816-7103/ I/PJSIP: 05:28:45.070 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
56 | 05-08 05:28:47.275 2816-7576/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA241f40ba75c1a20a1bc2782057ac92bb, twi_message_id=FCMc86c18c818144a668ab15092b993a5a6, twi_from=+}
57 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 548260510
58 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: showNotification() callInvite = CANCELED
59 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: removeIncomingCallNotification
60 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=548260510, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@bacbe36}]
61 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: ====> BEGIN this is the important block
62 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
63 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: activeCallInvite was cancelled by +
64 | 05-08 05:28:47.277 2816-2816/ D/TwilioVoice: creating a missed call, activeCallInvite: com.twilio.voice.CallInvite@bacbe36
65 | 05-08 05:28:47.288 2816-2816/ D/TwilioVoice: removeIncomingCallNotification
66 | 05-08 05:28:47.294 2816-2816/ D/TwilioVoice: ====> END this is the important block
67 | 05-08 05:28:47.294 2816-2816/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=548260510, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@bacbe36}]
68 | 05-08 05:28:47.294 2816-2816/ D/TwilioVoice: ====> BEGIN this is the important block
69 | 05-08 05:28:47.294 2816-2816/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
70 | 05-08 05:28:47.294 2816-2816/ D/TwilioVoice: activeCallInvite was cancelled by +
71 | 05-08 05:28:47.294 2816-2816/ D/TwilioVoice: creating a missed call, activeCallInvite: com.twilio.voice.CallInvite@bacbe36
72 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: removeIncomingCallNotification
73 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: ====> END this is the important block
74 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=548260510, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@bacbe36}]
75 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: ====> BEGIN this is the important block
76 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
77 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: activeCallInvite was cancelled by +
78 | 05-08 05:28:47.325 2816-2816/ D/TwilioVoice: creating a missed call, activeCallInvite: com.twilio.voice.CallInvite@bacbe36
79 | 05-08 05:28:47.335 2816-2816/ D/TwilioVoice: removeIncomingCallNotification
80 | 05-08 05:28:47.335 2816-2816/ D/TwilioVoice: ====> END this is the important block
81 | 05-08 05:28:47.757 2816-7103/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA241f40ba75c1a20a1bc2782057ac92bb"} }
82 | 05-08 05:28:59.922 2816-7103/ D/TwilioVoice: call disconnected
83 | 05-08 05:28:59.922 2816-7103/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA241f40ba75c1a20a1bc2782057ac92bb"} }
84 |
85 | call 3
86 |
87 | 05-08 05:39:06.617 11933-14557/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
88 | 05-08 05:39:06.617 11933-14557/ I/TwilioVoice: Registering with FCM
89 | 05-08 05:39:07.148 11933-14557/ D/TwilioVoice: Successfully registered FCM
90 | 05-08 05:39:07.148 11933-14557/ D/TwilioVoice: sendEvent deviceReady params null
91 | 05-08 05:39:15.732 11933-14790/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..Oazwr60eRsN41evu.9zo-9yRHiHjOwtWxqOCyn2eTsdW4z1iMi_1yQ67nwvizKgd47J1yUHzceN6LpU0IIPYustkFWXq2LeW57ENkTl5H7UXXS203huyFZPN1g4JOszDWszjsYpKMHuvnoyKX1jp4tX0H4cmfcaxgtl03ahBLb0WNfyHziD8_Dv08pXzpQBQYfX_wP4BrKPlpzFVZ1GqbOVP_pbmUjXmJMVFtUzAX-nJmcZqZkJ7T3XOx7ZYs23WmHTSpEWtK01Idvvb3dOb4rXOYqr7ad71UGGEw9nb3vI8TW-EWfK_gH3QUkP9fBMZr9SaaMw-8ExdCo6US0dbwPlFyCoHSHUrW8tBHNwRuleoWBuC0ubxnr4w1L1dwFZyIMiPw81DhlPycpW_U58jtB4MXoCARpwX6cMIAd6fXUqwKm0Q5VbR2abzeKpL_4eUVajX8ashE5rYHrNHRUHmeua7Tri4wEHTW12pLu74zoaeMQ9H66CKl2vK6fCaAGNt5XcEn5g7BzrApY3hUz-u6s6OWq5cManmX495LjBQHSogCciCAvsfRyX1R6GeFWQjnNcLeF9Y92vtgbzHbXelWDGU8Ri0fYWMuwGJwgmpJCEersEtaS-6MDtH5cwh6xXkXum8NaQae9sC0PVpWRVSVlPGaSxDEsyFa6Rwdr8dE.mQRtcDG3k5TUYBYwWpMMpA, twi_message_type=twilio.voice.call, twi_call_sid=CA74806e6f5e85fd5fdf86c36bad57ae00, twi_message_id=FCM0fd059ee8160477a9a517b2c9af7fa74, twi_from=+}
92 | 05-08 05:39:15.744 11933-11933/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 1734497732
93 | 05-08 05:39:15.744 11933-11933/ D/TwilioVoice: showNotification() callInvite PENDING
94 | 05-08 05:39:15.772 11933-11933/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1734497732, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@1d5a678}]
95 | 05-08 05:39:15.772 11933-11933/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
96 | 05-08 05:39:15.818 11933-11933/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA74806e6f5e85fd5fdf86c36bad57ae00"} }
97 | 05-08 05:39:15.818 11933-11933/ D/TwilioVoice: failed Catalyst instance not active
98 | 05-08 05:39:15.819 11933-11933/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1734497732, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@1d5a678}]
99 | 05-08 05:39:15.819 11933-11933/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
100 | 05-08 05:39:15.822 11933-11933/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA74806e6f5e85fd5fdf86c36bad57ae00"} }
101 | 05-08 05:39:15.822 11933-11933/ D/TwilioVoice: failed Catalyst instance not active
102 | 05-08 05:39:15.822 11933-11933/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1734497732, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@1d5a678}]
103 | 05-08 05:39:15.822 11933-11933/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
104 | 05-08 05:39:15.824 11933-11933/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA74806e6f5e85fd5fdf86c36bad57ae00"} }
105 | 05-08 05:39:18.911 11933-14557/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
106 | 05-08 05:39:18.973 11933-14557/ I/PJSIP: 05:39:18.973 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
107 | 05-08 05:39:18.973 11933-14557/ I/PJSIP: 05:39:18.973 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
108 | 05-08 05:39:22.214 11933-14921/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA74806e6f5e85fd5fdf86c36bad57ae00, twi_message_id=FCM5565f79d78164345bac9b14b9589fe42, twi_from=+}
109 | 05-08 05:39:22.215 11933-11933/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 2145809630
110 | 05-08 05:39:22.215 11933-11933/ D/TwilioVoice: showNotification() callInvite = CANCELED
111 | 05-08 05:39:22.215 11933-11933/ D/TwilioVoice: removeIncomingCallNotification
112 | 05-08 05:39:22.216 11933-11933/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=2145809630, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@5864496}]
113 | 05-08 05:39:22.216 11933-11933/ D/TwilioVoice: ====> BEGIN this is the important block
114 | 05-08 05:39:22.216 11933-11933/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
115 | 05-08 05:39:22.216 11933-11933/ D/TwilioVoice: activeCallInvite was cancelled by +
116 | 05-08 05:39:22.216 11933-11933/ D/TwilioVoice: creating a missed call, activeCallInvite state: CANCELED
117 | 05-08 05:39:22.247 11933-11933/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
118 | 05-08 05:39:22.247 11933-11933/ D/TwilioVoice: removeIncomingCallNotification
119 | 05-08 05:39:22.256 11933-11933/ D/TwilioVoice: ====> END this is the important block
120 | 05-08 05:39:22.256 11933-11933/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=2145809630, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@5864496}]
121 | 05-08 05:39:22.256 11933-11933/ D/TwilioVoice: ====> BEGIN this is the important block
122 | 05-08 05:39:22.256 11933-11933/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
123 | 05-08 05:39:22.256 11933-11933/ D/TwilioVoice: activeCallInvite was cancelled by +
124 | 05-08 05:39:22.256 11933-11933/ D/TwilioVoice: creating a missed call, activeCallInvite state: CANCELED
125 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
126 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: removeIncomingCallNotification
127 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: ====> END this is the important block
128 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=2145809630, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@5864496}]
129 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: ====> BEGIN this is the important block
130 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: handleIncomingCallIntent state = CANCELED
131 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: activeCallInvite was cancelled by +
132 | 05-08 05:39:22.317 11933-11933/ D/TwilioVoice: creating a missed call, activeCallInvite state: CANCELED
133 | 05-08 05:39:22.336 11933-11933/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
134 | 05-08 05:39:22.336 11933-11933/ D/TwilioVoice: removeIncomingCallNotification
135 | 05-08 05:39:22.336 11933-11933/ D/TwilioVoice: ====> END this is the important block
136 | 05-08 05:39:29.274 11933-14557/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"DISCONNECTED","call_sid":"CA74806e6f5e85fd5fdf86c36bad57ae00"} }
137 | 05-08 05:39:29.398 11933-14557/ D/TwilioVoice: call disconnected
138 | 05-08 05:39:29.399 11933-14557/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA74806e6f5e85fd5fdf86c36bad57ae00"} }
139 |
140 | call 4 start using private variable Boolean callAccepted to track the call acceptance, because the activeCallInvite' state never becomes ACCEPTED
141 |
142 | 05-08 05:51:12.024 17347-17427/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
143 | 05-08 05:51:12.024 17347-17427/ I/TwilioVoice: Registering with FCM
144 | 05-08 05:51:12.657 17347-17427/ D/TwilioVoice: Successfully registered FCM
145 | 05-08 05:51:12.657 17347-17427/ D/TwilioVoice: sendEvent deviceReady params null
146 | 05-08 05:51:34.392 17347-17920/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..5iAR_g_tjn5DmGSg.ZsIdF2Ahp1CVQTxLNq_D6PArrWZV1lL0xLAxPt9dveTf_mwWEMB8eg-YvUNkCqlAActarZaYsix8qCP-ZLv5qMmm-wGuSQ4KCS9rGWMEWxoPug9zXCkgCfXRRfjUq0RQUfmo3intRShwEzBZMSr4er5Qhs_UDPbsYgb_7RRE5sG0KzxhpRbjxiXzl3F9P-xDxoxO6jw-L7P6gJnqvNyIElWApg1g2RgrDlVqxD-cRvQ9675D7enPJJqsVAgfFN7OqnISZqXc_vAZQcojWNFYCaxLFp2jhAQVDKrwWOwGxPGFay813weY5_PJSbK5K376IJ2mwL3xu90o9-SHBVpPGDBb_-psv8-bK_XHtmRtsAWJpx_zptatF_KQZdO19RcX12zTrHkQH95lwEioELuM7pdXcqcuzY2pE0BcG3ZSh-5edQ2sN9Lnbd1iBQfevoOt2rXpsZdMmzAlUBDtTAjaTczbmfLrEKd_UD_4U4iVw38L8RFQR_088RSAIUpej23cEGciV-UpwQDZCk_q3ChSALCZnNghssFFf4knN-YGOP35rsPPs3Jb483F2rFNo66TFo7_NKQ8H6RJiEh5Yj48XT4SI7d9ySbouANKKS_yBv5ZZq91bxe-t4ic15WC27q7CJYqvw-I6Thw-lTxBgzW16oHK0k.Vu6mYRuDJsX89ldsNI93cQ, twi_message_type=twilio.voice.call, twi_call_sid=CA902f8cc00a16a0864b08eefbc11618e4, twi_message_id=FCMcd290f30ef874682a4efcf2fa847a051, twi_from=+}
147 | 05-08 05:51:34.401 17347-17347/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 1300754382
148 | 05-08 05:51:34.401 17347-17347/ D/TwilioVoice: showNotification() callInvite PENDING
149 | 05-08 05:51:34.443 17347-17347/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1300754382, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@6cd114}]
150 | 05-08 05:51:34.443 17347-17347/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
151 | 05-08 05:51:34.550 17347-17347/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA902f8cc00a16a0864b08eefbc11618e4"} }
152 | 05-08 05:51:37.070 17347-17427/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
153 | 05-08 05:51:37.118 17347-17427/ I/PJSIP: 05:51:37.118 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
154 | 05-08 05:51:37.118 17347-17427/ I/PJSIP: 05:51:37.118 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
155 | 05-08 05:51:40.465 17347-18042/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA902f8cc00a16a0864b08eefbc11618e4, twi_message_id=FCM756f2fb753a24eabaf93cfafd156db71, twi_from=+}
156 | 05-08 05:51:40.466 17347-17347/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -602998500
157 | 05-08 05:51:40.466 17347-17347/ D/TwilioVoice: showNotification() callInvite = CANCELED
158 | 05-08 05:51:40.466 17347-17347/ D/TwilioVoice: removeIncomingCallNotification
159 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-602998500, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@1cc521d}]
160 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
161 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: activeCallInvite state = CANCELED
162 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: activeCallInvite was cancelled by +
163 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
164 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: removeIncomingCallNotification
165 | 05-08 05:51:40.467 17347-17347/ D/TwilioVoice: ====> END this is the important block
166 | 05-08 05:51:47.392 17347-17427/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA902f8cc00a16a0864b08eefbc11618e4"} }
167 | 05-08 05:51:56.634 17347-17427/ D/TwilioVoice: call disconnected
168 | 05-08 05:51:56.634 17347-17427/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA902f8cc00a16a0864b08eefbc11618e4"} }
169 |
170 | call 5
171 |
172 | 05-08 05:55:06.087 20553-20671/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
173 | 05-08 05:55:06.088 20553-20671/ I/TwilioVoice: Registering with FCM
174 | 05-08 05:55:06.664 20553-20671/ D/TwilioVoice: Successfully registered FCM
175 | 05-08 05:55:06.664 20553-20671/ D/TwilioVoice: sendEvent deviceReady params null
176 | 05-08 05:55:19.503 20553-20988/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..OkpYD_o_qRfcnidx.XwYhGjrA_JYmTB3-uiYqzxV-eCi6sr3n6CawElA31UOSPmqe1UKSILj0pw8quKLlZ2FK-4tveuvS6kUc0dqaeiG7iMcnOg8KXYlV6MP6Z8FbEoQzTr86ddyokVpSGQ-XgboAg8mhFcJ7bPoCl-RVORIQlmzll29nknvHmKDG80RdjvKhoJ_zCvK3mvmn60C_n2-W4uE9bH9WB_TcufoCHf53w473U1rqn5SZ5AbrjLdeXjVDf47-Q4HNh5fiNxoD0WkeQR5PyjbdvLzd2Kl6PeVC7385Qs6T4jEhNZl5h-eJ8XsBEuWO6zyAFNqmZ33QkhOkrH7IbWvL7AYE5ZaeSL09mK8JN1Otv0Im0YhegL1PD1tEb73M2qh8Et0IF10ussPW-pOWrCAJoDPQLHjp_klfEI3Z7W3QTM4KzRmyMobtGlRVYAHAvahQWX6LB3uCY2S0dRhAKxuYJ7T_gAkJQQXc-7eEChueYzzpu609zXtcd7SRV9I74zwAFqyx-4tw-uv2ReMMw-5fsFn7T86BawVj69eWAQSypAtdTd7uw9le8q9wei7_YRaQ6E3I73Iz2M_CjP4fSbwR9McPlgFsEhYa7h5za1dH_JQzqpw3KBj8WkocMNX4iJ0f11X-N_eTw3O8YCZscE_-vaPa3AUaNy0v.yhglxciaEcKr-R5_6-lytA, twi_message_type=twilio.voice.call, twi_call_sid=CA48b19f09ff6af24e33f663ef3a5d4fe0, twi_message_id=FCMc93c252d795b443bad34f8a4eac758d1, twi_from=+}
177 | 05-08 05:55:19.515 20553-20553/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -663482343
178 | 05-08 05:55:19.515 20553-20553/ D/TwilioVoice: showNotification() callInvite PENDING
179 | 05-08 05:55:19.537 20553-20553/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-663482343, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@3be2c4e}]
180 | 05-08 05:55:19.537 20553-20553/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
181 | 05-08 05:55:19.567 20553-20553/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA48b19f09ff6af24e33f663ef3a5d4fe0"} }
182 | 05-08 05:55:25.808 20553-20671/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
183 | 05-08 05:55:25.837 20553-20671/ I/PJSIP: 05:55:25.837 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
184 | 05-08 05:55:25.837 20553-20671/ I/PJSIP: 05:55:25.837 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
185 | 05-08 05:55:31.176 20553-21238/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA48b19f09ff6af24e33f663ef3a5d4fe0, twi_message_id=FCM471a2827df614bfa92c85c13e2e108d7, twi_from=+}
186 | 05-08 05:55:31.177 20553-20553/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 616207823
187 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: showNotification() callInvite = CANCELED
188 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: removeIncomingCallNotification
189 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=616207823, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@267b248}]
190 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
191 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: activeCallInvite state = CANCELED
192 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: activeCallInvite was cancelled by +
193 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
194 | 05-08 05:55:31.178 20553-20553/ D/TwilioVoice: removeIncomingCallNotification
195 | 05-08 05:55:31.179 20553-20553/ D/TwilioVoice: ====> END this is the important block
196 | 05-08 05:55:38.762 20553-20671/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA48b19f09ff6af24e33f663ef3a5d4fe0"} }
197 | 05-08 05:55:45.650 20553-20671/ D/TwilioVoice: call disconnected
198 | 05-08 05:55:45.651 20553-20671/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA48b19f09ff6af24e33f663ef3a5d4fe0"} }
199 |
200 | call 6
201 |
202 | 05-08 09:39:52.329 22675-22784/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
203 | 05-08 09:39:52.330 22675-22784/ I/TwilioVoice: Registering with FCM
204 | 05-08 09:39:52.992 22675-22784/ D/TwilioVoice: Successfully registered FCM
205 | 05-08 09:39:52.992 22675-22784/ D/TwilioVoice: sendEvent deviceReady params null
206 | 05-08 09:41:11.628 22675-24116/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..ecTVRwih3teKKpGu._YP7nA6I_l3J2bhbyOTbosyP3Kr8J2X2YKmdCAFyIkxS594XPH7wRYmRiJGswxdB7m0N69WWjvh0AvzSRfLjaKDwompLDUuhAkWw3WXbcLx9FJJsrXy-naaTTHmV8TAdwhfAgUkHQWar84EjF_vfPdUJ3BAkdw4991RTZEozR35mwMq_Mmcl9k7mdr9FxDbHmoUYPP9LRfqoesSpQ5EO_Ho1TW0U5Khzy9hf1f7rYa2peYLY6DqaVx9VcoJ86AdkH5AhIS2U4R2_Ho3ewNOfrKeFML2Fvy2VKXq1mtEPZAiBnObuFFJde2HXLx5uhNzX3lXOx_6gKgPQaoxTlh34-ULL6rmXrrmSIINJ0nC4X5MFVpqRebnPnmpIN_3itma9iEw_wZtcVcs6NsA32JpNd7qWjZyUfgR_4FWnAWRGJTDWAD8D4m3v8QKroPvEm8kL7kcUITJrUSk6aOB3HxEVV7oK0IvsE1edx-_2aDfxO-jAcvUQbmPvuSuHs2loZfwdOaBEeELnzvRG5JMkGfy9Tdcz36yoCvR2lj3htbjPys5NkuMtRAT6MdavLPeYJAApbzmQKOxXcSG4udEtZgDqW-mhKoZ-MK724EiGk6k4Z9GuM_BNjaJ78U-Y9sN03cYKQJjFcGVvyd72gt-jItLjhC0o.hz3pHy0IzRh0O49gvQ-n2A, twi_message_type=twilio.voice.call, twi_call_sid=CA44e4fc10a53c7ea74f3d471cda8da30a, twi_message_id=FCMf17e35ed4243412e9acf08e37da74799, twi_from=+}
207 | 05-08 09:41:11.636 22675-22675/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -165767137
208 | 05-08 09:41:11.637 22675-22675/ D/TwilioVoice: showNotification() callInvite PENDING
209 | 05-08 09:41:11.654 22675-22675/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-165767137, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@f9501fe}]
210 | 05-08 09:41:11.654 22675-22675/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
211 | 05-08 09:41:11.692 22675-22675/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA44e4fc10a53c7ea74f3d471cda8da30a"} }
212 | 05-08 09:41:17.279 22675-22784/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
213 | 05-08 09:41:17.298 22675-22784/ I/PJSIP: 09:41:17.298 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
214 | 05-08 09:41:17.298 22675-22784/ I/PJSIP: 09:41:17.298 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
215 | 05-08 09:41:22.374 22675-22784/ D/TwilioVoice: CALL CONNECTED callListener().onConnected call state = CONNECTED
216 | 05-08 09:41:22.465 22675-24349/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA44e4fc10a53c7ea74f3d471cda8da30a, twi_message_id=FCMdc3e5d0a60b74a1d9171bb3e6ff78c2f, twi_from=+}
217 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -1484454859
218 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: showNotification() callInvite = CANCELED
219 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: removeIncomingCallNotification
220 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-1484454859, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@e435e24}]
221 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
222 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: activeCallInvite state = CANCELED
223 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: activeCallInvite was cancelled by +
224 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
225 | 05-08 09:41:22.469 22675-22675/ D/TwilioVoice: removeIncomingCallNotification
226 | 05-08 09:41:22.470 22675-22675/ D/TwilioVoice: ====> END
227 | 05-08 09:41:28.668 22675-22784/ D/TwilioVoice: CALL CONNECTED after setAudio(true)
228 | 05-08 09:41:28.674 22675-22784/ D/TwilioVoice: CALL CONNECTED after createHangupLocalNotification
229 | 05-08 09:41:28.674 22675-22784/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA44e4fc10a53c7ea74f3d471cda8da30a"} }
230 | 05-08 09:41:33.232 22675-22784/ D/TwilioVoice: call disconnected
231 | 05-08 09:41:33.233 22675-22784/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA44e4fc10a53c7ea74f3d471cda8da30a"} }
232 |
233 |
234 | call 7 real device One Plus 3
235 |
236 | 05-08 14:46:50.285 30027-30100/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
237 | 05-08 14:46:50.285 30027-30100/ I/TwilioVoice: Registering with FCM
238 | 05-08 14:46:50.806 30027-30100/ D/TwilioVoice: Successfully registered FCM
239 | 05-08 14:46:50.806 30027-30100/ D/TwilioVoice: sendEvent deviceReady params null
240 | 05-08 14:46:59.372 30027-30027/ D/TwilioVoice: onNewIntent Intent { act=com.hoxfon.react.TwilioVoice.INCOMING_CALL flg=0x10680080 cmp=com.hoxfon.HoxFon.DEV.debug/com.hoxfon.MainActivity (has extras) }
241 | 05-08 14:46:59.372 30027-30027/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
242 | 05-08 14:46:59.476 30027-30027/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA4a32e595ebff0f3adde73014349f0479"} }
243 | 05-08 14:47:01.397 30027-30100/ D/TwilioVoice: Active call invite found: com.twilio.voice.CallInvite@af98319
244 | 05-08 14:47:02.672 30027-30100/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
245 | 05-08 14:47:02.673 30027-30100/ I/TwilioVoice: Registering with FCM
246 | 05-08 14:47:03.177 30027-30100/ D/TwilioVoice: Successfully registered FCM
247 | 05-08 14:47:03.177 30027-30100/ D/TwilioVoice: sendEvent deviceReady params null
248 | 05-08 14:47:03.698 30027-30100/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
249 | 05-08 14:47:03.753 30027-30100/ I/PJSIP: 14:47:03.753 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
250 | 05-08 14:47:03.753 30027-30100/ I/PJSIP: 14:47:03.753 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
251 | 05-08 14:47:05.122 30027-30100/ D/TwilioVoice: CALL CONNECTED callListener().onConnected call state = CONNECTED
252 | 05-08 14:47:05.126 30027-30100/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.setAudioFocus(TwilioVoiceModule.java:654)
253 | 05-08 14:47:05.126 30027-30100/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.access$200(TwilioVoiceModule.java:52)
254 | 05-08 14:47:05.127 30027-30100/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule$2.onConnected(TwilioVoiceModule.java:215)
255 | 05-08 14:47:05.127 30027-30100/ V/AudioManager: Elem: com.twilio.voice.Call$1.run(Call.java:184)
256 | 05-08 14:47:05.165 30027-31220/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA4a32e595ebff0f3adde73014349f0479, twi_message_id=FCM1dd28a4d402647179ae4c0a1ef403b80, twi_from=+}
257 | 05-08 14:47:05.174 30027-30027/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -2031898349
258 | 05-08 14:47:05.174 30027-30027/ D/TwilioVoice: showNotification() callInvite = CANCELED
259 | 05-08 14:47:05.174 30027-30027/ D/TwilioVoice: removeIncomingCallNotification
260 | 05-08 14:47:05.175 30027-30027/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-2031898349, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@836139a}]
261 | 05-08 14:47:05.175 30027-30027/ D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
262 | 05-08 14:47:05.176 30027-30027/ D/TwilioVoice: activeCallInvite state = CANCELED
263 | 05-08 14:47:05.176 30027-30027/ D/TwilioVoice: activeCallInvite was cancelled by +
264 | 05-08 14:47:05.176 30027-30027/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
265 | 05-08 14:47:05.176 30027-30027/ D/TwilioVoice: removeIncomingCallNotification
266 | 05-08 14:47:05.177 30027-30027/ D/TwilioVoice: ====> END
267 | 05-08 14:47:05.370 30027-30100/ D/TwilioVoice: CALL CONNECTED after setAudio(true)
268 | 05-08 14:47:05.385 30027-30100/ D/TwilioVoice: CALL CONNECTED after createHangupLocalNotification
269 | 05-08 14:47:05.385 30027-30100/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA4a32e595ebff0f3adde73014349f0479"} }
270 | 05-08 14:47:08.595 30027-30100/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
271 | 05-08 14:47:08.595 30027-30100/ I/TwilioVoice: Registering with FCM
272 | 05-08 14:47:09.104 30027-30100/ D/TwilioVoice: Successfully registered FCM
273 | 05-08 14:47:09.104 30027-30100/ D/TwilioVoice: sendEvent deviceReady params null
274 | 05-08 14:47:15.589 30027-30100/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.setAudioFocus(TwilioVoiceModule.java:656)
275 | 05-08 14:47:15.589 30027-30100/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.access$200(TwilioVoiceModule.java:52)
276 | 05-08 14:47:15.589 30027-30100/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule$2.onDisconnected(TwilioVoiceModule.java:241)
277 | 05-08 14:47:15.589 30027-30100/ V/AudioManager: Elem: com.twilio.voice.Call$2.run(Call.java:210)
278 | 05-08 14:47:15.591 30027-30100/ D/TwilioVoice: call disconnected
279 | 05-08 14:47:15.591 30027-30100/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+","call_state":"DISCONNECTED","call_sid":"CA4a32e595ebff0f3adde73014349f0479"} }
280 |
281 | call 8 real device One Plus 3
282 |
283 | 05-08 15:16:36.607 3136-3202/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
284 | 05-08 15:16:36.607 3136-3202/ I/TwilioVoice: Registering with FCM
285 | 05-08 15:16:37.196 3136-3202/ D/TwilioVoice: Successfully registered FCM
286 | 05-08 15:16:37.197 3136-3202/ D/TwilioVoice: sendEvent deviceReady params null
287 | 05-08 15:16:43.345 4022-4088/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
288 | 05-08 15:16:43.345 4022-4088/ I/TwilioVoice: Registering with FCM
289 | 05-08 15:16:43.888 4022-4088/ D/TwilioVoice: Successfully registered FCM
290 | 05-08 15:16:43.888 4022-4088/ D/TwilioVoice: sendEvent deviceReady params null
291 | 05-08 15:17:07.577 4022-4206/ D/TwilioVoice: Bundle data: {twi_account_sid=ACf2f72c54065e0a29b0109595ef6df75f, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..dFKbynTgQyjarEMN.gcP0YxIPiUJceSbDsvWXSBfdjbWR_1zC-b4Ap1ov39_1yAtAXHSq_lBjiWGVirQ9xP6jSNOIRvInyliURBgbomMcChWA0eeItS3r6gL4VaiW1gstYfmtrnrt11SjRow7HjyDLnfJ77D2SKNmmvpJ15PEvg4R2w9_UFv48TLKtJDQ0PF78QXtKeRsjaCU7NxFoyGWCwzF7RTB50i0EGZ1anjnb-3a346ci3r79LTLGXg1r9IWrH1DfXd9i7abhcX6SEL8ULFQEc-ANvUGpwWk_mS_PNWAiqsY3ZhRbDdtGyNuFq4rXKx9oCZQCk6ecRRMi-pP4yB0z99l5T4eG_7EYXG8oHqSNAiKx0PBuVyuYz5B5w8q4wRrXEeMw68mA4xfaOQyfI0-Siiy_Bu6oXZw-yXXNWVrHK2FbGalXM0V3k8s-OY8z1CM3xpRbwIDhQ5tGZrwTAoFKrZe_T_SPXG2SXfGdoMsBuaIqR_YH2sJMB2r5jinRdCDhbi0E5tirpXq5ynUcf-83dNcj-Vncr_yrT6JpNreeJY8zVbhF_ovTtPzUwLjeRXGD_spizhnawY0OSQmUYeu1HdKvgFw8n2pXIviVYLj9FdeYWps_T8rkZijnUSRvc6A6cqjR-G2F2_hHkMyqBD0zoREo0Rc8SWIukrQ.UT6PpQ5W3xi1HiBAGfKnkQ, twi_message_type=twilio.voice.call, twi_call_sid=CA527ee39eefb201d59646a07ea293d146, twi_message_id=FCMad72c972e9074b3c95fd1360b23b8d6b, twi_from=+441253530539}
292 | 05-08 15:17:07.622 4022-4022/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 1930071204
293 | 05-08 15:17:07.622 4022-4022/ D/TwilioVoice: showNotification() callInvite PENDING
294 | 05-08 15:17:07.637 4022-4022/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1930071204, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@66b3a44}]
295 | 05-08 15:17:07.637 4022-4022/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
296 | 05-08 15:17:07.698 4022-4022/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+441253530539","call_sid":"CA527ee39eefb201d59646a07ea293d146"} }
297 | 05-08 15:17:12.012 4022-4088/ D/TwilioVoice: accept() activeCallInvite.getState() PENDING
298 | 05-08 15:17:12.036 4022-4088/ I/PJSIP: 15:17:12.036 pjsua_acc.c Adding account: id=sip:twilio@chunderm.gll.twilio.com
299 | 05-08 15:17:12.036 4022-4088/ I/PJSIP: 15:17:12.036 pjsua_acc.c .Account sip:twilio@chunderm.gll.twilio.com added with id 0
300 | 05-08 15:17:13.404 4022-4088/ D/TwilioVoice: CALL CONNECTED callListener().onConnected call state = CONNECTED
301 | 05-08 15:17:13.412 4022-4088/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.setAudioFocus(TwilioVoiceModule.java:654)
302 | 05-08 15:17:13.413 4022-4088/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.access$200(TwilioVoiceModule.java:52)
303 | 05-08 15:17:13.413 4022-4088/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule$2.onConnected(TwilioVoiceModule.java:215)
304 | 05-08 15:17:13.413 4022-4088/ V/AudioManager: Elem: com.twilio.voice.Call$1.run(Call.java:184)
305 | 05-08 15:17:13.520 4022-4234/ D/TwilioVoice: Bundle data: {twi_account_sid=ACf2f72c54065e0a29b0109595ef6df75f, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA527ee39eefb201d59646a07ea293d146, twi_message_id=FCM1298db5ba0c0420bb8e88b7a8660faf6, twi_from=+441253530539}
306 | 05-08 15:17:13.538 4022-4022/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -22160039
307 | 05-08 15:17:13.538 4022-4022/ D/TwilioVoice: showNotification() callInvite = CANCELED
308 | 05-08 15:17:13.538 4022-4022/ D/TwilioVoice: removeIncomingCallNotification
309 | 05-08 15:17:13.545 4022-4088/ D/TwilioVoice: CALL CONNECTED after setAudio(true)
310 | 05-08 15:17:13.552 4022-4022/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-22160039, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@57497b6}]
311 | 05-08 15:17:13.552 4022-4022/ D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
312 | 05-08 15:17:13.552 4022-4022/ D/TwilioVoice: activeCallInvite state = CANCELED
313 | 05-08 15:17:13.552 4022-4022/ D/TwilioVoice: activeCallInvite was answered. Call com.twilio.voice.Call@ac797a6
314 | 05-08 15:17:13.552 4022-4022/ D/TwilioVoice: ====> END
315 | 05-08 15:17:13.560 4022-4088/ D/TwilioVoice: CALL CONNECTED after createHangupLocalNotification
316 | 05-08 15:17:13.560 4022-4088/ D/TwilioVoice: sendEvent connectionDidConnect params { NativeMap: {"call_state":"CONNECTED","call_sid":"CA527ee39eefb201d59646a07ea293d146"} }
317 | 05-08 15:17:21.572 4022-4088/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.setAudioFocus(TwilioVoiceModule.java:656)
318 | 05-08 15:17:21.572 4022-4088/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule.access$200(TwilioVoiceModule.java:52)
319 | 05-08 15:17:21.572 4022-4088/ V/AudioManager: Elem: com.hoxfon.react.TwilioVoice.TwilioVoiceModule$2.onDisconnected(TwilioVoiceModule.java:241)
320 | 05-08 15:17:21.572 4022-4088/ V/AudioManager: Elem: com.twilio.voice.Call$2.run(Call.java:210)
321 | 05-08 15:17:21.712 4022-4088/ D/TwilioVoice: call disconnected
322 | 05-08 15:17:21.712 4022-4088/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_to":"client:","call_from":"+441253530539","call_state":"DISCONNECTED","call_sid":"CA527ee39eefb201d59646a07ea293d146"} }
323 |
--------------------------------------------------------------------------------
/logs/android/beta5/in-rejected-foreground:
--------------------------------------------------------------------------------
1 | Emulator Samsung Galaxy 8
2 | lib: com.twilio:voice-android:2.0.0-beta5
3 | firebase: com.google.firebase:firebase-messaging:10.2.4
4 | Android 7.0.0
5 |
6 |
7 | Incoming call Rejected app in foreground
8 |
9 | call 1 rejected manually
10 |
11 | 05-08 05:59:23.285 24901-25059/ D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
12 | 05-08 05:59:23.286 24901-25059/ I/TwilioVoice: Registering with FCM
13 | 05-08 05:59:24.059 24901-25059/ D/TwilioVoice: Successfully registered FCM
14 | 05-08 05:59:24.059 24901-25059/ D/TwilioVoice: sendEvent deviceReady params null
15 | 05-08 05:59:34.558 24901-25373/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..VeIJqiWliD2LrMVv.FzsfMLgg6HF47fviLh0lon6oO9qwvwxzh8Tv57OD_yEiX0zPFPXAK1RdSuyEz2XkevQwBrwgg8OGPt3MCtK8vd_JD4T7nrrqeRQY0EfxfYeXxGvB6nkXctnYZTmwYVyyV-wMGa3egCG4e-r3LtxGSLOD8jor7v66WZDnUYr2Si0SpPSRCLt1gAoJUJ8GleswjZFoXfv8exoZIhVl3jNx7CnUGtXvgqdme1SMOnp9fU_dKMPPsoLYP_S4GiSdE21Eu_t2weTnSYEn66lt4kbvz7tP_2DfG8FtIn6wSFZMd5qHisjLWswnr39_qX57YjXcB-oZKY8awlS5bsVrGeeEKBiLcxUaReXER68BQA7gsvnup6hL4P1AubQJ47Zo8KQh4g4WBhXRhrzp2n3rabwcWBegP5mf5o2mfnJkAqc-DDWXQMN2dpq5QK-6mKZq99qkl5RB-OvIdI_EDiPKFE7jGhAZAybHWU4U-Hb2MVc07q8diwL2bnUkaj2Yse1B-6bSKHlWBiyiz0hWeSFRMWwwXhD5p4-h4wqjWVNoXEFwRQS8mePSDSiXbs_tDQULJmvnvcG9Mo4EK0_JQ-sOAQxPw65j11Y-gDNj-iXM3BnCphWQdWuLTEzT0tzA4We5hVDrRwso0Lc-5W3B4JZBDt56A9uY.o-jW6Mg8l8OYMEepcdDU3Q, twi_message_type=twilio.voice.call, twi_call_sid=CAcf141eed003e7b1906973e5f7c94647c, twi_message_id=FCM06bb6af21eda4f6ebf13edc1e97d86db, twi_from=+}
16 | 05-08 05:59:34.576 24901-24901/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 1065010575
17 | 05-08 05:59:34.577 24901-24901/ D/TwilioVoice: showNotification() callInvite PENDING
18 | 05-08 05:59:34.658 24901-24901/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1065010575, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@c44bb96}]
19 | 05-08 05:59:34.658 24901-24901/ D/TwilioVoice: handleIncomingCallIntent state = PENDING
20 | 05-08 05:59:34.898 24901-24901/ D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CAcf141eed003e7b1906973e5f7c94647c"} }
21 | 05-08 05:59:41.930 24901-25503/ D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CAcf141eed003e7b1906973e5f7c94647c, twi_message_id=FCM319bc21b0498478ba19f443bac4ae874, twi_from=+}
22 | 05-08 05:59:41.933 24901-24901/ D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 1826783273
23 | 05-08 05:59:41.934 24901-24901/ D/TwilioVoice: showNotification() callInvite = CANCELED
24 | 05-08 05:59:41.948 24901-24901/ D/TwilioVoice: removeIncomingCallNotification
25 | 05-08 05:59:41.949 24901-24901/ D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=1826783273, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@c44bb96}]
26 | 05-08 05:59:41.949 24901-24901/ D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
27 | 05-08 05:59:47.949 24901-24901/ D/TwilioVoice: activeCallInvite state = CANCELED
28 | 05-08 05:59:47.949 24901-24901/ D/TwilioVoice: activeCallInvite was cancelled by +
29 | 05-08 05:59:47.949 24901-24901/ D/TwilioVoice: creating a missed call, activeCallInvite state: CANCELED
30 | 05-08 05:59:47.972 24901-24901/ D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_state":"CANCELED","call_to":"client:","call_from":"+","call_sid":"CAcf141eed003e7b1906973e5f7c94647c"} }
31 | 05-08 05:59:47.972 24901-24901/ D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
32 | 05-08 05:59:47.972 24901-24901/ D/TwilioVoice: removeIncomingCallNotification
33 | 05-08 05:59:47.972 24901-24901/ D/TwilioVoice: ====> END this is the important block
34 |
35 | call 2 I let the timeout pass to trigger a missed call
36 |
37 | 05-08 06:00:29.596 25944-26070/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: initWithAccessToken ACTION_FCM_TOKEN
38 | 05-08 06:00:29.596 25944-26070/com.hoxfon.HoxFon.DEV.debug I/TwilioVoice: Registering with FCM
39 | 05-08 06:00:30.167 25944-26070/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: Successfully registered FCM
40 | 05-08 06:00:30.167 25944-26070/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: sendEvent deviceReady params null
41 | 05-08 06:00:35.311 25944-26274/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_bridge_token=eyJraWQiOiJKd2VTM0JpZi0xIiwiY3R5IjoidHdpbGlvLWZwYTt2PTEiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiZGlyIn0..uH3PZuMKvD4bIt8J.tV_vFbkoFVv3iKHcebk6mzAf3UlrGGI_BBOS8si3bx5HtEYXbzPf4_wXXRYB48n7q_RNH3LkZWNlAqe6rf1E2VfPjxmzqXHezAheohRTuNDczlEz8sncYYcnRIgdD6kyVp0dfaPHKZKGNJl0tyR9igBelmKH6V1fhxWz3PzesXVDMt-AoiUPFaStk0xgL_0BHTEfn2_cKVlCOsEf5fU2N91TwaVAmufr3fs0GDeM3n0nRQSKwPgvcU0zaGn31m7WYt8DXjWgX7OSSmb6a8p9hM8MkKK_3dhQ8qirqDqiICMvi2Iz69_TM4I8bOGftD4sbQLZ92EhlckRNHbuDhzOEiT3wvbZTzFQBXgp-7TohB9zZDKTbVLkOSzTydMn57F-yaI9enUxmdSiHszfcE7vMNaPYrHnvc5446u57csytnByoxgENIMLlyMtZIj-4w9Mx0LiLiakj2i5MxwN6Fdl7rgKk0Me7LdhN7LISBlZ-PCksu-EGHffEt_KsFQhwtGTGzbb7q8bv2XuaUVjXohQBt5cFe_nRpATOdB32e9vg6tEKY-DOghDGtUKOqiBsHuKU-zMejzKBEipDreUGazQx7K1yQU5IfOlNx9L6b6kPRdl3yZdyHgW5cOkrGij2WVbfQXWU967gqEtfkwYEvc7znsU.X0faDkNBdiN8nep1Ltp7Tw, twi_message_type=twilio.voice.call, twi_call_sid=CA9a2409ec0649a7ef8bcb24a656b68f6d, twi_message_id=FCM60102d266205490bb1929e388b4f9b4b, twi_from=+}
42 | 05-08 06:00:35.319 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: -289643806
43 | 05-08 06:00:35.320 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: showNotification() callInvite PENDING
44 | 05-08 06:00:35.380 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=-289643806, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@8df2e7c}]
45 | 05-08 06:00:35.381 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: handleIncomingCallIntent state = PENDING
46 | 05-08 06:00:35.445 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: sendEvent deviceDidReceiveIncoming params { NativeMap: {"call_state":"PENDING","call_to":"client:","call_from":"+","call_sid":"CA9a2409ec0649a7ef8bcb24a656b68f6d"} }
47 | 05-08 06:01:11.552 25944-26871/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: Bundle data: {twi_account_sid=, twi_to=client:, twi_message_type=twilio.voice.cancel, twi_call_sid=CA9a2409ec0649a7ef8bcb24a656b68f6d, twi_message_id=FCM942084b469c945e4bc026a835336cd17, twi_from=+}
48 | 05-08 06:01:11.553 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: sendIncomingCallMessageToActivity() notificationId: 692097470
49 | 05-08 06:01:11.553 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: showNotification() callInvite = CANCELED
50 | 05-08 06:01:11.568 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: removeIncomingCallNotification
51 | 05-08 06:01:11.568 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: VoiceBroadcastReceiver.onReceive ACTION_INCOMING_CALL. Intent Bundle[{INCOMING_CALL_NOTIFICATION_ID=692097470, INCOMING_CALL_INVITE=com.twilio.voice.CallInvite@8df2e7c}]
52 | 05-08 06:01:11.568 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: ====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING
53 | 05-08 06:01:11.568 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: activeCallInvite state = CANCELED
54 | 05-08 06:01:11.568 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: activeCallInvite was cancelled by +
55 | 05-08 06:01:11.568 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: creating a missed call, activeCallInvite state: CANCELED
56 | 05-08 06:01:11.588 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: sendEvent connectionDidDisconnect params { NativeMap: {"call_state":"CANCELED","call_to":"client:","call_from":"+","call_sid":"CA9a2409ec0649a7ef8bcb24a656b68f6d"} }
57 | 05-08 06:01:11.588 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: clearIncomingNotification() callInvite state: CANCELED
58 | 05-08 06:01:11.588 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: removeIncomingCallNotification
59 | 05-08 06:01:11.588 25944-25944/com.hoxfon.HoxFon.DEV.debug D/TwilioVoice: ====> END this is the important block
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-twilio-programmable-voice",
3 | "version": "4.4.0",
4 | "description": "React Native wrapper for Twilio Programmable Voice SDK",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "nativePackage": true,
10 | "homepage": "https://github.com/hoxfon/react-native-twilio-programmable-voice",
11 | "keywords": [
12 | "twilio",
13 | "react-native",
14 | "voice",
15 | "programmable",
16 | "android",
17 | "ios",
18 | "react-component",
19 | "push",
20 | "apns",
21 | "gcm",
22 | "fcm"
23 | ],
24 | "bugs": {
25 | "url": "https://github.com/hoxfon/react-native-twilio-programmable-voice/issues"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "git+ssh://git@github.com:hoxfon/react-native-twilio-programmable-voice.git"
30 | },
31 | "contributors": [
32 | {
33 | "name": "Fabrizio Moscon"
34 | }
35 | ],
36 | "dependencies": {},
37 | "devDependencies": {},
38 | "peerDependencies": {
39 | "react-native": ">=0.60.0"
40 | },
41 | "license": "MIT"
42 | }
43 |
--------------------------------------------------------------------------------
/react-native.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | project: {
3 | ios: {},
4 | android: {
5 | packageInstance: "new TwilioVoicePackage()"
6 | }
7 | }
8 | };
9 |
--------------------------------------------------------------------------------