Skip to content

Commit 0e431d1

Browse files
committed
Merge branch 'main' into task/merge-main-into-preflight
2 parents 1a5eddd + f407388 commit 0e431d1

File tree

17 files changed

+104
-294
lines changed

17 files changed

+104
-294
lines changed

.circleci/config.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ parameters:
1616
release_workflow:
1717
type: boolean
1818
default: false
19+
qe_workflow:
20+
type: boolean
21+
default: false
1922
release_command:
2023
type: string
2124
default: "echo \"no release-command specified\""
@@ -344,14 +347,14 @@ jobs:
344347
command: |
345348
curl --fail --write-out "\nHTTP Response Code: %{http_code}\n" \
346349
-u "$CIRCLECI_PERSONAL_API_TOKEN": -X POST --header "Content-Type: application/json" \
347-
-d '{"branch":"main","parameters":{"rc-version":"'$CIRCLE_TAG'","rc-testing":true}}' \
350+
-d '{"config":{"branch":"main"},"checkout":{"branch":"main"},"definition_id":"'$SDKS_QE_CIRCLECI_VOICE_REACT_SLAVE_DEFINITION_ID'","parameters":{"rc-version":"'$CIRCLE_TAG'","rc-testing":true}}' \
348351
$SDKS_QE_CIRCLECI_VOICE_REACT_SLAVE_PIPELINE_ENDPOINT
349352
- run:
350353
name: Trigger custom android messaging QE tests
351354
command: |
352355
curl --fail --write-out "\nHTTP Response Code: %{http_code}\n" \
353356
-u "$CIRCLECI_PERSONAL_API_TOKEN": -X POST --header "Content-Type: application/json" \
354-
-d '{"branch":"custom-android-messaging","parameters":{"rc-version":"'$CIRCLE_TAG'","rc-testing":true}}' \
357+
-d '{"config":{"branch":"custom-android-messaging"},"checkout":{"branch":"custom-android-messaging"},"definition_id":"'$SDKS_QE_CIRCLECI_VOICE_REACT_SLAVE_DEFINITION_ID'","parameters":{"rc-version":"'$CIRCLE_TAG'","rc-testing":true}}' \
355358
$SDKS_QE_CIRCLECI_VOICE_REACT_SLAVE_PIPELINE_ENDPOINT
356359
357360
@@ -427,8 +430,11 @@ workflows:
427430
dry-run: false
428431
requires:
429432
- Release approval
433+
release-candidate:
434+
jobs:
430435
- trigger-qe-tests:
431436
context: sdks-qe
437+
name: Trigger QE Regression Tests
432438
filters:
433439
tags:
434440
only:
@@ -437,3 +443,9 @@ workflows:
437443
- /^\d+\.\d+\.\d+-beta\d+-rc\d+$/
438444
branches:
439445
ignore: /.*/
446+
qe-trigger-test:
447+
when: <<pipeline.parameters.qe_workflow>>
448+
jobs:
449+
- trigger-qe-tests:
450+
context: sdks-qe
451+
name: Test QE Regression Tests Trigger

CHANGELOG.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
1.7.0 (In Progress)
22
===================
33

4-
## Fixes
4+
## Changes
55

6-
- The call contact handle template feature now caches the set value. This fixes an issue where the handle template value would be `null` when an incoming call was received and the React Native JS runtime was not initialized or was restarted by the OS.
6+
### Platform Specific Changes
77

8-
## Features
8+
#### iOS
99

10-
### Platform Specific Features
10+
- Updated the Twilio Voice iOS SDK version to `6.13.3`. This update fixes a Bluetooth device type deprecation warning when building with Xcode 26.
1111

12-
#### Android
12+
## Fixes
1313

14-
- Added a new API to check for and request Full Screen Notification permissions on Android platforms.
14+
- The call contact handle template feature now caches the set value. This fixes an issue where the handle template value would be `null` when an incoming call was received and the React Native JS runtime was not initialized or was restarted by the OS.
1515

1616
1.6.1 (July 7, 2025)
1717
====================

android/src/main/java/com/twiliovoicereactnative/ConfigurationProperties.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,4 @@ public static boolean isFirebaseServiceEnabled(Context context) {
2727
return context.getResources()
2828
.getBoolean(R.bool.twiliovoicereactnative_firebasemessagingservice_enabled);
2929
}
30-
31-
/**
32-
* Get configuration boolean, used to determine if full screen notifications are enabled
33-
* or not.
34-
* @param context the application context
35-
* @return a boolean read from the application resources
36-
*/
37-
public static boolean isFullScreenNotificationEnabled(Context context) {
38-
return context.getResources()
39-
.getBoolean(R.bool.twiliovoicereactnative_fullscreennotification_enabled);
40-
}
4130
}

