diff --git a/CHANGELOG.md b/CHANGELOG.md
index db2b8b76..0c925fa9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
[Release Section](https://github.com/hoxfon/react-native-twilio-programmable-voice/releases)
+## 4.0.0
+- Android
+ - update Firebase Messaging to 17.3.4 which simplifies how to obtain the FCM token
+- iOS
+ - convert params for connectionDidConnect to => call_to, from => call_from
+ - convert params for connectionDidDisconnect to => call_to, from => call_from, error => err
+
## 3.21.1
- Android: fix crash when asking for microphone permission before an activity is displayed
diff --git a/README.md b/README.md
index b0e65d07..e7454c2f 100644
--- a/README.md
+++ b/README.md
@@ -3,41 +3,15 @@ This is a React Native wrapper for Twilio Programmable Voice SDK that lets you m
# Twilio Programmable Voice SDK
-- Android 2.0.7 (bundled within this library)
-- iOS 2.0.4 (specified by the app's own podfile)
+- Android 2.0.9 (bundled within this library)
+- iOS 2.0.7 (specified by the app's own podfile)
-## Breaking changes in v3.0.0
-
-- initWitToken returns an object with a property `initialized` instead of `initilized`
-- iOS event `connectionDidConnect` returns the same properties as Android
-move property `to` => `call_to`
-move property `from` => `call_from`
-
-## Migrating Android from v1 to v2 (incoming call use FCM)
-You will need to make changes both on your Twilio account using Twilio Web Console and on your react native app.
-Twilio Programmable Voice Android SDK uses `FCM` since version 2.0.0.beta5.
-Before you start, I strongly suggest that you read the list of Twilio changes from Android SDK v2.0.0 beta4 to beta5:
-[Twilio example App: Migrating from GCM to FCM](https://github.com/twilio/voice-quickstart-android/blob/d7d4f0658e145eb94ab8f5e34f6fd17314e7ab17/README.md#migrating-from-gcm-to-fcm)
-
-These are all the changes required:
-
-- remove all the GCM related code from your `AndroidManifest.xml` and add the following code to receive `FCM` notifications
-(I wasn't successful in keeping react-native-fcm working at the same time. If you know how please open an issue to share).
+## Breaking changes in v4.0.0
+- Android: remove the following block from your application's `AndroidManifest.xml`
```xml
- .....
-
-
-
-
-
-
-
-
-
-
```
-- log into your Firebase console. Navigate to: Project settings > CLOUD MESSAGING. Copy your `Server key`
-- in Twilio console add a new Push Credential, type `FCM`, fcm secret Firebase FCM `Server key`
-- include in your project `google-services.json`; if you have not include it yet
-- rename getIncomingCall() to getActiveCall()
+- iOS: params changes for `connectionDidConnect` and `connectionDidDisconnect`
+
+ to => call_to
+ from => call_from
+ error => err
-If something doesn't work as expected or you want to make a request open an issue.
+## Breaking changes in v3.0.0
+
+- initWitToken returns an object with a property `initialized` instead of `initilized`
+- iOS event `connectionDidConnect` returns the same properties as Android
+move property `to` => `call_to`
+move property `from` => `call_from`
## Help wanted!
@@ -220,8 +199,8 @@ public class MainApplication extends Application implements ReactApplication {
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
- new TwilioVoicePackage() // <---- Add the Package : by default it will ask microphone permissions
- // new TwilioVoicePackage(false) // <---- pass false to handle microphone permissions in your application
+ new TwilioVoicePackage() // <---- Add the package
+ // new TwilioVoicePackage(false) // <---- pass false if you don't want to ask for microphone permissions
);
}
};
@@ -277,7 +256,7 @@ TwilioVoice.addEventListener('connectionDidConnect', function(data) {
// Android
// {
// call_sid: string, // Twilio call sid
- // call_state: 'PENDING' | 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' 'DISCONNECTED' | 'CANCELLED',
+ // call_state: 'PENDING' | 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' | 'RECONNECTING' | 'DISCONNECTED' | 'CANCELLED',
// call_from: string, // "+441234567890"
// call_to: string, // "client:bob"
// }
@@ -297,7 +276,7 @@ TwilioVoice.addEventListener('connectionDidDisconnect', function(data: mixed) {
// | Android
// {
// call_sid: string, // Twilio call sid
- // call_state: 'PENDING' | 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' 'DISCONNECTED' | 'CANCELLED',
+ // call_state: 'PENDING' | 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' | 'RECONNECTING' | 'DISCONNECTED' | 'CANCELLED',
// call_from: string, // "+441234567890"
// call_to: string, // "client:bob"
// err?: string,
@@ -321,7 +300,7 @@ TwilioVoice.addEventListener('callRejected', function(value: 'callRejected') {})
TwilioVoice.addEventListener('deviceDidReceiveIncoming', function(data) {
// {
// call_sid: string, // Twilio call sid
- // call_state: 'PENDING' | 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' 'DISCONNECTED' | 'CANCELLED',
+ // call_state: 'PENDING' | 'CONNECTED' | 'ACCEPTED' | 'CONNECTING' | 'RECONNECTING' | 'DISCONNECTED' | 'CANCELLED',
// call_from: string, // "+441234567890"
// call_to: string, // "client:bob"
// }
diff --git a/android/build.gradle b/android/build.gradle
index 878fc179..13d135fb 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -2,11 +2,16 @@
buildscript {
repositories {
+ maven {
+ url 'https://maven.google.com/'
+ name 'Google'
+ }
jcenter()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.1'
- classpath 'com.google.gms:google-services:3.1.2'
+ classpath 'com.android.tools.build:gradle:3.3.0'
+ classpath 'com.google.gms:google-services:4.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -15,21 +20,27 @@ buildscript {
allprojects {
repositories {
- jcenter()
maven {
- url "https://maven.google.com"
+ url 'https://maven.google.com/'
+ name 'Google'
}
+ jcenter()
}
}
apply plugin: 'com.android.library'
+def DEFAULT_COMPILE_SDK_VERSION = 28
+def DEFAULT_BUILD_TOOLS_VERSION = "28.0.3"
+def DEFAULT_TARGET_SDK_VERSION = 28
+def DEFAULT_SUPPORT_LIB_VERSION = "28.0.3"
+
android {
- compileSdkVersion 27
- buildToolsVersion "27.0.3"
+ compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
+ buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion 16
- targetSdkVersion 27
+ targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary = true
@@ -43,9 +54,11 @@ android {
}
dependencies {
+ def supportLibVersion = rootProject.hasProperty('supportLibVersion') ? rootProject.supportLibVersion : DEFAULT_SUPPORT_LIB_VERSION
+
compile fileTree(include: ['*.jar'], dir: 'libs')
- compile 'com.twilio:voice-android:2.0.7'
- compile 'com.android.support:appcompat-v7:27.0.2'
+ compile 'com.twilio:voice-android:4+'
+ compile 'com.android.support:appcompat-v7:$supportLibVersion'
compile 'com.facebook.react:react-native:+'
compile 'com.google.firebase:firebase-messaging:17.+'
testCompile 'junit:junit:4.12'
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 04e285f3..708a0dce 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-4.10.2-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
deleted file mode 100644
index e69de29b..00000000
diff --git a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/CallNotificationManager.java b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/CallNotificationManager.java
index bace1249..11050c1d 100644
--- a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/CallNotificationManager.java
+++ b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/CallNotificationManager.java
@@ -21,6 +21,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.twilio.voice.CallInvite;
+import com.twilio.voice.CancelledCallInvite;
import java.util.List;
@@ -307,12 +308,12 @@ public void createHangupLocalNotification(ReactApplicationContext context, Strin
}
public void removeIncomingCallNotification(ReactApplicationContext context,
- CallInvite callInvite,
+ CancelledCallInvite callInvite,
int notificationId) {
Log.d(TAG, "removeIncomingCallNotification");
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
- if (callInvite != null && callInvite.getState() == CallInvite.State.PENDING) {
+ if (callInvite != null) {
/*
* If the incoming call message was cancelled then remove the notification by matching
* it with the call sid from the list of notifications in the notification drawer.
diff --git a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/TwilioVoiceModule.java b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/TwilioVoiceModule.java
index 774ad936..fae97b8a 100644
--- a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/TwilioVoiceModule.java
+++ b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/TwilioVoiceModule.java
@@ -15,6 +15,7 @@
import android.media.AudioManager;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
@@ -38,11 +39,14 @@
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
-import com.google.firebase.FirebaseApp;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
import com.google.firebase.iid.FirebaseInstanceId;
+import com.google.firebase.iid.InstanceIdResult;
import com.twilio.voice.Call;
import com.twilio.voice.CallException;
import com.twilio.voice.CallInvite;
+import com.twilio.voice.ConnectOptions;
import com.twilio.voice.LogLevel;
import com.twilio.voice.RegistrationException;
import com.twilio.voice.RegistrationListener;
@@ -75,8 +79,10 @@ public class TwilioVoiceModule extends ReactContextBaseJavaModule implements Act
public static final String INCOMING_CALL_INVITE = "INCOMING_CALL_INVITE";
public static final String INCOMING_CALL_NOTIFICATION_ID = "INCOMING_CALL_NOTIFICATION_ID";
public static final String NOTIFICATION_TYPE = "NOTIFICATION_TYPE";
+ public static final String CANCELLED_CALL_INVITE = "CANCELLED_CALL_INVITE";
public static final String ACTION_INCOMING_CALL = "com.hoxfon.react.TwilioVoice.INCOMING_CALL";
+ public static final String ACTION_CANCEL_CALL = "com.hoxfon.react.TwilioVoice.CANCEL_CALL";
public static final String ACTION_FCM_TOKEN = "com.hoxfon.react.TwilioVoice.ACTION_FCM_TOKEN";
public static final String ACTION_MISSED_CALL = "com.hoxfon.react.TwilioVoice.MISSED_CALL";
public static final String ACTION_ANSWER_CALL = "com.hoxfon.react.TwilioVoice.ANSWER_CALL";
@@ -214,6 +220,24 @@ public void onError(RegistrationException error, String accessToken, String fcmT
private Call.Listener callListener() {
return new Call.Listener() {
+ /*
+ * This callback is emitted once before the Call.Listener.onConnected() callback when
+ * the callee is being alerted of a Call. The behavior of this callback is determined by
+ * the answerOnBridge flag provided in the Dial verb of your TwiML application
+ * associated with this client. If the answerOnBridge flag is false, which is the
+ * default, the Call.Listener.onConnected() callback will be emitted immediately after
+ * Call.Listener.onRinging(). If the answerOnBridge flag is true, this will cause the
+ * call to emit the onConnected callback only after the call is answered.
+ * See answeronbridge for more details on how to use it with the Dial TwiML verb. If the
+ * twiML response contains a Say verb, then the call will emit the
+ * Call.Listener.onConnected callback immediately after Call.Listener.onRinging() is
+ * raised, irrespective of the value of answerOnBridge being set to true or false
+ */
+ @Override
+ public void onRinging(Call call) {
+ Log.d(TAG, "Ringing");
+ }
+
@Override
public void onConnected(Call call) {
if (BuildConfig.DEBUG) {
@@ -242,6 +266,16 @@ public void onConnected(Call call) {
eventManager.sendEvent(EVENT_CONNECTION_DID_CONNECT, params);
}
+ @Override
+ public void onReconnecting(Call call, CallException callException) {
+ Log.d(TAG, "onReconnecting");
+ }
+
+ @Override
+ public void onReconnected(Call call) {
+ Log.d(TAG, "onReconnected");
+ }
+
@Override
public void onDisconnected(Call call, CallException error) {
unsetAudioFocus();
@@ -386,7 +420,7 @@ private void handleIncomingCallIntent(Intent intent) {
if (intent.getAction().equals(ACTION_INCOMING_CALL)) {
activeCallInvite = intent.getParcelableExtra(INCOMING_CALL_INVITE);
- if (activeCallInvite != null && (activeCallInvite.getState() == CallInvite.State.PENDING)) {
+ if (activeCallInvite != null) { // && (activeCallInvite.getState() == CallInvite.State.PENDING)
callAccepted = false;
if (BuildConfig.DEBUG) {
Log.d(TAG, "handleIncomingCallIntent state = PENDING");
@@ -409,57 +443,45 @@ private void handleIncomingCallIntent(Intent intent) {
params.putString("call_sid", activeCallInvite.getCallSid());
params.putString("call_from", activeCallInvite.getFrom());
params.putString("call_to", activeCallInvite.getTo());
- params.putString("call_state", activeCallInvite.getState().name());
+ // params.putString("call_state", activeCallInvite.getState().name());
eventManager.sendEvent(EVENT_DEVICE_DID_RECEIVE_INCOMING, params);
}
-
-
} else {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING");
- }
- // this block is executed when the callInvite is cancelled and:
- // - the call is answered (activeCall != null)
- // - the call is rejected
- SoundPoolManager.getInstance(getReactApplicationContext()).stopRinging();
+ }
+ } else if (intent.getAction().equals(ACTION_CANCEL_CALL)) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "====> BEGIN handleIncomingCallIntent when activeCallInvite != PENDING");
+ }
+ // this block is executed when the callInvite is cancelled and:
+ // - the call is answered (activeCall != null)
+ // - the call is rejected
- // the call is not active yet
- if (activeCall == null) {
+ SoundPoolManager.getInstance(getReactApplicationContext()).stopRinging();
- if (activeCallInvite != null) {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "activeCallInvite state = " + activeCallInvite.getState());
- }
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "activeCallInvite was cancelled by " + activeCallInvite.getFrom());
- }
- if (!callAccepted) {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "creating a missed call, activeCallInvite state: " + activeCallInvite.getState());
- }
- callNotificationManager.createMissedCallNotification(getReactApplicationContext(), activeCallInvite);
- int appImportance = callNotificationManager.getApplicationImportance(getReactApplicationContext());
- if (appImportance != RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
- WritableMap params = Arguments.createMap();
- params.putString("call_sid", activeCallInvite.getCallSid());
- params.putString("call_from", activeCallInvite.getFrom());
- params.putString("call_to", activeCallInvite.getTo());
- params.putString("call_state", activeCallInvite.getState().name());
- eventManager.sendEvent(EVENT_CONNECTION_DID_DISCONNECT, params);
- }
- }
- }
- clearIncomingNotification(activeCallInvite);
- } else {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "activeCallInvite was answered. Call " + activeCall);
- }
- }
+ if (BuildConfig.DEBUG) {
+ // Log.d(TAG, "activeCallInvite state = " + activeCallInvite.getState());
+ }
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "activeCallInvite was cancelled by " + activeCallInvite.getFrom());
+ }
+ if (!callAccepted) {
if (BuildConfig.DEBUG) {
- Log.d(TAG, "====> END");
+ Log.d(TAG, "creating a missed call");
+ }
+ callNotificationManager.createMissedCallNotification(getReactApplicationContext(), activeCallInvite);
+ int appImportance = callNotificationManager.getApplicationImportance(getReactApplicationContext());
+ if (appImportance != RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
+ WritableMap params = Arguments.createMap();
+ params.putString("call_sid", activeCallInvite.getCallSid());
+ params.putString("call_from", activeCallInvite.getFrom());
+ params.putString("call_to", activeCallInvite.getTo());
+ // params.putString("call_state", activeCallInvite.getState().name());
+ eventManager.sendEvent(EVENT_CONNECTION_DID_DISCONNECT, params);
}
}
+
+ clearIncomingNotification(activeCallInvite);
} else if (intent.getAction().equals(ACTION_FCM_TOKEN)) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "handleIncomingCallIntent ACTION_FCM_TOKEN");
@@ -494,15 +516,16 @@ public void initWithAccessToken(final String accessToken, Promise promise) {
if (accessToken.equals("")) {
promise.reject(new JSApplicationIllegalArgumentException("Invalid access token"));
return;
- }
-
+ }
+
if(!checkPermissionForMicrophone()) {
- promise.reject(new AssertionException("Can't init without microphone permission"));
- }
+ promise.reject(new AssertionException("Allow microphone permission"));
+ return;
+ }
TwilioVoiceModule.this.accessToken = accessToken;
if (BuildConfig.DEBUG) {
- Log.d(TAG, "initWithAccessToken ACTION_FCM_TOKEN");
+ Log.d(TAG, "initWithAccessToken");
}
registerForCallInvites();
WritableMap params = Arguments.createMap();
@@ -512,7 +535,7 @@ public void initWithAccessToken(final String accessToken, Promise promise) {
private void clearIncomingNotification(CallInvite callInvite) {
if (BuildConfig.DEBUG) {
- Log.d(TAG, "clearIncomingNotification() callInvite state: "+ callInvite.getState());
+ // Log.d(TAG, "clearIncomingNotification() callInvite state: "+ callInvite.getState());
}
if (callInvite != null && callInvite.getCallSid() != null) {
// remove incoming call notification
@@ -533,20 +556,28 @@ private void clearIncomingNotification(CallInvite callInvite) {
* If a valid google-services.json has not been provided or the FirebaseInstanceId has not been
* initialized the fcmToken will be null.
*
- * In the case where the FirebaseInstanceId has not yet been initialized the
- * VoiceFirebaseInstanceIDService.onTokenRefresh should result in a LocalBroadcast to this
- * activity which will attempt registerForCallInvites again.
- *
*/
private void registerForCallInvites() {
- FirebaseApp.initializeApp(getReactApplicationContext());
- final String fcmToken = FirebaseInstanceId.getInstance().getToken();
- if (fcmToken != null) {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "Registering with FCM");
- }
- Voice.register(getReactApplicationContext(), accessToken, Voice.RegistrationChannel.FCM, fcmToken, registrationListener);
- }
+ FirebaseInstanceId.getInstance().getInstanceId()
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (!task.isSuccessful()) {
+ Log.w(TAG, "getInstanceId failed", task.getException());
+ return;
+ }
+
+ // Get new Instance ID token
+ String fcmToken = task.getResult().getToken();
+ if (fcmToken != null) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Registering with FCM");
+ }
+ Voice.register(getReactApplicationContext(), accessToken, Voice.RegistrationChannel.FCM, fcmToken, registrationListener);
+ }
+ }
+ });
+
}
@ReactMethod
@@ -554,26 +585,26 @@ public void accept() {
callAccepted = true;
SoundPoolManager.getInstance(getReactApplicationContext()).stopRinging();
if (activeCallInvite != null){
- if (activeCallInvite.getState() == CallInvite.State.PENDING) {
+ // if (activeCallInvite.getState() == CallInvite.State.PENDING) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "accept() activeCallInvite.getState() PENDING");
}
activeCallInvite.accept(getReactApplicationContext(), callListener);
clearIncomingNotification(activeCallInvite);
- } else {
- // when the user answers a call from a notification before the react-native App
- // is completely initialised, and the first event has been skipped
- // re-send connectionDidConnect message to JS
- WritableMap params = Arguments.createMap();
- params.putString("call_sid", activeCallInvite.getCallSid());
- params.putString("call_from", activeCallInvite.getFrom());
- params.putString("call_to", activeCallInvite.getTo());
- params.putString("call_state", activeCallInvite.getState().name());
- callNotificationManager.createHangupLocalNotification(getReactApplicationContext(),
- activeCallInvite.getCallSid(),
- activeCallInvite.getFrom());
- eventManager.sendEvent(EVENT_CONNECTION_DID_CONNECT, params);
- }
+ // } else {
+ // // when the user answers a call from a notification before the react-native App
+ // // is completely initialised, and the first event has been skipped
+ // // re-send connectionDidConnect message to JS
+ // WritableMap params = Arguments.createMap();
+ // params.putString("call_sid", activeCallInvite.getCallSid());
+ // params.putString("call_from", activeCallInvite.getFrom());
+ // params.putString("call_to", activeCallInvite.getTo());
+ // // params.putString("call_state", activeCallInvite.getState().name());
+ // callNotificationManager.createHangupLocalNotification(getReactApplicationContext(),
+ // activeCallInvite.getCallSid(),
+ // activeCallInvite.getFrom());
+ // eventManager.sendEvent(EVENT_CONNECTION_DID_CONNECT, params);
+ // }
} else {
eventManager.sendEvent(EVENT_CONNECTION_DID_DISCONNECT, null);
}
@@ -588,7 +619,7 @@ public void reject() {
params.putString("call_sid", activeCallInvite.getCallSid());
params.putString("call_from", activeCallInvite.getFrom());
params.putString("call_to", activeCallInvite.getTo());
- params.putString("call_state", activeCallInvite.getState().name());
+ // params.putString("call_state", activeCallInvite.getState().name());
activeCallInvite.reject(getReactApplicationContext());
clearIncomingNotification(activeCallInvite);
}
@@ -604,7 +635,7 @@ public void ignore() {
params.putString("call_sid", activeCallInvite.getCallSid());
params.putString("call_from", activeCallInvite.getFrom());
params.putString("call_to", activeCallInvite.getTo());
- params.putString("call_state", activeCallInvite.getState().name());
+ // params.putString("call_state", activeCallInvite.getState().name());
clearIncomingNotification(activeCallInvite);
}
eventManager.sendEvent(EVENT_CONNECTION_DID_DISCONNECT, params);
@@ -661,7 +692,11 @@ public void connect(ReadableMap params) {
}
}
- activeCall = Voice.call(getReactApplicationContext(), accessToken, twiMLParams, callListener);
+ ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
+ .params(twiMLParams)
+ .build();
+
+ activeCall = Voice.connect(getReactApplicationContext(), connectOptions, callListener);
}
@ReactMethod
@@ -702,13 +737,13 @@ public void getActiveCall(Promise promise) {
}
if (activeCallInvite != null) {
if (BuildConfig.DEBUG) {
- Log.d(TAG, "Active call invite found state = "+activeCallInvite.getState());
+ // Log.d(TAG, "Active call invite found state = "+activeCallInvite.getState());
}
WritableMap params = Arguments.createMap();
params.putString("call_sid", activeCallInvite.getCallSid());
params.putString("call_from", activeCallInvite.getFrom());
params.putString("call_to", activeCallInvite.getTo());
- params.putString("call_state", activeCallInvite.getState().name());
+ // params.putString("call_state", activeCallInvite.getState().name());
promise.resolve(params);
return;
}
@@ -778,14 +813,15 @@ private boolean checkPermissionForMicrophone() {
}
private void requestPermissionForMicrophone() {
- if (getCurrentActivity() != null) {
- if (ActivityCompat.shouldShowRequestPermissionRationale(getCurrentActivity(), Manifest.permission.RECORD_AUDIO)) {
- // Snackbar.make(coordinatorLayout,
- // "Microphone permissions needed. Please allow in your application settings.",
- // SNACKBAR_DURATION).show();
- } else {
- ActivityCompat.requestPermissions(getCurrentActivity(), new String[]{Manifest.permission.RECORD_AUDIO}, MIC_PERMISSION_REQUEST_CODE);
- }
+ if (getCurrentActivity() == null) {
+ return;
+ }
+ if (ActivityCompat.shouldShowRequestPermissionRationale(getCurrentActivity(), Manifest.permission.RECORD_AUDIO)) {
+// Snackbar.make(coordinatorLayout,
+// "Microphone permissions needed. Please allow in your application settings.",
+// SNACKBAR_DURATION).show();
+ } else {
+ ActivityCompat.requestPermissions(getCurrentActivity(), new String[]{Manifest.permission.RECORD_AUDIO}, MIC_PERMISSION_REQUEST_CODE);
}
}
}
diff --git a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseInstanceIDService.java b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseInstanceIDService.java
deleted file mode 100644
index 9154661c..00000000
--- a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseInstanceIDService.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.hoxfon.react.RNTwilioVoice.fcm;
-
-import android.content.Intent;
-import android.support.v4.content.LocalBroadcastManager;
-import android.util.Log;
-
-import com.google.firebase.iid.FirebaseInstanceId;
-import com.google.firebase.iid.FirebaseInstanceIdService;
-import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_FCM_TOKEN;
-import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
-
-public class VoiceFirebaseInstanceIDService extends FirebaseInstanceIdService {
-
- /**
- * Called if InstanceID token is updated. This may occur if the security of
- * the previous token had been compromised. Note that this is called when the InstanceID token
- * is initially generated so this is where you would retrieve the token.
- */
- // [START refresh_token]
- @Override
- public void onTokenRefresh() {
- // Get updated InstanceID token.
- String refreshedToken = FirebaseInstanceId.getInstance().getToken();
- Log.d(TAG, "Refreshed token: " + refreshedToken);
-
- // Notify Activity of FCM token
- Intent intent = new Intent(ACTION_FCM_TOKEN);
- LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
- }
- // [END refresh_token]
-
- /**
- * Persist token to third-party servers.
- *
- * Modify this method to associate the user's FCM InstanceID token with any server-side account
- * maintained by your application.
- *
- * @param token The new token.
- */
- private void sendRegistrationToServer(String token) {
- // TODO: Implement this method to send token to your app server.
- }
-}
diff --git a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseMessagingService.java b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseMessagingService.java
index b50facdc..d0a290a9 100644
--- a/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseMessagingService.java
+++ b/android/src/main/java/com/hoxfon/react/RNTwilioVoice/fcm/VoiceFirebaseMessagingService.java
@@ -4,7 +4,6 @@
import android.app.ActivityManager;
import android.content.Intent;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.LocalBroadcastManager;
@@ -20,7 +19,7 @@
import com.hoxfon.react.RNTwilioVoice.BuildConfig;
import com.hoxfon.react.RNTwilioVoice.CallNotificationManager;
import com.twilio.voice.CallInvite;
-import com.twilio.voice.MessageException;
+import com.twilio.voice.CancelledCallInvite;
import com.twilio.voice.MessageListener;
import com.twilio.voice.Voice;
@@ -28,8 +27,11 @@
import java.util.Random;
import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.TAG;
+import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_FCM_TOKEN;
import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_INCOMING_CALL;
+import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.ACTION_CANCEL_CALL;
import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_CALL_INVITE;
+import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.CANCELLED_CALL_INVITE;
import static com.hoxfon.react.RNTwilioVoice.TwilioVoiceModule.INCOMING_CALL_NOTIFICATION_ID;
import com.hoxfon.react.RNTwilioVoice.SoundPoolManager;
@@ -43,6 +45,15 @@ public void onCreate() {
callNotificationManager = new CallNotificationManager();
}
+ @Override
+ public void onNewToken(String token) {
+ Log.d(TAG, "Refreshed token: " + token);
+
+ // Notify Activity of FCM token
+ Intent intent = new Intent(ACTION_FCM_TOKEN);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
/**
* Called when message is received.
*
@@ -62,8 +73,7 @@ public void onMessageReceived(RemoteMessage remoteMessage) {
Random randomNumberGenerator = new Random(System.currentTimeMillis());
final int notificationId = randomNumberGenerator.nextInt();
- Voice.handleMessage(this, data, new MessageListener() {
-
+ boolean valid = Voice.handleMessage(data, new MessageListener() {
@Override
public void onCallInvite(final CallInvite callInvite) {
@@ -117,10 +127,23 @@ public void onReactContextInitialized(ReactContext context) {
}
@Override
- public void onError(MessageException messageException) {
- Log.e(TAG, "Error handling FCM message" + messageException.toString());
+ public void onCancelledCallInvite(final CancelledCallInvite cancelledCallInvite) {
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ public void run() {
+ ReactInstanceManager mReactInstanceManager = ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager();
+ ReactContext context = mReactInstanceManager.getCurrentReactContext();
+ VoiceFirebaseMessagingService.this.cancelNotification((ReactApplicationContext)context, cancelledCallInvite);
+ VoiceFirebaseMessagingService.this.sendCancelledCallInviteToActivity(
+ cancelledCallInvite);
+ }
+ });
}
});
+
+ if (!valid) {
+ Log.e(TAG, "Error handling FCM message");
+ }
}
// Check if message contains a notification payload.
@@ -152,6 +175,15 @@ private void sendIncomingCallMessageToActivity(
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
+ /*
+ * Send the CancelledCallInvite to the VoiceActivity
+ */
+ private void sendCancelledCallInviteToActivity(CancelledCallInvite cancelledCallInvite) {
+ Intent intent = new Intent(ACTION_CANCEL_CALL);
+ intent.putExtra(CANCELLED_CALL_INVITE, cancelledCallInvite);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
/*
* Show the notification in the Android notification drawer
*/
@@ -161,11 +193,12 @@ private void showNotification(ReactApplicationContext context,
int notificationId,
Intent launchIntent
) {
- if (callInvite != null && callInvite.getState() == CallInvite.State.PENDING) {
- callNotificationManager.createIncomingCallNotification(context, callInvite, notificationId, launchIntent);
- } else {
- SoundPoolManager.getInstance(context.getBaseContext()).stopRinging();
- callNotificationManager.removeIncomingCallNotification(context, callInvite, 0);
- }
+ callNotificationManager.createIncomingCallNotification(context, callInvite, notificationId, launchIntent);
}
+
+ private void cancelNotification(ReactApplicationContext context, CancelledCallInvite cancelledCallInvite) {
+ SoundPoolManager.getInstance((this)).stopRinging();
+ callNotificationManager.removeIncomingCallNotification(context, cancelledCallInvite, 0);
+ }
+
}
diff --git a/ios/RNTwilioVoice/RNTwilioVoice.m b/ios/RNTwilioVoice/RNTwilioVoice.m
index 0e0a6bfc..57c43c54 100644
--- a/ios/RNTwilioVoice/RNTwilioVoice.m
+++ b/ios/RNTwilioVoice/RNTwilioVoice.m
@@ -99,7 +99,7 @@ - (void)dealloc {
device.proximityMonitoringEnabled = YES;
if (self.call && self.call.state == TVOCallStateConnected) {
- [self.call disconnect];
+ [self performEndCallActionWithUUID:self.call.uuid];
} else {
NSUUID *uuid = [NSUUID UUID];
NSString *handle = [params valueForKey:@"To"];
@@ -122,14 +122,14 @@ - (void)dealloc {
[self toggleAudioRoute:speaker];
}
-RCT_EXPORT_METHOD(sendDigits: (NSString *)digits){
+RCT_EXPORT_METHOD(sendDigits: (NSString *)digits) {
if (self.call && self.call.state == TVOCallStateConnected) {
NSLog(@"SendDigits %@", digits);
[self.call sendDigits:digits];
}
}
-RCT_EXPORT_METHOD(unregister){
+RCT_EXPORT_METHOD(unregister) {
NSLog(@"unregister");
NSString *accessToken = [self fetchAccessToken];
@@ -148,16 +148,16 @@ - (void)dealloc {
RCT_REMAP_METHOD(getActiveCall,
resolver:(RCTPromiseResolveBlock)resolve
- rejecter:(RCTPromiseRejectBlock)reject){
+ rejecter:(RCTPromiseRejectBlock)reject) {
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
if (self.callInvite) {
- if (self.callInvite.callSid){
+ if (self.callInvite.callSid) {
[params setObject:self.callInvite.callSid forKey:@"call_sid"];
}
- if (self.callInvite.from){
+ if (self.callInvite.from) {
[params setObject:self.callInvite.from forKey:@"from"];
}
- if (self.callInvite.to){
+ if (self.callInvite.to) {
[params setObject:self.callInvite.to forKey:@"to"];
}
if (self.callInvite.state == TVOCallInviteStatePending) {
@@ -172,10 +172,10 @@ - (void)dealloc {
if (self.call.sid) {
[params setObject:self.call.sid forKey:@"call_sid"];
}
- if (self.call.to){
+ if (self.call.to) {
[params setObject:self.call.to forKey:@"call_to"];
}
- if (self.call.from){
+ if (self.call.from) {
[params setObject:self.call.from forKey:@"call_from"];
}
if (self.call.state == TVOCallStateConnected) {
@@ -294,15 +294,15 @@ - (void)handleCallInviteCanceled:(TVOCallInvite *)callInvite {
[self performEndCallActionWithUUID:callInvite.uuid];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
- if (self.callInvite.callSid){
+ if (self.callInvite.callSid) {
[params setObject:self.callInvite.callSid forKey:@"call_sid"];
}
- if (self.callInvite.from){
- [params setObject:self.callInvite.from forKey:@"from"];
+ if (self.callInvite.from) {
+ [params setObject:self.callInvite.from forKey:@"call_from"];
}
- if (self.callInvite.to){
- [params setObject:self.callInvite.to forKey:@"to"];
+ if (self.callInvite.to) {
+ [params setObject:self.callInvite.to forKey:@"call_to"];
}
if (self.callInvite.state == TVOCallInviteStateCanceled) {
[params setObject:StateDisconnected forKey:@"call_state"];
@@ -332,11 +332,11 @@ - (void)callDidConnect:(TVOCall *)call {
[callParams setObject:StateConnected forKey:@"call_state"];
}
- if (call.from){
- [callParams setObject:call.from forKey:@"from"];
+ if (call.from) {
+ [callParams setObject:call.from forKey:@"call_from"];
}
- if (call.to){
- [callParams setObject:call.to forKey:@"to"];
+ if (call.to) {
+ [callParams setObject:call.to forKey:@"call_to"];
}
[self sendEventWithName:@"connectionDidConnect" body:callParams];
}
@@ -352,7 +352,6 @@ - (void)call:(TVOCall *)call didFailToConnectWithError:(NSError *)error {
- (void)call:(TVOCall *)call didDisconnectWithError:(NSError *)error {
NSLog(@"Call disconnected with error: %@", error);
- [self performEndCallActionWithUUID:call.uuid];
[self callDisconnected:error];
}
@@ -363,15 +362,15 @@ - (void)callDisconnected:(NSError *)error {
if (error.localizedFailureReason) {
errMsg = [error localizedFailureReason];
}
- [params setObject:errMsg forKey:@"error"];
+ [params setObject:errMsg forKey:@"err"];
}
if (self.call.sid) {
[params setObject:self.call.sid forKey:@"call_sid"];
}
- if (self.call.to){
+ if (self.call.to) {
[params setObject:self.call.to forKey:@"call_to"];
}
- if (self.call.from){
+ if (self.call.from) {
[params setObject:self.call.from forKey:@"call_from"];
}
if (self.call.state == TVOCallStateDisconnected) {
diff --git a/package.json b/package.json
index 02d0d67b..3936971a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-twilio-programmable-voice",
- "version": "3.21.1",
+ "version": "4.0.0",
"description": "React Native wrapper for Twilio Programmable Voice SDK",
"main": "index.js",
"scripts": {