See: Description
Package | Description |
---|---|
com.boostlingo.android |
The Boostlingo Android library enables developers to embed the Boostlingo caller directly into their own applications. This can then be used for placing calls in the Boostlingo platform.
In order to place calls in Boostlingo, you must have a requestor account. You can then embed Boostlingo Android into your application, request a Boostlingo API token from your server, and start making calls.
Download the lastest version of the Boostlingo library and put it into your /lib folder.
Add all needed dependencies into your build.gradle:
// Boostlingo
implementation fileTree(dir: 'libs', include: ['*.aar'])
// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Okhttp3
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
implementation 'com.android.support:support-annotations:28.0.0'
// SignalR
implementation 'com.microsoft.signalr:signalr:3.1.8'
// Rx
implementation "io.reactivex.rxjava2:rxjava:2.2.8"
// Twilio Voice
implementation 'com.twilio:voice-android:5.7.2'
// Twilio Video
implementation 'com.twilio:video-android:6.3.0'
// Gson
implementation 'com.google.code.gson:gson:2.8.6'
// Android X
implementation 'androidx.annotation:annotation:1.2.0'
Update your proguard-rules.pro file:
# Twilio Programmable Voice
-keep class com.twilio.** { *; }
-keep class org.webrtc.** { *; }
-dontwarn org.webrtc.**
-keep class com.twilio.voice.** { *; }
-keepattributes InnerClasses
# Twilio Programmable Video
-keep class tvi.webrtc.** { *; }
-keep class com.twilio.video.** { *; }
-keep class com.twilio.common.** { *; }
-keepattributes InnerClasses
These steps will guide you through the basic process of placing calls through Boostlingo.
First step is to obtain a boostlingo authentication token from your server. Never store an API token or username/password in your front end/mobile code. Your server should be the one that logs the user in, obtains the authentication token, and passes it back down to the web application.
POST https://app.boostlingo.com/api/web/account/signin
Request Model
{
"email": "<string>",
"password": "<string>"
}
Request Model
token is what will be needed by the boostlingo-android library
{
"userAccountId": "<integer>",
"role": "<string>",
"token": "<string>",
"companyAccountId": "<integer>"
}
This is a working example that will demonstrate how to Boostlingo calls. Now let’s go to the Quickstart folder. Then download and build dependencies. Update the placeholder of TOKEN with the token you got from the API.
private static final String TOKEN = <TOKEN>;
We recommend you do this only once. The Boostlingo library will cache specific data and create instances of classes that do not need to be refreshed very frequently. The next step is typically to pull down the call dictionaries. Whether you expose these directly or are just mapping languages and service types with your internal types, loading these lists will almost definitely be required. In this example we populate a series of select dropdown inputs.
updateUI(State.LOADING);
boostlingo = new Boostlingo(this, TOKEN, spRegions.getSelectedItem().toString(), BLLogLevel.DEBUG);
boostlingo.getCallDictionaries().subscribe(new SingleObserver<CallDictionaries>() {
@Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onSuccess(CallDictionaries callDictionaries) {
runOnUiThread(() -> {
ArrayAdapter<Language> languageAdapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_spinner_item, callDictionaries.languages);
languageAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spLanguageFrom.setAdapter(languageAdapter);
for (Language language : callDictionaries.languages) {
if (language.id == 4) {
spLanguageFrom.setSelection(callDictionaries.languages.indexOf(language));
break;
}
}
spLanguageTo.setAdapter(languageAdapter);
for (Language language : callDictionaries.languages) {
if (language.id == 1) {
spLanguageTo.setSelection(callDictionaries.languages.indexOf(language));
break;
}
}
ArrayAdapter<ServiceType> serviceTypeAdapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_spinner_item, callDictionaries.serviceTypes);
serviceTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spServiceType.setAdapter(serviceTypeAdapter);
for (ServiceType serviceType : callDictionaries.serviceTypes) {
if (serviceType.id == 1) {
spServiceType.setSelection(callDictionaries.serviceTypes.indexOf(serviceType));
break;
}
}
ArrayAdapter<Gender> genderAdapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_spinner_item, callDictionaries.genders);
genderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spGender.setAdapter(genderAdapter);
for (Gender gender : callDictionaries.genders) {
if (gender.id == 1) {
spGender.setSelection(callDictionaries.genders.indexOf(gender));
break;
}
}
updateUI(State.AUTHENTICATED);
});
}
@Override
public void onError(Throwable e) {
runOnUiThread(() -> {
updateUI(State.NOT_AUTHENTICATED);
Snackbar.make(clRoot,
e != null ? "Error: " + e.getLocalizedMessage() : "Error",
SNACKBAR_DURATION).show();
});
}
});
@Override
public void callConnected(@NonNull BLCall call) {
runOnUiThread(() -> {
currentCall = (BLVoiceCall) call;
lastCallId = call.getCallId();
swMute.setChecked(call.isMuted());
updateUI(State.IN_PROGRESS);
setAudioFocus(true);
swSpeaker.setChecked(true);
});
}
@Override
public void callFailedToConnect(@Nullable Throwable e) {
runOnUiThread(() -> {
currentCall = null;
updateUI(State.NO_CALL);
setAudioFocus(false);
Snackbar.make(clRoot,
e != null ? "Call did fail to connect with error: " + e.getLocalizedMessage() : "Call did fail to connect",
SNACKBAR_DURATION).show();
});
}
@Override
public void callDisconnected(@Nullable Throwable e) {
runOnUiThread(() -> {
currentCall = null;
updateUI(State.NO_CALL);
setAudioFocus(false);
Snackbar.make(clRoot,
e != null ? "Call did disconnect with error: " + e.getLocalizedMessage() : "Call did disconnect",
SNACKBAR_DURATION).show();
});
}
@Override public void callConnected(@NonNull BLCall call) { runOnUiThread(() -> { currentCall = (BLVideoCall) call; updateUI(State.IN_PROGRESS); lastCallId = call.getCallId(); }); } @Override public void callFailedToConnect(@Nullable Throwable e) { runOnUiThread(() -> { currentCall = null; configureAudio(false); updateUI(State.NO_CALL); Snackbar.make(clRoot, e != null ? "Call did fail to connect with error: " + e.getLocalizedMessage() : "Call did fail to connect", SNACKBAR_DURATION).show(); }); } @Override public void callDisconnected(@Nullable Throwable e) { runOnUiThread(() -> { currentCall = null; configureAudio(false); updateUI(State.NO_CALL); Snackbar.make(clRoot, e != null ? "Call did disconnect with error: " + e.getLocalizedMessage() : "Call did disconnect", SNACKBAR_DURATION).show(); }); } @Override public void onAudioTrackPublished() { } @Override public void onAudioTrackUnpublished() { } @Override public void onVideoTrackPublished() { } @Override public void onVideoTrackUnpublished() { } @Override public void onAudioTrackEnabled() { } @Override public void onAudioTrackDisabled() { } @Override public void onVideoTrackEnabled() { } @Override public void onVideoTrackDisabled() { // TODO: Interpreter has disabled the video. Show you privacy screen here // Get the interpreter profile image URL // String url = currentCall.getInterlocutorInfo().imageInfo.url(null); }
Placing a voice call
Before placing a call you will need to check record permission:
private boolean checkPermissionForMicrophone() { int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO); return resultMic == PackageManager.PERMISSION_GRANTED; } private void requestPermissionForMicrophone() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) { Snackbar.make(clRoot, "Microphone permissions needed. Please allow in your application settings.", SNACKBAR_DURATION).show(); } else { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.RECORD_AUDIO}, MIC_PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // Check if microphone permissions is granted. if (requestCode == MIC_PERMISSION_REQUEST_CODE && permissions.length > 0) { if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { Snackbar.make(clRoot, "Microphone permissions needed. Please allow in your application settings.", SNACKBAR_DURATION).show(); } else { makeCall(); } } }
Placing a voice call:
private void makeCall() { boostlingo.makeVoiceCall(callRequest, this) .subscribe(new SingleObserver<BLVoiceCall>() { @Override public void onSubscribe(Disposable d) { compositeDisposable.add(d); } @Override public void onSuccess(BLVoiceCall call) { runOnUiThread(() -> { updateUI(State.CALLING); currentCall = call; }); } @Override public void onError(Throwable e) { runOnUiThread(() -> { updateUI(State.NO_CALL); Snackbar.make(clRoot, e != null ? "Error: " + e.getLocalizedMessage() : "Error", SNACKBAR_DURATION).show(); }); } }); }
Placing a video call
Before placing a call you will need to check camera permission:
private boolean checkPermissionForCameraAndMicrophone() { int resultCamera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO); return resultCamera == PackageManager.PERMISSION_GRANTED && resultMic == PackageManager.PERMISSION_GRANTED; } private void requestPermissionForCameraAndMicrophone() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) { Toast.makeText(this, R.string.permissions_needed, Toast.LENGTH_LONG).show(); } else { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, CAMERA_MIC_PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == CAMERA_MIC_PERMISSION_REQUEST_CODE) { boolean cameraAndMicPermissionGranted = true; for (int grantResult : grantResults) { cameraAndMicPermissionGranted &= grantResult == PackageManager.PERMISSION_GRANTED; } if (cameraAndMicPermissionGranted) { connectToRoom(); } else { Toast.makeText(this, R.string.permissions_needed, Toast.LENGTH_LONG).show(); } } }
Placing a video call:
private void connectToRoom() { configureAudio(true); boostlingo.makeVideoCall(callRequest, this, this, primaryVideoView, thumbnailVideoView) .subscribe(new SingleObserver<BLVideoCall>() { @Override public void onSubscribe(Disposable d) { compositeDisposable.add(d); } @Override public void onSuccess(BLVideoCall call) { runOnUiThread(() -> { updateUI(State.CALLING); currentCall = call; }); } @Override public void onError(Throwable e) { runOnUiThread(() -> { updateUI(State.NO_CALL); Snackbar.make(clRoot, e != null ? "Error: " + e.getLocalizedMessage() : "Error", SNACKBAR_DURATION).show(); }); } }); } private void configureAudio(boolean enable) { if (enable) { previousAudioMode = audioManager.getMode(); // Request audio focus before making any device switch requestAudioFocus(); /* * Use MODE_IN_COMMUNICATION as the default audio mode. It is required * to be in this mode when playout and/or recording starts for the best * possible VoIP performance. Some devices have difficulties with * speaker mode if this is not set. */ audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); /* * Always disable microphone mute during a WebRTC call. */ previousMicrophoneMute = audioManager.isMicrophoneMute(); audioManager.setMicrophoneMute(false); } else { audioManager.setMode(previousAudioMode); audioManager.abandonAudioFocus(null); audioManager.setMicrophoneMute(previousMicrophoneMute); } }
Using the chat functionality
Subscribe for the chat related callback using BLChatListener.
// Chat Listener @Override public void chatConnected() { } @Override public void chatDisconnected() { } @Override public void chatMessageReceived(ChatMessage message) { runOnUiThread(() -> { Snackbar.make(clRoot, "Chat Message Received: " + message.text, SNACKBAR_DURATION).show(); }); }
Sending messages.
boostlingo.sendChatMessage("Test").subscribe(new SingleObserver<ChatMessage>() { @Override public void onSubscribe(Disposable d) { compositeDisposable.add(d); } @Override public void onSuccess(ChatMessage message) { runOnUiThread(() -> { Snackbar.make(clRoot, "Success: Message sent", SNACKBAR_DURATION).show(); }); } @Override public void onError(Throwable e) { runOnUiThread(() -> { Snackbar.make(clRoot, e != null ? "Error: " + e.getLocalizedMessage() : "Error", SNACKBAR_DURATION).show(); }); } });
Geting profile image URL
Getting requestor profile image url.
boostlingo.getProfile().subscribe(new SingleObserver<Profile>() { @Override public void onSubscribe(Disposable d) { compositeDisposable.add(d); } @Override public void onSuccess(Profile profile) { if (profile.imageInfo != null) { String url = profile.imageInfo.url(null); } } @Override public void onError(Throwable e) { } });
Getting interpreter profile image url from BLCall.
if (currentCall.getInterlocutorInfo().imageInfo != null) { String url = currentCall.getInterlocutorInfo().imageInfo.url(null); }
More Documentation
You can find more documentation and useful information below:
Quickstart