android/src/main/java/com/twiliovoicereactnative/NotificationUtility.java

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import com.twilio.voice.CallInvite;
2626

27-
import static com.twiliovoicereactnative.ConfigurationProperties.isFullScreenNotificationEnabled;
2827
import static com.twiliovoicereactnative.Constants.VOICE_CHANNEL_DEFAULT_IMPORTANCE;
2928
import static com.twiliovoicereactnative.Constants.VOICE_CHANNEL_HIGH_IMPORTANCE;
3029
import static com.twiliovoicereactnative.Constants.VOICE_CHANNEL_LOW_IMPORTANCE;
@@ -166,18 +165,16 @@ public static Notification createIncomingCallNotification(@NonNull Context conte
166165
callRecord.getUuid());
167166
PendingIntent piAcceptIntent = constructPendingIntentForActivity(context, acceptIntent);
168167

169-
NotificationCompat.Builder builder = constructNotificationBuilder(context, channelImportance)
168+
return constructNotificationBuilder(context, channelImportance)
170169
.setSmallIcon(notificationResource.getSmallIconId())
171170
.setCategory(Notification.CATEGORY_CALL)
172171
.setAutoCancel(true)
173172
.setContentIntent(piForegroundIntent)
173+
.setFullScreenIntent(piForegroundIntent, true)
174174
.addPerson(incomingCaller)
175175
.setStyle(NotificationCompat.CallStyle.forIncomingCall(
176-
incomingCaller, piRejectIntent, piAcceptIntent));
177-
if (isFullscreenIntentEnabled(context)) {
178-
builder.setFullScreenIntent(piForegroundIntent, true);
179-
}
180-
return builder.build();
176+
incomingCaller, piRejectIntent, piAcceptIntent))
177+
.build();
181178
}
182179

183180
public static Notification createCallAnsweredNotificationWithLowImportance(@NonNull Context context,
@@ -205,18 +202,16 @@ public static Notification createCallAnsweredNotificationWithLowImportance(@NonN
205202
callRecord.getUuid());
206203
PendingIntent piEndCallIntent = constructPendingIntentForService(context, endCallIntent);
207204

208-
NotificationCompat.Builder builder = constructNotificationBuilder(context, Constants.VOICE_CHANNEL_LOW_IMPORTANCE)
205+
return constructNotificationBuilder(context, Constants.VOICE_CHANNEL_LOW_IMPORTANCE)
209206
.setSmallIcon(notificationResource.getSmallIconId())
210207
.setCategory(Notification.CATEGORY_CALL)
211208
.setAutoCancel(false)
212209
.setContentIntent(piForegroundIntent)
210+
.setFullScreenIntent(piForegroundIntent, true)
213211
.setOngoing(true)
214212
.addPerson(activeCaller)
215-
.setStyle(NotificationCompat.CallStyle.forOngoingCall(activeCaller, piEndCallIntent));
216-
if (isFullscreenIntentEnabled(context)) {
217-
builder.setFullScreenIntent(piForegroundIntent, true);
218-
}
219-
return builder.build();
213+
.setStyle(NotificationCompat.CallStyle.forOngoingCall(activeCaller, piEndCallIntent))
214+
.build();
220215
}
221216

222217
public static Notification createOutgoingCallNotificationWithLowImportance(@NonNull Context context,
@@ -244,18 +239,16 @@ public static Notification createOutgoingCallNotificationWithLowImportance(@NonN
244239
callRecord.getUuid());
245240
PendingIntent piEndCallIntent = constructPendingIntentForService(context, endCallIntent);
246241

247-
NotificationCompat.Builder builder = constructNotificationBuilder(context, Constants.VOICE_CHANNEL_LOW_IMPORTANCE)
242+
return constructNotificationBuilder(context, Constants.VOICE_CHANNEL_LOW_IMPORTANCE)
248243
.setSmallIcon(notificationResource.getSmallIconId())
249244
.setCategory(Notification.CATEGORY_CALL)
250245
.setAutoCancel(false)
251246
.setContentIntent(piForegroundIntent)
247+
.setFullScreenIntent(piForegroundIntent, true)
252248
.setOngoing(true)
253249
.addPerson(activeCaller)
254-
.setStyle(NotificationCompat.CallStyle.forOngoingCall(activeCaller, piEndCallIntent));
255-
if (isFullscreenIntentEnabled(context)) {
256-
builder.setFullScreenIntent(piForegroundIntent, true);
257-
}
258-
return builder.build();
250+
.setStyle(NotificationCompat.CallStyle.forOngoingCall(activeCaller, piEndCallIntent))
251+
.build();
259252
}
260253

