[Calling][Release] 1.8.0
This commit is contained in:
Коммит
9f97232735
|
@ -5,7 +5,7 @@ buildscript {
|
|||
}
|
||||
|
||||
ext {
|
||||
call_library_version_name = '1.7.0'
|
||||
call_library_version_name = '1.8.0'
|
||||
chat_library_version_name = '1.0.0-beta.3'
|
||||
|
||||
ui_library_version_code = getVersionCode()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Latest Release
|
||||
|
||||
- [1.6.0 release](https://github.com/Azure/communication-ui-library-android/releases/tag/calling-v1.6.0)
|
||||
- [1.8.0 release](https://github.com/Azure/communication-ui-library-android/releases/tag/calling-v1.8.0)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -27,7 +27,7 @@ android {
|
|||
```groovy
|
||||
dependencies {
|
||||
...
|
||||
implementation 'com.azure.android:azure-communication-ui-calling:1.6.0'
|
||||
implementation 'com.azure.android:azure-communication-ui-calling:1.8.0'
|
||||
...
|
||||
}
|
||||
```
|
||||
|
@ -57,11 +57,13 @@ Create `CallComposite` and launch it. Replace `<GROUP_CALL_ID>` with your group
|
|||
val communicationTokenRefreshOptions = CommunicationTokenRefreshOptions({ "<USER_ACCESS_TOKEN>" }, true)
|
||||
val communicationTokenCredential = CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
|
||||
val locator: CallCompositeJoinLocator = CallCompositeGroupCallLocator(UUID.fromString("<GROUP_CALL_ID>"))
|
||||
val remoteOptions = CallCompositeRemoteOptions(locator, communicationTokenCredential, "<DISPLAY_NAME>")
|
||||
|
||||
val callComposite: CallComposite = CallCompositeBuilder().build()
|
||||
callComposite.launch(context, remoteOptions)
|
||||
val locator: CallCompositeJoinLocator = CallCompositeGroupCallLocator(UUID.fromString("GROUP_CALL_ID"))
|
||||
val callComposite: CallComposite = CallCompositeBuilder()
|
||||
.applicationContext(this.applicationContext)
|
||||
.credential(communicationTokenCredential)
|
||||
.displayName("DISPLAY_NAME").build()
|
||||
|
||||
callComposite.launch(this, locator)
|
||||
```
|
||||
|
||||
#### [Java](#tab/java)
|
||||
|
@ -73,22 +75,23 @@ CommunicationTokenRefreshOptions communicationTokenRefreshOptions =
|
|||
CommunicationTokenCredential communicationTokenCredential =
|
||||
new CommunicationTokenCredential(communicationTokenRefreshOptions);
|
||||
|
||||
final CallCompositeJoinLocator locator = new CallCompositeGroupCallLocator(UUID.fromString("<GROUP_CALL_ID>"));
|
||||
final CallCompositeRemoteOptions remoteOptions =
|
||||
new CallCompositeRemoteOptions(locator, communicationTokenCredential, "<DISPLAY_NAME>");
|
||||
final CallCompositeJoinLocator locator = new CallCompositeGroupCallLocator(UUID.fromString("GROUP_CALL_ID"));
|
||||
|
||||
CallComposite callComposite = new CallCompositeBuilder().build();
|
||||
callComposite.launch(context, remoteOptions);
|
||||
CallComposite callComposite = new CallCompositeBuilder()
|
||||
.applicationContext(this.getApplicationContext())
|
||||
.credential(communicationTokenCredential)
|
||||
.displayName("DISPLAY_NAME").build();
|
||||
callComposite.launch(this, locator);
|
||||
```
|
||||
|
||||
For more details on Mobile UI Library functionalities visit the [API Reference Documentation](https://azure.github.io/azure-sdk-for-android/azure-communication-ui-calling).
|
||||
|
||||
### Accessibility
|
||||
|
||||
Previous Android API devices could perform accessibility differently comparing to the latest version. We ran through accessibility testing on previous Android API (21, 24, 27, 28) devices to detect the possible differences on accessibility performance.
|
||||
Previous Android API devices could perform accessibility differently comparing to the latest version. We ran through accessibility testing on previous Android API (26, 27, 28) devices to detect the possible differences on accessibility performance.
|
||||
|
||||
#### [API 21](#tab/API21)
|
||||
```API 21
|
||||
#### [API 26](#tab/API26)
|
||||
```API 26
|
||||
When focusing on buttons, screen reader will not announce "double tap to activate".
|
||||
There is no initial focus on setup screen.
|
||||
The state/selected change for audio device select menu and video/mic/switch camera buttons may not be announced.
|
||||
|
|
|
@ -147,203 +147,203 @@ internal class LobbyTest : BaseUiTest() {
|
|||
assertViewNotDisplayed(lobbyHeaderId)
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun testOnAdmitErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Admit"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnDeclineErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Decline"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnAdmitAllErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
//
|
||||
// callingSDK.addRemoteParticipant(
|
||||
// CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
// displayName = "ACS User 2",
|
||||
// isMuted = false,
|
||||
// isSpeaking = true,
|
||||
// videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
// state = ParticipantState.IN_LOBBY
|
||||
// )
|
||||
//
|
||||
// waitUntilDisplayed(lobbyHeaderId)
|
||||
//
|
||||
// tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// // one local + one remote + 2 texts (calling, lobby)
|
||||
// assertViewHasChild(bottomDrawer, 4)
|
||||
//
|
||||
// val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
// lobbyActionResult.complete(CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED)
|
||||
// callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
//
|
||||
// // click on admit all
|
||||
// tapWithTextWhenDisplayed("Admit all")
|
||||
//
|
||||
// // to close participant list
|
||||
// tapOnScreen()
|
||||
//
|
||||
// waitUntilDisplayed(lobbyErrorHeaderId)
|
||||
//
|
||||
// // assert error text
|
||||
// assertViewText(
|
||||
// lobbyErrorHeaderText,
|
||||
// appContext!!.getString(R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded)
|
||||
// )
|
||||
//
|
||||
// // close lobby error header
|
||||
// tapWhenDisplayed(lobbyErrorHeaderCloseButton)
|
||||
//
|
||||
// // assert lobby error header is not displayed
|
||||
// assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnAdmitAllSuccess() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
// // Launch the UI.
|
||||
// joinTeamsCall()
|
||||
//
|
||||
// callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
//
|
||||
// callingSDK.addRemoteParticipant(
|
||||
// CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
// displayName = "ACS User 2",
|
||||
// isMuted = false,
|
||||
// isSpeaking = true,
|
||||
// videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
// state = ParticipantState.IN_LOBBY
|
||||
// )
|
||||
//
|
||||
// waitUntilDisplayed(lobbyHeaderId)
|
||||
//
|
||||
// tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// // one local + one remote + 2 texts (calling, lobby)
|
||||
// assertViewHasChild(bottomDrawer, 4)
|
||||
//
|
||||
// val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
// callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
//
|
||||
// // click on admit all
|
||||
// tapWithTextWhenDisplayed("Admit all")
|
||||
//
|
||||
// // to close participant list
|
||||
// tapOnScreen()
|
||||
//
|
||||
// callingSDK.changeParticipantState("ACS User 2", ParticipantState.CONNECTED)
|
||||
// lobbyActionResult.complete(null)
|
||||
//
|
||||
// // assert lobby error header is not displayed
|
||||
// assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
//
|
||||
// // tap on screen
|
||||
// tapOnScreen()
|
||||
// tapWhenDisplayed(participantListOpenButton)
|
||||
//
|
||||
// // one local + one remote + 1 texts (calling)
|
||||
// assertViewHasChild(bottomDrawer, 3)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnLobbyErrorMessageUIForRoleNotPermitted() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Admit"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(
|
||||
// buttonTextToClick, appContext,
|
||||
// CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
// R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded
|
||||
// )
|
||||
// }
|
||||
@Test
|
||||
fun testOnAdmitErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
// @Test
|
||||
// fun testOnLobbyErrorMessageUIForMeetingTypeNotSupported() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Admit"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(
|
||||
// buttonTextToClick, appContext,
|
||||
// CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
// R.string.azure_communication_ui_calling_error_lobby_conversation_type_not_supported
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnLobbyErrorMessageUIForFailedToRemove() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Admit"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(
|
||||
// buttonTextToClick, appContext,
|
||||
// CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
// R.string.azure_communication_ui_calling_error_lobby_failed_to_remove_participant
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnLobbyErrorMessageUIForUnknownError() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Admit"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(
|
||||
// buttonTextToClick, appContext,
|
||||
// CallCompositeLobbyErrorCode.UNKNOWN_ERROR,
|
||||
// R.string.azure_communication_ui_calling_error_lobby_unknown
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testOnLobbyErrorMessageUIForLobbyDisabledError() = runTest {
|
||||
// injectDependencies(testScheduler)
|
||||
//
|
||||
// val buttonTextToClick = "Admit"
|
||||
//
|
||||
// // Launch the UI.
|
||||
// val appContext = joinTeamsCall()
|
||||
//
|
||||
// lobbyButtonActionsTest(
|
||||
// buttonTextToClick, appContext,
|
||||
// CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS,
|
||||
// R.string.azure_communication_ui_calling_error_lobby_disabled_by_configuration
|
||||
// )
|
||||
// }
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnDeclineErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Decline"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(buttonTextToClick, appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitAllErrorLobbyErrorHeaderIsDisplayed() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
lobbyActionResult.complete(CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED)
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
// click on admit all
|
||||
tapWithTextWhenDisplayed("Admit all")
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
waitUntilDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// assert error text
|
||||
assertViewText(
|
||||
lobbyErrorHeaderText,
|
||||
appContext!!.getString(R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded)
|
||||
)
|
||||
|
||||
// close lobby error header
|
||||
tapWhenDisplayed(lobbyErrorHeaderCloseButton)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAdmitAllSuccess() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
// Launch the UI.
|
||||
joinTeamsCall()
|
||||
|
||||
callingSDK.setParticipantRoleSharedFlow(CallCompositeInternalParticipantRole.PRESENTER)
|
||||
|
||||
callingSDK.addRemoteParticipant(
|
||||
CommunicationIdentifier.CommunicationUserIdentifier("ACS User 2"),
|
||||
displayName = "ACS User 2",
|
||||
isMuted = false,
|
||||
isSpeaking = true,
|
||||
videoStreams = listOf(MediaStreamType.VIDEO),
|
||||
state = ParticipantState.IN_LOBBY
|
||||
)
|
||||
|
||||
waitUntilDisplayed(lobbyHeaderId)
|
||||
|
||||
tapWhenDisplayed(lobbyHeaderOpenParticipantListButton)
|
||||
// one local + one remote + 2 texts (calling, lobby)
|
||||
assertViewHasChild(bottomDrawer, 4)
|
||||
|
||||
val lobbyActionResult = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
callingSDK.setLobbyResultCompletableFuture(lobbyActionResult)
|
||||
|
||||
// click on admit all
|
||||
tapWithTextWhenDisplayed("Admit all")
|
||||
|
||||
// to close participant list
|
||||
tapOnScreen()
|
||||
|
||||
callingSDK.changeParticipantState("ACS User 2", ParticipantState.CONNECTED)
|
||||
lobbyActionResult.complete(null)
|
||||
|
||||
// assert lobby error header is not displayed
|
||||
assertViewNotDisplayed(lobbyErrorHeaderId)
|
||||
|
||||
// tap on screen
|
||||
tapOnScreen()
|
||||
tapWhenDisplayed(participantListOpenButton)
|
||||
|
||||
// one local + one remote + 1 texts (calling)
|
||||
assertViewHasChild(bottomDrawer, 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForRoleNotPermitted() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
R.string.azure_communication_ui_calling_error_lobby_meeting_role_not_allowded
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForMeetingTypeNotSupported() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
R.string.azure_communication_ui_calling_error_lobby_conversation_type_not_supported
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForFailedToRemove() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
R.string.azure_communication_ui_calling_error_lobby_failed_to_remove_participant
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForUnknownError() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.UNKNOWN_ERROR,
|
||||
R.string.azure_communication_ui_calling_error_lobby_unknown
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnLobbyErrorMessageUIForLobbyDisabledError() = runTest {
|
||||
injectDependencies(testScheduler)
|
||||
|
||||
val buttonTextToClick = "Admit"
|
||||
|
||||
// Launch the UI.
|
||||
val appContext = joinTeamsCall()
|
||||
|
||||
lobbyButtonActionsTest(
|
||||
buttonTextToClick, appContext,
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS,
|
||||
R.string.azure_communication_ui_calling_error_lobby_disabled_by_configuration
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun lobbyButtonActionsTest(
|
||||
buttonTextToClick: String,
|
||||
|
|
|
@ -60,7 +60,6 @@
|
|||
android:stopWithTask="false"
|
||||
android:exported="false"
|
||||
/>
|
||||
<!-- <TELECOM_MANAGER_SUPPORT:0> -->
|
||||
<service
|
||||
android:name="com.azure.android.communication.calling.TelecomConnectionService"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
|
@ -69,7 +68,6 @@
|
|||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<!-- </TELECOM_MANAGER_SUPPORT:0> -->
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -34,15 +34,13 @@ import com.azure.android.communication.ui.calling.models.CallCompositePictureInP
|
|||
import com.azure.android.communication.ui.calling.models.CallCompositePushNotification;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteParticipantJoinedEvent;
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRoomLocator;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantRole;
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantViewData;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSetParticipantViewDataResult;
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingIdLocator;
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent;
|
||||
import com.azure.android.communication.ui.calling.presentation.CallCompositeActivity;
|
||||
|
@ -694,57 +692,53 @@ public final class CallComposite {
|
|||
|
||||
UUID groupId = null;
|
||||
String meetingLink = null;
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
String meetingId = null;
|
||||
String meetingPasscode = null;
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
String roomId = null;
|
||||
CallCompositeParticipantRole roomRole = null;
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
final CallType callType;
|
||||
|
||||
final CallCompositeJoinLocator locator = remoteOptions.getLocator();
|
||||
if (locator instanceof CallCompositeGroupCallLocator) {
|
||||
callType = CallType.GROUP_CALL;
|
||||
groupId = ((CallCompositeGroupCallLocator) locator).getGroupId();
|
||||
/* <MEETING_ID_LOCATOR> */ } else if (locator instanceof CallCompositeTeamsMeetingIdLocator) {
|
||||
} else if (locator instanceof CallCompositeTeamsMeetingIdLocator) {
|
||||
callType = CallType.TEAMS_MEETING;
|
||||
final CallCompositeTeamsMeetingIdLocator teamsMeetingIdLocator =
|
||||
(CallCompositeTeamsMeetingIdLocator) locator;
|
||||
meetingId = teamsMeetingIdLocator.getMeetingId();
|
||||
meetingPasscode = teamsMeetingIdLocator.getMeetingPasscode();
|
||||
/* </MEETING_ID_LOCATOR> */ } else if (locator instanceof CallCompositeTeamsMeetingLinkLocator) {
|
||||
} else if (locator instanceof CallCompositeTeamsMeetingLinkLocator) {
|
||||
callType = CallType.TEAMS_MEETING;
|
||||
meetingLink = ((CallCompositeTeamsMeetingLinkLocator) locator).getMeetingLink();
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
} else if (locator instanceof CallCompositeRoomLocator) {
|
||||
callType = CallType.ROOMS_CALL;
|
||||
final CallCompositeRoomLocator roomLocator = (CallCompositeRoomLocator) locator;
|
||||
roomId = roomLocator.getRoomId();
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
} else {
|
||||
throw new CallCompositeException("Not supported Call Locator type");
|
||||
}
|
||||
|
||||
if (localOptions != null) {
|
||||
configuration.setCallCompositeLocalOptions(localOptions);
|
||||
/* <ROOMS_SUPPORT:2> */
|
||||
/* <ROOMS_SUPPORT:2>
|
||||
roomRole = localOptions.getRoleHint();
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
}
|
||||
|
||||
configuration.setCallConfig(new CallConfiguration(
|
||||
groupId,
|
||||
meetingLink,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId,
|
||||
meetingPasscode,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId,
|
||||
roomRole,
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
callType,
|
||||
null,
|
||||
null));
|
||||
|
@ -778,34 +772,32 @@ public final class CallComposite {
|
|||
|
||||
UUID groupId = null;
|
||||
String meetingLink = null;
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
String meetingId = null;
|
||||
String meetingPasscode = null;
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
String roomId = null;
|
||||
CallCompositeParticipantRole roomRole = null;
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
final CallType callType;
|
||||
|
||||
if (locator instanceof CallCompositeGroupCallLocator) {
|
||||
callType = CallType.GROUP_CALL;
|
||||
groupId = ((CallCompositeGroupCallLocator) locator).getGroupId();
|
||||
/* <MEETING_ID_LOCATOR> */ } else if (locator instanceof CallCompositeTeamsMeetingIdLocator) {
|
||||
} else if (locator instanceof CallCompositeTeamsMeetingIdLocator) {
|
||||
callType = CallType.TEAMS_MEETING;
|
||||
final CallCompositeTeamsMeetingIdLocator teamsMeetingIdLocator =
|
||||
(CallCompositeTeamsMeetingIdLocator) locator;
|
||||
meetingId = teamsMeetingIdLocator.getMeetingId();
|
||||
meetingPasscode = teamsMeetingIdLocator.getMeetingPasscode();
|
||||
/* </MEETING_ID_LOCATOR> */ } else if (locator instanceof CallCompositeTeamsMeetingLinkLocator) {
|
||||
} else if (locator instanceof CallCompositeTeamsMeetingLinkLocator) {
|
||||
callType = CallType.TEAMS_MEETING;
|
||||
meetingLink = ((CallCompositeTeamsMeetingLinkLocator) locator).getMeetingLink();
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
} else if (locator instanceof CallCompositeRoomLocator) {
|
||||
callType = CallType.ROOMS_CALL;
|
||||
final CallCompositeRoomLocator roomLocator = (CallCompositeRoomLocator) locator;
|
||||
roomId = roomLocator.getRoomId();
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
} else if (participants != null) {
|
||||
callType = CallType.ONE_TO_N_OUTGOING;
|
||||
} else if (incomingCallId != null) {
|
||||
|
@ -816,9 +808,9 @@ public final class CallComposite {
|
|||
|
||||
if (localOptions != null) {
|
||||
configuration.setCallCompositeLocalOptions(localOptions);
|
||||
/* <ROOMS_SUPPORT:2> */
|
||||
/* <ROOMS_SUPPORT:2>
|
||||
roomRole = localOptions.getRoleHint();
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
}
|
||||
initializeCallingSDK();
|
||||
|
||||
|
@ -826,14 +818,12 @@ public final class CallComposite {
|
|||
configuration.setCallConfig(new CallConfiguration(
|
||||
groupId,
|
||||
meetingLink,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId,
|
||||
meetingPasscode,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId,
|
||||
roomRole,
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
callType,
|
||||
participants,
|
||||
incomingCallId));
|
||||
|
|
|
@ -6,9 +6,9 @@ package com.azure.android.communication.ui.calling;
|
|||
import android.content.Context;
|
||||
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential;
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeAudioSelectionMode;
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallScreenOptions;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalizationOptions;
|
||||
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration;
|
||||
|
@ -36,9 +36,9 @@ public final class CallCompositeBuilder {
|
|||
private String displayName = null;
|
||||
private CommunicationTokenCredential credential = null;
|
||||
private Boolean disableInternalPushForIncomingCall = false;
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
private CallCompositeAudioSelectionMode audioSelectionMode = null;
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
|
||||
/**
|
||||
* Sets an optional theme for call-composite to use by {@link CallComposite}.
|
||||
|
@ -166,18 +166,18 @@ public final class CallCompositeBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/**
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
\**
|
||||
* Sets the audio selection mode.
|
||||
*
|
||||
* @param audioSelectionMode audio selection mode.
|
||||
* @return {@link CallCompositeBuilder} for chaining options.
|
||||
*/
|
||||
*\
|
||||
public CallCompositeBuilder audioSelectionMode(final CallCompositeAudioSelectionMode audioSelectionMode) {
|
||||
this.audioSelectionMode = audioSelectionMode;
|
||||
return this;
|
||||
}
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
|
||||
/**
|
||||
* Builds the CallCompositeClass {@link CallComposite}.
|
||||
|
@ -198,9 +198,9 @@ public final class CallCompositeBuilder {
|
|||
config.setDisplayName(displayName);
|
||||
config.setApplicationContext(applicationContext);
|
||||
config.setDisableInternalPushForIncomingCall(disableInternalPushForIncomingCall);
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
config.setAudioSelectionMode(audioSelectionMode);
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
return new CallComposite(config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ package com.azure.android.communication.ui.calling.configuration
|
|||
import android.content.Context
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.ui.calling.configuration.events.CallCompositeEventsHandler
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeAudioSelectionMode
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallScreenOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalizationOptions
|
||||
|
@ -16,9 +16,9 @@ import com.azure.android.communication.ui.calling.models.CallCompositeSupportedS
|
|||
import com.azure.android.communication.ui.calling.models.CallCompositeTelecomManagerOptions
|
||||
|
||||
internal class CallCompositeConfiguration {
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
var audioSelectionMode: CallCompositeAudioSelectionMode? = null
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
var themeConfig: Int? = null
|
||||
var localizationConfig: CallCompositeLocalizationOptions? = null
|
||||
var callCompositeEventsHandler = CallCompositeEventsHandler()
|
||||
|
|
|
@ -5,9 +5,9 @@ package com.azure.android.communication.ui.calling.configuration
|
|||
|
||||
import com.azure.android.communication.common.CommunicationIdentifier
|
||||
import com.azure.android.communication.ui.calling.DiagnosticConfig
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantRole
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
import java.util.UUID
|
||||
|
||||
internal enum class CallType {
|
||||
|
@ -15,22 +15,20 @@ internal enum class CallType {
|
|||
TEAMS_MEETING,
|
||||
ONE_TO_N_OUTGOING,
|
||||
ONE_TO_ONE_INCOMING,
|
||||
/* <ROOMS_SUPPORT:3> */
|
||||
/* <ROOMS_SUPPORT:3>
|
||||
ROOMS_CALL,
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
}
|
||||
|
||||
internal data class CallConfiguration(
|
||||
val groupId: UUID?,
|
||||
val meetingLink: String?,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
val meetingId: String?,
|
||||
val meetingPasscode: String?,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
val roomId: String?,
|
||||
val roomRoleHint: CallCompositeParticipantRole?,
|
||||
/* </ROOMS_SUPPORT:4> */
|
||||
</ROOMS_SUPPORT:4> */
|
||||
val callType: CallType,
|
||||
val participants: Collection<CommunicationIdentifier>? = null,
|
||||
val incomingCallId: String? = null,
|
||||
|
|
|
@ -123,9 +123,9 @@ internal class DependencyInjectionContainerImpl(
|
|||
AudioSessionManager(
|
||||
appStore,
|
||||
applicationContext,
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
configuration.audioSelectionMode
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -33,9 +33,9 @@ import com.azure.android.communication.ui.calling.CallComposite;
|
|||
public final class CallCompositeLocalOptions {
|
||||
private CallCompositeParticipantViewData participantViewData = null;
|
||||
private CallCompositeSetupScreenViewData setupScreenViewData = null;
|
||||
/* <ROOMS_SUPPORT:3> */
|
||||
/* <ROOMS_SUPPORT:3>
|
||||
private CallCompositeParticipantRole roleHint = null;
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
private boolean cameraOn = false;
|
||||
private boolean microphoneOn = false;
|
||||
private boolean skipSetupScreen = false;
|
||||
|
@ -99,11 +99,11 @@ public final class CallCompositeLocalOptions {
|
|||
* Get role hint.
|
||||
* @return {@link CallCompositeParticipantRole}
|
||||
*/
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
public CallCompositeParticipantRole getRoleHint() {
|
||||
return roleHint;
|
||||
}
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
|
||||
/**
|
||||
* Get role hint. Use this to hint the role of the user when the role is not available before a Rooms
|
||||
|
@ -113,12 +113,12 @@ public final class CallCompositeLocalOptions {
|
|||
* The true role of the user will be synced with ACS services when a Rooms call starts.
|
||||
* @return The current {@link CallCompositeLocalOptions} object for Fluent use.
|
||||
*/
|
||||
/* <ROOMS_SUPPORT:8> */
|
||||
/* <ROOMS_SUPPORT:8>
|
||||
public CallCompositeLocalOptions setRoleHint(final CallCompositeParticipantRole roleHint) {
|
||||
this.roleHint = roleHint;
|
||||
return this;
|
||||
}
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
|
||||
/**
|
||||
* Get the boolean value for skip setup screen.
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
package com.azure.android.communication.ui.calling.models;
|
||||
|
||||
import com.azure.android.core.util.ExpandableStringEnum;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
\**
|
||||
* Defines values for {@linkCallCompositeParticipantRole}.
|
||||
*/
|
||||
*\
|
||||
public final class CallCompositeParticipantRole extends ExpandableStringEnum<CallCompositeParticipantRole> {
|
||||
|
||||
/**
|
||||
\**
|
||||
* Presenter Role in the Room call.
|
||||
*/
|
||||
*\
|
||||
public static final CallCompositeParticipantRole PRESENTER = fromString("Presenter");
|
||||
|
||||
/**
|
||||
\**
|
||||
* Attendee Role in the Room call.
|
||||
*/
|
||||
*\
|
||||
public static final CallCompositeParticipantRole ATTENDEE = fromString("Attendee");
|
||||
|
||||
/**
|
||||
\**
|
||||
* Creates instance of {@linkCallCompositeParticipantRole}.
|
||||
*/
|
||||
*\
|
||||
public CallCompositeParticipantRole() { }
|
||||
|
||||
/**
|
||||
\**
|
||||
* Creates or finds a {@linkCallCompositeParticipantRole} from it's string representation.
|
||||
*
|
||||
* @param name a name to look for.
|
||||
* @return the corresponding {@linkCallCompositeParticipantRole}.
|
||||
*/
|
||||
*\
|
||||
public static CallCompositeParticipantRole fromString(final String name) {
|
||||
return fromString(name, CallCompositeParticipantRole.class);
|
||||
}
|
||||
|
||||
/**
|
||||
\**
|
||||
* @return known {@linkCallCompositeParticipantRole} values.
|
||||
*/
|
||||
*\
|
||||
public static Collection<CallCompositeParticipantRole> values() {
|
||||
return values(CallCompositeParticipantRole.class);
|
||||
}
|
||||
}
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
|
||||
package com.azure.android.communication.ui.calling.models;
|
||||
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/**
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
\**
|
||||
* Room Call locator to start Room call experience using
|
||||
* {@link com.azure.android.communication.ui.calling.CallComposite}.
|
||||
*
|
||||
* You need to use LocalOptions parameter for
|
||||
* CallComposite.launch() method with roleHint provided.
|
||||
*/
|
||||
*\
|
||||
public final class CallCompositeRoomLocator extends CallCompositeJoinLocator {
|
||||
private final String roomId;
|
||||
|
||||
/**
|
||||
\**
|
||||
* Creates {@link CallCompositeRoomLocator}.
|
||||
* @param roomId Room identifier.
|
||||
*/
|
||||
*\
|
||||
public CallCompositeRoomLocator(final String roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
/**
|
||||
\**
|
||||
* Get room id.
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
*\
|
||||
public String getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
}
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
package com.azure.android.communication.ui.calling.models;
|
||||
|
||||
import com.azure.android.communication.ui.calling.CallComposite;
|
||||
|
@ -42,4 +41,3 @@ public final class CallCompositeTeamsMeetingIdLocator extends CallCompositeJoinL
|
|||
return meetingPasscode;
|
||||
}
|
||||
}
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
|
|
|
@ -326,15 +326,11 @@ internal class ParticipantListView(
|
|||
participantViewData,
|
||||
isOnHold,
|
||||
onClickAction = {
|
||||
if (accessibilityManager.isEnabled) {
|
||||
if (status == ParticipantStatus.IN_LOBBY) {
|
||||
showAdmitDialog(displayName, userIdentifier)
|
||||
} else if (accessibilityManager.isEnabled) {
|
||||
participantListDrawer.dismiss()
|
||||
}
|
||||
// TODO: uncomment when admit all button is supported
|
||||
// if (status == ParticipantStatus.IN_LOBBY) {
|
||||
// showAdmitDialog(displayName, userIdentifier)
|
||||
// } else if (accessibilityManager.isEnabled) {
|
||||
// participantListDrawer.dismiss()
|
||||
// }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -27,9 +27,9 @@ import androidx.annotation.RequiresApi
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.azure.android.communication.ui.calling.CallCompositeException
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeAudioSelectionMode
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.IllegalArgumentException
|
||||
import com.azure.android.communication.ui.calling.redux.state.PermissionStatus
|
||||
|
@ -37,9 +37,9 @@ import com.azure.android.communication.ui.calling.redux.state.PermissionStatus
|
|||
internal class AudioSessionManager(
|
||||
private val store: Store<ReduxState>,
|
||||
private val context: Context,
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
private val audioSelectionMode: CallCompositeAudioSelectionMode? = null,
|
||||
/* </DEFAULT_AUDIO_MODE:0> */
|
||||
</DEFAULT_AUDIO_MODE:0> */
|
||||
|
||||
) : BluetoothProfile.ServiceListener, BroadcastReceiver() {
|
||||
|
||||
|
@ -230,7 +230,7 @@ internal class AudioSessionManager(
|
|||
if (initialized) return
|
||||
initialized = true
|
||||
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
if (audioSelectionMode == CallCompositeAudioSelectionMode.RECEIVER) {
|
||||
enableEarpiece()
|
||||
store.dispatch(
|
||||
|
@ -241,13 +241,13 @@ internal class AudioSessionManager(
|
|||
store.dispatch(
|
||||
LocalParticipantAction.AudioDeviceChangeSucceeded(AudioDeviceSelectionStatus.BLUETOOTH_SCO_SELECTED)
|
||||
)
|
||||
} else { /* </DEFAULT_AUDIO_MODE:0> */
|
||||
enableSpeakerPhone()
|
||||
store.dispatch(
|
||||
LocalParticipantAction.AudioDeviceChangeSucceeded(AudioDeviceSelectionStatus.SPEAKER_SELECTED)
|
||||
)
|
||||
/* <DEFAULT_AUDIO_MODE:0> */
|
||||
} /* </DEFAULT_AUDIO_MODE:0> */
|
||||
} else { </DEFAULT_AUDIO_MODE:0> */
|
||||
enableSpeakerPhone()
|
||||
store.dispatch(
|
||||
LocalParticipantAction.AudioDeviceChangeSucceeded(AudioDeviceSelectionStatus.SPEAKER_SELECTED)
|
||||
)
|
||||
/* <DEFAULT_AUDIO_MODE:0>
|
||||
} </DEFAULT_AUDIO_MODE:0> */
|
||||
|
||||
updateHeadphoneStatus()
|
||||
}
|
||||
|
|
|
@ -11,17 +11,13 @@ import com.azure.android.communication.calling.IncomingCall
|
|||
import com.azure.android.communication.calling.IncomingCallListener
|
||||
import com.azure.android.communication.calling.PropertyChangedListener
|
||||
import com.azure.android.communication.calling.PushNotificationInfo
|
||||
/* <TELECOM_MANAGER_SUPPORT:0> */
|
||||
import com.azure.android.communication.calling.TelecomManagerOptions
|
||||
/* </TELECOM_MANAGER_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.calling.CallCompositeException
|
||||
import com.azure.android.communication.ui.calling.DiagnosticConfig
|
||||
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration
|
||||
import com.azure.android.communication.ui.calling.logger.Logger
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositePushNotification
|
||||
/* <TELECOM_MANAGER_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTelecomManagerIntegrationMode
|
||||
/* </TELECOM_MANAGER_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.calling.models.buildCallCompositeIncomingCallCancelledEvent
|
||||
import com.azure.android.communication.ui.calling.models.buildCallCompositeIncomingCallEvent
|
||||
import com.azure.android.communication.ui.calling.service.sdk.ext.setTags
|
||||
|
@ -107,14 +103,12 @@ internal class CallingSDKInitializer(
|
|||
if (callAgentCompletableFuture == null || callAgentCompletableFuture!!.isCompletedExceptionally) {
|
||||
callAgentCompletableFuture = CompletableFuture<CallAgent>()
|
||||
val options = CallAgentOptions().apply { displayName = callCompositeConfiguration.displayName }
|
||||
/* <TELECOM_MANAGER_SUPPORT:0> */
|
||||
callCompositeConfiguration.telecomManagerOptions?.let {
|
||||
if (it.telecomManagerIntegrationMode == CallCompositeTelecomManagerIntegrationMode.SDK_PROVIDED_TELECOM_MANAGER) {
|
||||
options.telecomManagerOptions = TelecomManagerOptions(it.phoneAccountId)
|
||||
}
|
||||
}
|
||||
options.setDisableInternalPushForIncomingCall(callCompositeConfiguration.disableInternalPushForIncomingCall)
|
||||
/* </TELECOM_MANAGER_SUPPORT:0> */
|
||||
|
||||
try {
|
||||
setupCallClient()?.whenComplete { callClient, callAgentError ->
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.azure.android.communication.calling.AcceptCallOptions
|
|||
import com.azure.android.communication.calling.Call
|
||||
import com.azure.android.communication.calling.CallAgent
|
||||
import com.azure.android.communication.calling.CallClient
|
||||
import com.azure.android.communication.calling.CallingCommunicationException
|
||||
import com.azure.android.communication.calling.CameraFacing
|
||||
import com.azure.android.communication.calling.DeviceManager
|
||||
import com.azure.android.communication.calling.GroupCallLocator
|
||||
|
@ -17,13 +18,11 @@ import com.azure.android.communication.calling.JoinCallOptions
|
|||
import com.azure.android.communication.calling.JoinMeetingLocator
|
||||
import com.azure.android.communication.calling.OutgoingAudioOptions
|
||||
import com.azure.android.communication.calling.OutgoingVideoOptions
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
import com.azure.android.communication.calling.RoomCallLocator
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
import com.azure.android.communication.calling.StartCallOptions
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
import com.azure.android.communication.calling.TeamsMeetingIdLocator
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
import com.azure.android.communication.calling.TeamsMeetingLinkLocator
|
||||
import com.azure.android.communication.calling.VideoDevicesUpdatedListener
|
||||
import com.azure.android.communication.ui.calling.CallCompositeException
|
||||
|
@ -184,17 +183,17 @@ internal class CallingSDKWrapper(
|
|||
override fun admitAll(): CompletableFuture<CallCompositeLobbyErrorCode?> {
|
||||
val future = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
if (lobbyNullCheck(future)) return future
|
||||
// nullableCall?.callLobby?.admitAll()?.whenComplete { _, error ->
|
||||
// if (error != null) {
|
||||
// var errorCode = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
// if (error.cause is CallingCommunicationException) {
|
||||
// errorCode = getLobbyErrorCode(error.cause as CallingCommunicationException)
|
||||
// }
|
||||
// future.complete(errorCode)
|
||||
// } else {
|
||||
// future.complete(null)
|
||||
// }
|
||||
// }
|
||||
nullableCall?.callLobby?.admitAll()?.whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
var errorCode = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
if (error.cause is CallingCommunicationException) {
|
||||
errorCode = getLobbyErrorCode(error.cause as CallingCommunicationException)
|
||||
}
|
||||
future.complete(errorCode)
|
||||
} else {
|
||||
future.complete(null)
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
|
@ -202,27 +201,27 @@ internal class CallingSDKWrapper(
|
|||
val future = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
if (lobbyNullCheck(future)) return future
|
||||
val participant = nullableCall?.remoteParticipants?.find { it.identifier.rawId.equals(userIdentifier) }
|
||||
// participant?.let {
|
||||
// nullableCall?.callLobby?.admit(listOf(it.identifier))?.whenComplete { _, error ->
|
||||
// if (error != null) {
|
||||
// var errorCode = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
// if (error.cause is CallingCommunicationException) {
|
||||
// errorCode = getLobbyErrorCode(error.cause as CallingCommunicationException)
|
||||
// }
|
||||
// future.complete(errorCode)
|
||||
// } else {
|
||||
// future.complete(null)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
participant?.let {
|
||||
nullableCall?.callLobby?.admit(listOf(it.identifier))?.whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
var errorCode = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
if (error.cause is CallingCommunicationException) {
|
||||
errorCode = getLobbyErrorCode(error.cause as CallingCommunicationException)
|
||||
}
|
||||
future.complete(errorCode)
|
||||
} else {
|
||||
future.complete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
private fun lobbyNullCheck(future: CompletableFuture<CallCompositeLobbyErrorCode?>): Boolean {
|
||||
// if (nullableCall == null || nullableCall?.callLobby == null) {
|
||||
// future.complete(CallCompositeLobbyErrorCode.UNKNOWN_ERROR)
|
||||
// return true
|
||||
// }
|
||||
if (nullableCall == null || nullableCall?.callLobby == null) {
|
||||
future.complete(CallCompositeLobbyErrorCode.UNKNOWN_ERROR)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -230,20 +229,20 @@ internal class CallingSDKWrapper(
|
|||
val future = CompletableFuture<CallCompositeLobbyErrorCode?>()
|
||||
if (lobbyNullCheck(future)) return future
|
||||
val participant = nullableCall?.remoteParticipants?.find { it.identifier.rawId.equals(userIdentifier) }
|
||||
// participant?.let {
|
||||
// nullableCall?.callLobby?.reject(it.identifier)
|
||||
// ?.whenComplete { _, error ->
|
||||
// if (error != null) {
|
||||
// var errorCode = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
// if (error.cause is CallingCommunicationException) {
|
||||
// errorCode = getLobbyErrorCode(error.cause as CallingCommunicationException)
|
||||
// }
|
||||
// future.complete(errorCode)
|
||||
// } else {
|
||||
// future.complete(null)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
participant?.let {
|
||||
nullableCall?.callLobby?.reject(it.identifier)
|
||||
?.whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
var errorCode = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
if (error.cause is CallingCommunicationException) {
|
||||
errorCode = getLobbyErrorCode(error.cause as CallingCommunicationException)
|
||||
}
|
||||
future.complete(errorCode)
|
||||
} else {
|
||||
future.complete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
|
@ -402,9 +401,7 @@ internal class CallingSDKWrapper(
|
|||
|
||||
override fun setTelecomManagerAudioRoute(audioRoute: Int) {
|
||||
if (nullableCall != null) {
|
||||
/* <TELECOM_MANAGER_SUPPORT:0> */
|
||||
call.setTelecomManagerAudioRoute(audioRoute)
|
||||
/* </TELECOM_MANAGER_SUPPORT:0> */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,18 +448,18 @@ internal class CallingSDKWrapper(
|
|||
CallType.TEAMS_MEETING -> {
|
||||
if (!callConfig.meetingLink.isNullOrEmpty()) {
|
||||
TeamsMeetingLinkLocator(callConfig.meetingLink)
|
||||
} /* <MEETING_ID_LOCATOR> */ else if (!callConfig.meetingId.isNullOrEmpty() && !callConfig.meetingPasscode.isNullOrEmpty()) {
|
||||
} else if (!callConfig.meetingId.isNullOrEmpty() && !callConfig.meetingPasscode.isNullOrEmpty()) {
|
||||
TeamsMeetingIdLocator(callConfig.meetingId, callConfig.meetingPasscode)
|
||||
} /* </MEETING_ID_LOCATOR> */ else {
|
||||
} else {
|
||||
throw CallCompositeException(
|
||||
"Teams Meeting information is incomplete",
|
||||
IllegalStateException()
|
||||
)
|
||||
}
|
||||
}
|
||||
/* <ROOMS_SUPPORT:3> */
|
||||
/* <ROOMS_SUPPORT:3>
|
||||
CallType.ROOMS_CALL -> RoomCallLocator(callConfig.roomId)
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
else -> {
|
||||
throw CallCompositeException(
|
||||
"Unsupported call type",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package com.azure.android.communication.ui.calling.service.sdk
|
||||
|
||||
import com.azure.android.communication.calling.CallingCommunicationErrors
|
||||
import com.azure.android.communication.calling.CallingCommunicationException
|
||||
import com.azure.android.communication.calling.ParticipantState
|
||||
import com.azure.android.communication.common.CommunicationUserIdentifier
|
||||
|
@ -77,30 +78,30 @@ internal fun com.azure.android.communication.calling.CallParticipantRole.into():
|
|||
com.azure.android.communication.calling.CallParticipantRole.CONSUMER -> CallCompositeInternalParticipantRole.CONSUMER
|
||||
com.azure.android.communication.calling.CallParticipantRole.PRESENTER -> CallCompositeInternalParticipantRole.PRESENTER
|
||||
com.azure.android.communication.calling.CallParticipantRole.ORGANIZER -> CallCompositeInternalParticipantRole.ORGANIZER
|
||||
// com.azure.android.communication.calling.CallParticipantRole.CO_ORGANIZER -> CallCompositeInternalParticipantRole.COORGANIZER
|
||||
com.azure.android.communication.calling.CallParticipantRole.CO_ORGANIZER -> CallCompositeInternalParticipantRole.COORGANIZER
|
||||
com.azure.android.communication.calling.CallParticipantRole.UNINITIALIZED -> CallCompositeInternalParticipantRole.UNINITIALIZED
|
||||
else -> { null }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getLobbyErrorCode(error: CallingCommunicationException) = CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
// when (error.errorCode) {
|
||||
// CallingCommunicationErrors.LOBBY_DISABLED_BY_CONFIGURATIONS -> {
|
||||
// CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS
|
||||
// }
|
||||
//
|
||||
// CallingCommunicationErrors.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED -> {
|
||||
// CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED
|
||||
// }
|
||||
//
|
||||
// CallingCommunicationErrors.LOBBY_MEETING_ROLE_NOT_ALLOWED -> {
|
||||
// CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED
|
||||
// }
|
||||
//
|
||||
// CallingCommunicationErrors.REMOVE_PARTICIPANT_OPERATION_FAILURE -> {
|
||||
// CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE
|
||||
// }
|
||||
// else -> {
|
||||
// CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
// }
|
||||
// }
|
||||
internal fun getLobbyErrorCode(error: CallingCommunicationException) =
|
||||
when (error.errorCode) {
|
||||
CallingCommunicationErrors.LOBBY_DISABLED_BY_CONFIGURATIONS -> {
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS
|
||||
}
|
||||
|
||||
CallingCommunicationErrors.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED -> {
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
CallingCommunicationErrors.LOBBY_MEETING_ROLE_NOT_ALLOWED -> {
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED
|
||||
}
|
||||
|
||||
CallingCommunicationErrors.REMOVE_PARTICIPANT_OPERATION_FAILURE -> {
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE
|
||||
}
|
||||
else -> {
|
||||
CallCompositeLobbyErrorCode.UNKNOWN_ERROR
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,7 @@ internal open class BottomCellViewHolder(itemView: View) : RecyclerView.ViewHold
|
|||
itemView.setOnClickListener(bottomCellItem.onClickAction)
|
||||
itemView.isClickable = bottomCellItem.onClickAction != null
|
||||
|
||||
// TODO: enable admit all button when it is supported
|
||||
admitAllButton?.visibility = View.GONE
|
||||
// admitAllButton?.visibility = if (bottomCellItem.showAdmitAllButton) View.VISIBLE else View.GONE
|
||||
admitAllButton?.visibility = if (bottomCellItem.showAdmitAllButton) View.VISIBLE else View.GONE
|
||||
admitAllButton?.setOnClickListener(bottomCellItem.admitAllButtonAction)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2072,14 +2072,12 @@ internal class CallingMiddlewareActionHandlerUnitTest : ACSBaseTestCoroutine() {
|
|||
configuration.callConfig = CallConfiguration(
|
||||
groupId = null,
|
||||
meetingLink = null,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId = null,
|
||||
meetingPasscode = null,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
roomId = null,
|
||||
roomRoleHint = null,
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
callType = CallType.ONE_TO_N_OUTGOING
|
||||
)
|
||||
val handler = CallingMiddlewareActionHandlerImpl(
|
||||
|
@ -2150,14 +2148,12 @@ internal class CallingMiddlewareActionHandlerUnitTest : ACSBaseTestCoroutine() {
|
|||
configuration.callConfig = CallConfiguration(
|
||||
groupId = null,
|
||||
meetingLink = null,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId = null,
|
||||
meetingPasscode = null,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
roomId = null,
|
||||
roomRoleHint = null,
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
callType = CallType.ONE_TO_N_OUTGOING
|
||||
)
|
||||
val handler = CallingMiddlewareActionHandlerImpl(
|
||||
|
@ -2228,14 +2224,12 @@ internal class CallingMiddlewareActionHandlerUnitTest : ACSBaseTestCoroutine() {
|
|||
configuration.callConfig = CallConfiguration(
|
||||
groupId = null,
|
||||
meetingLink = null,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId = null,
|
||||
meetingPasscode = null,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
roomId = null,
|
||||
roomRoleHint = null,
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
callType = CallType.ONE_TO_N_OUTGOING
|
||||
)
|
||||
val handler = CallingMiddlewareActionHandlerImpl(
|
||||
|
|
|
@ -5,6 +5,8 @@ package com.azure.android.communication.ui.calling.service.calling
|
|||
|
||||
import com.azure.android.communication.calling.CallParticipantRole
|
||||
import com.azure.android.communication.calling.CallState
|
||||
import com.azure.android.communication.calling.CallingCommunicationErrors
|
||||
import com.azure.android.communication.calling.CallingCommunicationException
|
||||
import com.azure.android.communication.ui.calling.service.CallingService
|
||||
import com.azure.android.communication.ui.calling.error.ErrorCode
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeEventCode
|
||||
|
@ -34,6 +36,7 @@ import com.azure.android.communication.ui.calling.service.sdk.CallingStateWrappe
|
|||
import com.azure.android.communication.ui.calling.service.sdk.CallingStateWrapper.Companion.CALL_END_REASON_SUB_CODE_DECLINED
|
||||
import com.azure.android.communication.ui.calling.service.sdk.DominantSpeakersInfo
|
||||
import com.azure.android.communication.ui.calling.service.sdk.LocalVideoStream
|
||||
import com.azure.android.communication.ui.calling.service.sdk.getLobbyErrorCode
|
||||
import com.azure.android.communication.ui.calling.service.sdk.into
|
||||
|
||||
import java9.util.concurrent.CompletableFuture
|
||||
|
@ -706,47 +709,47 @@ internal class CallingServiceUnitTests : ACSBaseTestCoroutine() {
|
|||
fun typeConversion_testRoleConversion() {
|
||||
// assert
|
||||
Assert.assertEquals(CallCompositeInternalParticipantRole.UNINITIALIZED, CallParticipantRole.UNINITIALIZED.into())
|
||||
// Assert.assertEquals(CallCompositeInternalParticipantRole.COORGANIZER, CallParticipantRole.CO_ORGANIZER.into())
|
||||
Assert.assertEquals(CallCompositeInternalParticipantRole.COORGANIZER, CallParticipantRole.CO_ORGANIZER.into())
|
||||
Assert.assertEquals(CallCompositeInternalParticipantRole.ORGANIZER, CallParticipantRole.ORGANIZER.into())
|
||||
Assert.assertEquals(CallCompositeInternalParticipantRole.PRESENTER, CallParticipantRole.PRESENTER.into())
|
||||
Assert.assertEquals(CallCompositeInternalParticipantRole.CONSUMER, CallParticipantRole.CONSUMER.into())
|
||||
Assert.assertEquals(CallCompositeInternalParticipantRole.ATTENDEE, CallParticipantRole.ATTENDEE.into())
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun typeConversion_lobbyErrorCodeConversion() {
|
||||
// // assert
|
||||
// Assert.assertEquals(
|
||||
// CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS,
|
||||
// getLobbyErrorCode(
|
||||
// CallingCommunicationException(CallingCommunicationErrors.LOBBY_DISABLED_BY_CONFIGURATIONS)
|
||||
// )
|
||||
// )
|
||||
// Assert.assertEquals(
|
||||
// CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
// getLobbyErrorCode(
|
||||
// CallingCommunicationException(CallingCommunicationErrors.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED)
|
||||
// )
|
||||
// )
|
||||
// Assert.assertEquals(
|
||||
// CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
// getLobbyErrorCode(
|
||||
// CallingCommunicationException(CallingCommunicationErrors.LOBBY_MEETING_ROLE_NOT_ALLOWED)
|
||||
// )
|
||||
// )
|
||||
// Assert.assertEquals(
|
||||
// CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
// getLobbyErrorCode(
|
||||
// CallingCommunicationException(CallingCommunicationErrors.REMOVE_PARTICIPANT_OPERATION_FAILURE)
|
||||
// )
|
||||
// )
|
||||
// Assert.assertEquals(
|
||||
// CallCompositeLobbyErrorCode.UNKNOWN_ERROR,
|
||||
// getLobbyErrorCode(
|
||||
// CallingCommunicationException(CallingCommunicationErrors.CAPTIONS_FAILED_TO_START)
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
@Test
|
||||
fun typeConversion_lobbyErrorCodeConversion() {
|
||||
// assert
|
||||
Assert.assertEquals(
|
||||
CallCompositeLobbyErrorCode.LOBBY_DISABLED_BY_CONFIGURATIONS,
|
||||
getLobbyErrorCode(
|
||||
CallingCommunicationException(CallingCommunicationErrors.LOBBY_DISABLED_BY_CONFIGURATIONS)
|
||||
)
|
||||
)
|
||||
Assert.assertEquals(
|
||||
CallCompositeLobbyErrorCode.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED,
|
||||
getLobbyErrorCode(
|
||||
CallingCommunicationException(CallingCommunicationErrors.LOBBY_CONVERSATION_TYPE_NOT_SUPPORTED)
|
||||
)
|
||||
)
|
||||
Assert.assertEquals(
|
||||
CallCompositeLobbyErrorCode.LOBBY_MEETING_ROLE_NOT_ALLOWED,
|
||||
getLobbyErrorCode(
|
||||
CallingCommunicationException(CallingCommunicationErrors.LOBBY_MEETING_ROLE_NOT_ALLOWED)
|
||||
)
|
||||
)
|
||||
Assert.assertEquals(
|
||||
CallCompositeLobbyErrorCode.REMOVE_PARTICIPANT_OPERATION_FAILURE,
|
||||
getLobbyErrorCode(
|
||||
CallingCommunicationException(CallingCommunicationErrors.REMOVE_PARTICIPANT_OPERATION_FAILURE)
|
||||
)
|
||||
)
|
||||
Assert.assertEquals(
|
||||
CallCompositeLobbyErrorCode.UNKNOWN_ERROR,
|
||||
getLobbyErrorCode(
|
||||
CallingCommunicationException(CallingCommunicationErrors.CAPTIONS_FAILED_TO_START)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// admitAll test
|
||||
@ExperimentalCoroutinesApi
|
||||
|
|
|
@ -33,7 +33,14 @@ library. Showcases use of both Java and Kotlin to run library.
|
|||
- `END_POINT_URL`="..." # the URL for chat end point
|
||||
- `IDENTITY`="..." # the identity for chat
|
||||
- `THREAD_ID`="..." # chat thread id
|
||||
- `PARTICIPANT_MRIS`="..." # the MRIs for remote participants to dial
|
||||
4. For build variants:
|
||||
You can add variants selection for build to indicate which demo app you want to run or debug (
|
||||
calling, chat, call-with-chat)
|
||||
5. Build and Run
|
||||
|
||||
### 1 to N Calling Push Notifications Setup
|
||||
|
||||
1. Follow [QuickStart](https://learn.microsoft.com/en-us/azure/communication-services/how-tos/ui-library-sdk/one-to-one-calling?tabs=kotlin&pivots=platform-android) to setup Push Notification Hub for ACS Resource or EventGrid
|
||||
2. For `demo-app` add `google-service.json`
|
||||
3. In `local.properties` set `ENABLE_GOOGLE_SERVICES=true`
|
||||
|
|
|
@ -36,19 +36,17 @@ import com.azure.android.communication.ui.calling.models.CallCompositeLeaveCallC
|
|||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeLocalizationOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeMultitaskingOptions
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantRole
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantViewData
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositePushNotification
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRemoteOptions
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeRoomLocator
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSetupScreenViewData
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingIdLocator
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTelecomManagerIntegrationMode
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTelecomManagerOptions
|
||||
|
@ -69,14 +67,12 @@ class CallCompositeManager(private val context: Context) {
|
|||
acsToken: String,
|
||||
displayName: String,
|
||||
groupId: UUID?,
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId: String?,
|
||||
roomRoleHint: CallCompositeParticipantRole?,
|
||||
/* </ROOMS_SUPPORT:2> */
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
</ROOMS_SUPPORT:2> */
|
||||
meetingLink: String?,
|
||||
meetingId: String?,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
meetingPasscode: String?,
|
||||
participantMris: String?,
|
||||
) {
|
||||
|
@ -106,28 +102,24 @@ class CallCompositeManager(private val context: Context) {
|
|||
val remoteOptions = getRemoteOptions(
|
||||
acsToken,
|
||||
groupId,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingLink,
|
||||
meetingId,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
meetingPasscode,
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId,
|
||||
roomRoleHint,
|
||||
/* </ROOMS_SUPPORT:2> */
|
||||
</ROOMS_SUPPORT:2> */
|
||||
displayName,
|
||||
)
|
||||
val locator = getLocator(
|
||||
groupId,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingLink,
|
||||
meetingId,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
meetingPasscode,
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId,
|
||||
roomRoleHint,
|
||||
/* </ROOMS_SUPPORT:2> */
|
||||
</ROOMS_SUPPORT:2> */
|
||||
)
|
||||
|
||||
if (localOptions == null) {
|
||||
|
@ -150,14 +142,12 @@ class CallCompositeManager(private val context: Context) {
|
|||
acsToken: String,
|
||||
groupId: UUID?,
|
||||
meetingLink: String?,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId: String?,
|
||||
meetingPasscode: String?,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId: String?,
|
||||
roomRoleHint: CallCompositeParticipantRole?,
|
||||
/* </ROOMS_SUPPORT:2> */
|
||||
</ROOMS_SUPPORT:2> */
|
||||
displayName: String,
|
||||
): CallCompositeRemoteOptions {
|
||||
val communicationTokenRefreshOptions =
|
||||
|
@ -170,12 +160,10 @@ class CallCompositeManager(private val context: Context) {
|
|||
groupId != null -> CallCompositeGroupCallLocator(groupId)
|
||||
|
||||
!meetingLink.isNullOrEmpty() -> CallCompositeTeamsMeetingLinkLocator(meetingLink)
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
!meetingId.isNullOrEmpty() -> CallCompositeTeamsMeetingIdLocator(meetingId, meetingPasscode)
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
roomId != null && roomRoleHint != null -> CallCompositeRoomLocator(roomId)
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
else -> throw IllegalArgumentException("Cannot launch call composite with provided arguments.")
|
||||
}
|
||||
|
||||
|
@ -185,25 +173,21 @@ class CallCompositeManager(private val context: Context) {
|
|||
private fun getLocator(
|
||||
groupId: UUID?,
|
||||
meetingLink: String?,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId: String?,
|
||||
meetingPasscode: String?,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
roomId: String?,
|
||||
roomRoleHint: CallCompositeParticipantRole?,
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
): CallCompositeJoinLocator {
|
||||
val locator: CallCompositeJoinLocator =
|
||||
when {
|
||||
groupId != null -> CallCompositeGroupCallLocator(groupId)
|
||||
!meetingLink.isNullOrEmpty() -> CallCompositeTeamsMeetingLinkLocator(meetingLink)
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
!meetingId.isNullOrEmpty() -> CallCompositeTeamsMeetingIdLocator(meetingId, meetingPasscode)
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
roomId != null && roomRoleHint != null -> CallCompositeRoomLocator(roomId)
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
else -> throw IllegalArgumentException("Cannot launch call composite with provided arguments.")
|
||||
}
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeParticipantRole
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.databinding.ActivityCallLauncherBinding
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.AdditionalFeatures
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.FeatureFlags
|
||||
|
@ -123,21 +123,21 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
groupIdOrTeamsMeetingLinkText.setText(deeplinkGroupId)
|
||||
groupCallRadioButton.isChecked = true
|
||||
teamsMeetingRadioButton.isChecked = false
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
roomsMeetingRadioButton.isChecked = false
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
} else if (!deeplinkTeamsUrl.isNullOrEmpty()) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(deeplinkTeamsUrl)
|
||||
groupCallRadioButton.isChecked = false
|
||||
teamsMeetingRadioButton.isChecked = true
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
roomsMeetingRadioButton.isChecked = false
|
||||
} else if (!deepLinkRoomsId.isNullOrEmpty()) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(deepLinkRoomsId)
|
||||
groupCallRadioButton.isChecked = false
|
||||
teamsMeetingRadioButton.isChecked = false
|
||||
roomsMeetingRadioButton.isChecked = true
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
} else if (!participantMRIs.isNullOrEmpty()) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(participantMRIs)
|
||||
} else {
|
||||
|
@ -174,16 +174,15 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
if (groupCallRadioButton.isChecked) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(BuildConfig.GROUP_CALL_ID)
|
||||
teamsMeetingRadioButton.isChecked = false
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
oneToNCallRadioButton.isChecked = false
|
||||
teamsMeetingPasscode.visibility = View.GONE
|
||||
teamsMeetingId.visibility = View.GONE
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
roomsMeetingRadioButton.isChecked = false
|
||||
oneToNCallRadioButton.isChecked = false
|
||||
attendeeRoleRadioButton.visibility = View.GONE
|
||||
presenterRoleRadioButton.visibility = View.GONE
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
}
|
||||
}
|
||||
teamsMeetingRadioButton.setOnClickListener {
|
||||
|
@ -191,18 +190,16 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
groupIdOrTeamsMeetingLinkText.setText(BuildConfig.TEAMS_MEETING_LINK)
|
||||
groupCallRadioButton.isChecked = false
|
||||
oneToNCallRadioButton.isChecked = false
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
teamsMeetingPasscode.visibility = View.VISIBLE
|
||||
teamsMeetingId.visibility = View.VISIBLE
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
roomsMeetingRadioButton.isChecked = false
|
||||
attendeeRoleRadioButton.visibility = View.GONE
|
||||
presenterRoleRadioButton.visibility = View.GONE
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
}
|
||||
}
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
roomsMeetingRadioButton.setOnClickListener {
|
||||
if (roomsMeetingRadioButton.isChecked) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(BuildConfig.ROOMS_ID)
|
||||
|
@ -212,10 +209,8 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
groupCallRadioButton.isChecked = false
|
||||
oneToNCallRadioButton.isChecked = false
|
||||
teamsMeetingRadioButton.isChecked = false
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
teamsMeetingPasscode.visibility = View.GONE
|
||||
teamsMeetingId.visibility = View.GONE
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
} else {
|
||||
presenterRoleRadioButton.visibility = View.GONE
|
||||
attendeeRoleRadioButton.visibility = View.GONE
|
||||
|
@ -233,18 +228,20 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
presenterRoleRadioButton.isChecked = false
|
||||
}
|
||||
}
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
</ROOMS_SUPPORT:0> */
|
||||
|
||||
oneToNCallRadioButton.setOnClickListener {
|
||||
if (oneToNCallRadioButton.isChecked) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(BuildConfig.PARTICIPANT_MRIS)
|
||||
groupCallRadioButton.isChecked = false
|
||||
teamsMeetingRadioButton.isChecked = false
|
||||
/* <ROOMS_SUPPORT:4> */
|
||||
teamsMeetingPasscode.visibility = View.GONE
|
||||
teamsMeetingId.visibility = View.GONE
|
||||
/* <ROOMS_SUPPORT:4>
|
||||
roomsMeetingRadioButton.isChecked = false
|
||||
attendeeRoleRadioButton.visibility = View.GONE
|
||||
presenterRoleRadioButton.visibility = View.GONE
|
||||
/* </ROOMS_SUPPORT:1> */
|
||||
</ROOMS_SUPPORT:1> */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +326,6 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
unregisterReceiver(callLauncherBroadCastReceiver)
|
||||
DismissCompositeButtonView.get(this).hide()
|
||||
DismissCompositeButtonView.buttonView = null
|
||||
(application as CallLauncherApplication).onDestroy()
|
||||
}
|
||||
|
||||
// check whether new Activity instance was brought to top of stack,
|
||||
|
@ -354,13 +350,16 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
sharedPreference.edit().putString(CACHED_TOKEN, acsToken).apply()
|
||||
sharedPreference.edit().putString(CACHED_USER_NAME, userName).apply()
|
||||
|
||||
/* <ROOMS_SUPPORT:0> */
|
||||
/* <ROOMS_SUPPORT:0>
|
||||
val roomId = binding.groupIdOrTeamsMeetingLinkText.text.toString()
|
||||
val roomRole =
|
||||
if (binding.attendeeRoleRadioButton.isChecked) CallCompositeParticipantRole.ATTENDEE
|
||||
else if (binding.presenterRoleRadioButton.isChecked) CallCompositeParticipantRole.PRESENTER
|
||||
else null
|
||||
/* </ROOMS_SUPPORT:0> */
|
||||
val roomRole = if (binding.attendeeRoleRadioButton.isChecked) CallCompositeParticipantRole.ATTENDEE
|
||||
else if (binding.presenterRoleRadioButton.isChecked) CallCompositeParticipantRole.PRESENTER
|
||||
else null
|
||||
</ROOMS_SUPPORT:0> */
|
||||
|
||||
var groupId: UUID? = null
|
||||
if (binding.groupCallRadioButton.isChecked) {
|
||||
|
@ -378,13 +377,8 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
var meetingPasscode: String? = null
|
||||
if (binding.teamsMeetingRadioButton.isChecked) {
|
||||
meetingLink = binding.groupIdOrTeamsMeetingLinkText.text.toString()
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId = binding.teamsMeetingId.text.toString()
|
||||
meetingPasscode = binding.teamsMeetingPasscode.text.toString()
|
||||
/* <|MEETING_ID_LOCATOR>
|
||||
meetingId = ""
|
||||
meetingPasscode = ""
|
||||
</MEETING_ID_LOCATOR> */
|
||||
if (meetingId.isBlank() && meetingLink.isBlank()) {
|
||||
val message = getString(R.string.teams_meeting_link_empty_alert)
|
||||
showAlert(message)
|
||||
|
@ -414,15 +408,13 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
acsToken,
|
||||
userName,
|
||||
groupId,
|
||||
/* <ROOMS_SUPPORT:5> */
|
||||
/* <ROOMS_SUPPORT:5>
|
||||
roomId,
|
||||
roomRole,
|
||||
/* </ROOMS_SUPPORT:2> */
|
||||
</ROOMS_SUPPORT:2> */
|
||||
meetingLink,
|
||||
/* <MEETING_ID_LOCATOR> */
|
||||
meetingId,
|
||||
meetingPasscode,
|
||||
/* </MEETING_ID_LOCATOR> */
|
||||
participantMris
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,9 +15,4 @@ class CallLauncherApplication : Application() {
|
|||
}
|
||||
return callCompositeManager!!
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
callCompositeManager?.dismissCallComposite()
|
||||
callCompositeManager = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
android:layout_weight="1"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="@string/one_to_n_call_label" />
|
||||
<!-- <ROOMS_SUPPORT:0> -->
|
||||
<!-- <ROOMS_SUPPORT:0>
|
||||
<RadioButton
|
||||
android:id="@+id/roomsMeetingRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -88,7 +88,7 @@
|
|||
android:text="@string/rooms_attendee"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<!-- </ROOMS_SUPPORT:0> -->
|
||||
</ROOMS_SUPPORT:0> -->
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
|
@ -100,7 +100,6 @@
|
|||
android:hint="@string/group_call_id_or_teams_meeting_hint"
|
||||
android:inputType="textNoSuggestions"
|
||||
/>
|
||||
<!-- <MEETING_ID_LOCATOR> -->
|
||||
<EditText
|
||||
android:id="@+id/teamsMeetingId"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -121,7 +120,6 @@
|
|||
android:inputType="textNoSuggestions"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<!-- </MEETING_ID_LOCATOR> -->
|
||||
|
||||
<Button
|
||||
android:id="@+id/showCallHistoryButton"
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
# Azure Communication UI Calling Release History
|
||||
|
||||
## 1.8.0 (*)
|
||||
## 1.8.0 (2024-06-20)
|
||||
|
||||
### Features
|
||||
- Telecom manager integration supported by native calling SDK
|
||||
- One to one call support with push notifications
|
||||
- Teams meeting join with meeting id
|
||||
|
||||
## 1.7.0 (2024-05-29)
|
||||
|
||||
### Features
|
||||
- Disable leave call confirmation dialog
|
||||
|
||||
- Teams meeting short URL support
|
||||
|
||||
## 1.6.2 (2024-05-03)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче