├── .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 | ![help wanted](images/vjeux_tweet.png "help wanted") 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 | --------------------------------------------------------------------------------