261254
public static void createNotificationChannels(@NonNull Context context) {
@@ -279,11 +272,6 @@ public static void destroyNotificationChannels(@NonNull Context context) {
279272
notificationManager.deleteNotificationChannelGroup(Constants.VOICE_CHANNEL_GROUP);
280273
}
281274

282-
public static boolean isFullscreenIntentEnabled(Context context) {
283-
return isFullScreenNotificationEnabled(context) &&
284-
NotificationManagerCompat.from(context).canUseFullScreenIntent();
285-
}
286-
287275
private static NotificationChannelCompat createNotificationChannel(@NonNull Context context,
288276
@NonNull final String voiceChannelId) {
289277
final int notificationImportance = getChannelImportance(voiceChannelId);

android/src/main/java/com/twiliovoicereactnative/TwilioVoiceReactNativeModule.java

Lines changed: 61 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.twiliovoicereactnative;
22

33
import androidx.annotation.NonNull;
4-
import androidx.core.app.NotificationManagerCompat;
54

65
import com.facebook.react.bridge.Arguments;
76
import com.facebook.react.bridge.Promise;
@@ -53,7 +52,6 @@
5352
import static com.twiliovoicereactnative.CommonConstants.VoiceEventError;
5453
import static com.twiliovoicereactnative.CommonConstants.VoiceEventRegistered;
5554
import static com.twiliovoicereactnative.CommonConstants.VoiceEventUnregistered;
56-
import static com.twiliovoicereactnative.ConfigurationProperties.isFullScreenNotificationEnabled;
5755
import static com.twiliovoicereactnative.JSEventEmitter.constructJSMap;
5856
import static com.twiliovoicereactnative.ReactNativeArgumentsSerializer.serializeCall;
5957
import static com.twiliovoicereactnative.ReactNativeArgumentsSerializer.serializeCallInvite;
@@ -64,12 +62,8 @@
6462
import static com.twiliovoicereactnative.ReactNativeArgumentsSerializer.*;
6563

6664
import android.annotation.SuppressLint;
67-
import android.content.Intent;
68-
import android.net.Uri;
69-
import android.os.Build;
7065
import android.os.Handler;
7166
import android.os.Looper;
72-
import android.provider.Settings;
7367
import android.util.Pair;
7468

7569
import com.twiliovoicereactnative.CallRecordDatabase.CallRecord;
@@ -160,6 +154,67 @@ public void removeListeners(Integer count) {
160154
logger.debug("Calling removeListeners: " + count);
161155
}
162156

157+
@Override
158+
@NonNull
159+
public String getName() {
160+
return TAG;
161+
}
162+
163+
private RegistrationListener createRegistrationListener(Promise promise) {
164+
return new RegistrationListener() {
165+
@Override
166+
public void onRegistered(@NonNull String accessToken, @NonNull String fcmToken) {
167+
logger.log("Successfully registered FCM");
168+
sendJSEvent(constructJSMap(new Pair<>(VoiceEventType, VoiceEventRegistered)));
169+
promise.resolve(null);
170+
}
171+
172+
@Override
173+
public void onError(@NonNull RegistrationException registrationException,
174+
@NonNull String accessToken,
175+
@NonNull String fcmToken) {
176+
String errorMessage = reactContext.getString(
177+
R.string.registration_error,
178+
registrationException.getErrorCode(),
179+
registrationException.getMessage());
180+
logger.error(errorMessage);
181+
182+
sendJSEvent(constructJSMap(
183+
new Pair<>(VoiceEventType, VoiceEventError),
184+
new Pair<>(VoiceErrorKeyError, serializeVoiceException(registrationException))));
185+
186+
promise.reject(errorMessage);
187+
}
188+
};
189+
}
190+
191+
private UnregistrationListener createUnregistrationListener(Promise promise) {
192+
return new UnregistrationListener() {
193+
@Override
194+
public void onUnregistered(String accessToken, String fcmToken) {
195+
logger.log("Successfully unregistered FCM");
196+
sendJSEvent(constructJSMap(new Pair<>(VoiceEventType, VoiceEventUnregistered)));
197+
promise.resolve(null);
198+
}
199+
200+
@Override
201+
public void onError(RegistrationException registrationException, String accessToken, String fcmToken) {
202+
@SuppressLint("DefaultLocale")
203+
String errorMessage = reactContext.getString(
204+
R.string.unregistration_error,
205+
registrationException.getErrorCode(),
206+
registrationException.getMessage());
207+
logger.error(errorMessage);
208+
209+
sendJSEvent(constructJSMap(
210+
new Pair<>(VoiceEventType, VoiceEventError),
211+
new Pair<>(VoiceErrorKeyError, serializeVoiceException(registrationException))));
212+
213+
promise.reject(errorMessage);
214+
}
215+
};
216+
}
217+
163218
@ReactMethod
164219
public void voice_connect_android(
165220
String accessToken,
@@ -908,97 +963,6 @@ public void callInvite_reject(String uuid, Promise promise) {
908963
});
909964
}
910965

911-
@ReactMethod
912-
public void system_isFullScreenNotificationEnabled(Promise promise) {
913-
boolean isEnabled =
914-
isFullScreenNotificationEnabled(reactContext) &&
915-
NotificationManagerCompat.from(reactContext).canUseFullScreenIntent();
916-
917-
promise.resolve(isEnabled);
918-
}
919-
920-
@ReactMethod
921-
public void system_requestFullScreenNotificationPermission(Promise promise) {
922-
final boolean shouldStartActivity =
923-
Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU &&
924-
isFullScreenNotificationEnabled(reactContext);
925-
926-
if (shouldStartActivity) {
927-
try {
928-
Intent intent = new Intent(
929-
Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
930-
Uri.parse("package:" + reactContext.getPackageName()));
931-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
932-
reactContext.startActivity(intent);
933-
} catch (Exception e) {
934-
promise.reject(e);
935-
}
936-
}
937-
938-
promise.resolve(null);
939-
}
940-
941-
@Override
942-
@NonNull
943-
public String getName() {
944-
return TAG;
945-
}
946-
947-
private RegistrationListener createRegistrationListener(Promise promise) {
948-
return new RegistrationListener() {
949-
@Override
950-
public void onRegistered(@NonNull String accessToken, @NonNull String fcmToken) {
951-
logger.log("Successfully registered FCM");
952-
sendJSEvent(constructJSMap(new Pair<>(VoiceEventType, VoiceEventRegistered)));
953-
promise.resolve(null);
954-
}
955-
956-
@Override
957-
public void onError(@NonNull RegistrationException registrationException,
958-
@NonNull String accessToken,
959-
@NonNull String fcmToken) {
960-
String errorMessage = reactContext.getString(
961-
R.string.registration_error,
962-
registrationException.getErrorCode(),
963-
registrationException.getMessage());
964-
logger.error(errorMessage);
965-
966-
sendJSEvent(constructJSMap(
967-
new Pair<>(VoiceEventType, VoiceEventError),
968-
new Pair<>(VoiceErrorKeyError, serializeVoiceException(registrationException))));
969-
970-
promise.reject(errorMessage);
971-
}
972-
};
973-
}
974-
975-
private UnregistrationListener createUnregistrationListener(Promise promise) {
976-
return new UnregistrationListener() {
977-
@Override
978-
public void onUnregistered(String accessToken, String fcmToken) {
979-
logger.log("Successfully unregistered FCM");
980-
sendJSEvent(constructJSMap(new Pair<>(VoiceEventType, VoiceEventUnregistered)));
981-
promise.resolve(null);
982-
}
983-
984-
@Override
985-
public void onError(RegistrationException registrationException, String accessToken, String fcmToken) {
986-
@SuppressLint("DefaultLocale")
987-
String errorMessage = reactContext.getString(
988-
R.string.unregistration_error,
989-
registrationException.getErrorCode(),
990-
registrationException.getMessage());
991-
logger.error(errorMessage);
992-
993-
sendJSEvent(constructJSMap(
994-
new Pair<>(VoiceEventType, VoiceEventError),
995-
new Pair<>(VoiceErrorKeyError, serializeVoiceException(registrationException))));
996-
997-
promise.reject(errorMessage);
998-
}
999-
};
1000-
}
1001-
1002966
/**
1003967
* Use the score map to get a Call.Score value from a string.
1004968
* @param score The score as a string passed from the JS layer.
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
33
<bool name="twiliovoicereactnative_firebasemessagingservice_enabled">true</bool>
4-
<bool name="twiliovoicereactnative_fullscreennotification_enabled">true</bool>
54
</resources>

api/voice-react-native-sdk.api.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,9 +1205,7 @@ export class Voice extends EventEmitter {
12051205
getVersion(): Promise<string>;
12061206
handleFirebaseMessage(remoteMessage: Record<string, string>): Promise<boolean>;
12071207
initializePushRegistry(): Promise<void>;
1208-
isFullScreenNotificationEnabled(): Promise<boolean>;
12091208
register(token: string): Promise<void>;
1210-
requestFullScreenNotificationPermission(): Promise<void>;
12111209
runPreflight(accessToken: string, options?: PreflightTest.Options): Promise<PreflightTest>;
12121210
setCallKitConfiguration(configuration: CallKit.ConfigurationOptions): Promise<void>;
12131211
setIncomingCallContactHandleTemplate(template?: string): Promise<void>;

constants/constants.src

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// React Native Voice SDK
22
ReactNativeVoiceSDK=react-native
3-
ReactNativeVoiceSDKVer=1.6.2-dev
3+
ReactNativeVoiceSDKVer=1.7.0-dev
44

55
// Scope names
66
ScopeVoice=scopeVoice

0 commit comments

Comments
 (0)