[Feature][Calling] CSS call history (#710)
This commit is contained in:
Родитель
e7975fcab9
Коммит
ccc4d89ff9
|
@ -5,7 +5,7 @@ buildscript {
|
|||
}
|
||||
|
||||
ext {
|
||||
call_library_version_name = '1.2.0-beta.2'
|
||||
call_library_version_name = '1.2.0'
|
||||
chat_library_version_name = '1.0.0-beta.1'
|
||||
|
||||
ui_library_version_code = getVersionCode()
|
||||
|
|
|
@ -99,12 +99,16 @@ dependencies {
|
|||
implementation "com.microsoft.fluentui:fluentui_persona:$microsoft_fluent_ui_version"
|
||||
implementation "com.microsoft.fluentui:fluentui_transients:$microsoft_fluent_ui_version"
|
||||
|
||||
api 'com.jakewharton.threetenabp:threetenabp:1.4.4'
|
||||
|
||||
testImplementation "androidx.arch.core:core-testing:$androidx_core_testing_version"
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.mockito:mockito-inline:$mockito_inline_version"
|
||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$jetbrains_kotlinx_coroutines_test_version"
|
||||
|
||||
testImplementation('org.threeten:threetenbp:1.6.5') {
|
||||
exclude group: 'com.jakewharton.threetenabp', module: 'threetenabp'
|
||||
}
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidx_junit_version"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidx_espresso_core_version"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$androidx_espresso_contrib_version"
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepository
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepositoryImpl
|
||||
import com.azure.android.communication.ui.calling.logger.DefaultLogger
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
// To test CallHistoryRepository we need to have context, so putting this test the androidTest
|
||||
internal class CallHistoryRepositoryTest {
|
||||
@Test
|
||||
@ExperimentalCoroutinesApi
|
||||
fun callHistoryService_onCallStateUpdate_callsRepositoryInsert() = runTest {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
AndroidThreeTen.init(context.applicationContext)
|
||||
|
||||
val repository: CallHistoryRepository = CallHistoryRepositoryImpl(context, DefaultLogger())
|
||||
|
||||
val originalList = repository.getAll()
|
||||
val olderThanMonth = originalList.firstOrNull {
|
||||
// should not have records older then now() - 31 days.
|
||||
// Subtract a min to account possible delay while executing.
|
||||
it.callStartedOn.isBefore(OffsetDateTime.now().minusDays(31).minusMinutes(1))
|
||||
}
|
||||
Assert.assertNull(olderThanMonth)
|
||||
|
||||
var callId = UUID.randomUUID().toString()
|
||||
var callStartDate = OffsetDateTime.now()
|
||||
// inserting a record older then 31 days
|
||||
repository.insert(callId, callStartDate.minusDays(32))
|
||||
// should not return new record
|
||||
var freshList = repository.getAll()
|
||||
Assert.assertEquals(originalList.count(), freshList.count())
|
||||
Assert.assertNull(freshList.find { it.callId == callId })
|
||||
|
||||
// inset new record
|
||||
repository.insert(callId, callStartDate)
|
||||
|
||||
freshList = repository.getAll()
|
||||
|
||||
Assert.assertEquals(originalList.count() + 1, freshList.count())
|
||||
|
||||
val retrievedNewRecord = freshList.find { it.callId == callId }
|
||||
Assert.assertNotNull(retrievedNewRecord)
|
||||
Assert.assertEquals(callStartDate, retrievedNewRecord!!.callStartedOn)
|
||||
}
|
||||
}
|
|
@ -23,8 +23,9 @@ import com.azure.android.communication.ui.calling.models.CallCompositeSetPartici
|
|||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator;
|
||||
import com.azure.android.communication.ui.calling.presentation.CallCompositeActivity;
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager;
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen;
|
||||
|
||||
import static com.azure.android.communication.ui.calling.models.CallCompositeDebugInfoExtensionsKt.buildCallCompositeDebugInfo;
|
||||
import static com.azure.android.communication.ui.calling.CallCompositeExtentionsKt.createDebugInfoManager;
|
||||
import static com.azure.android.communication.ui.calling.service.sdk.TypeConversionsKt.into;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
@ -213,25 +214,32 @@ public final class CallComposite {
|
|||
*
|
||||
* @return {@link CallCompositeDebugInfo}
|
||||
*/
|
||||
public CallCompositeDebugInfo getDebugInfo() {
|
||||
final DebugInfoManager debugInfoManager = getDebugInfoManager();
|
||||
return debugInfoManager != null
|
||||
? debugInfoManager.getDebugInfo()
|
||||
: buildCallCompositeDebugInfo();
|
||||
public CallCompositeDebugInfo getDebugInfo(final Context context) {
|
||||
AndroidThreeTen.init(context.getApplicationContext());
|
||||
final DebugInfoManager debugInfoManager = getDebugInfoManager(context.getApplicationContext());
|
||||
return debugInfoManager.getDebugInfo();
|
||||
}
|
||||
|
||||
void setDependencyInjectionContainer(final DependencyInjectionContainer diContainer) {
|
||||
this.diContainer = new WeakReference<DependencyInjectionContainer>(diContainer);
|
||||
this.diContainer = new WeakReference<>(diContainer);
|
||||
}
|
||||
|
||||
private DebugInfoManager getDebugInfoManager() {
|
||||
return diContainer != null ? diContainer.get().getDebugInfoManager() : null;
|
||||
private DebugInfoManager getDebugInfoManager(final Context context) {
|
||||
if (diContainer != null) {
|
||||
final DependencyInjectionContainer container = diContainer.get();
|
||||
if (container != null) {
|
||||
return container.getDebugInfoManager();
|
||||
}
|
||||
}
|
||||
|
||||
return createDebugInfoManager(context.getApplicationContext());
|
||||
}
|
||||
|
||||
private void launchComposite(final Context context,
|
||||
final CallCompositeRemoteOptions remoteOptions,
|
||||
final CallCompositeLocalOptions localOptions,
|
||||
final boolean isTest) {
|
||||
AndroidThreeTen.init(context.getApplicationContext());
|
||||
|
||||
UUID groupId = null;
|
||||
String meetingLink = null;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling
|
||||
|
||||
import android.content.Context
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepositoryImpl
|
||||
import com.azure.android.communication.ui.calling.logger.DefaultLogger
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManagerImpl
|
||||
|
||||
internal fun createDebugInfoManager(context: Context): DebugInfoManager {
|
||||
return DebugInfoManagerImpl(CallHistoryRepositoryImpl(context, DefaultLogger()))
|
||||
}
|
|
@ -7,7 +7,7 @@ internal class DiagnosticConfig {
|
|||
val tags: Array<String> by lazy { arrayOf(getApplicationId()) }
|
||||
|
||||
private fun getApplicationId(): String {
|
||||
val callingCompositeVersionName = "1.2.0-beta.2"
|
||||
val callingCompositeVersionName = "1.2.0"
|
||||
val baseTag = "ac"
|
||||
// Tag template is: acXYYY/<version>
|
||||
// Where:
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling.data
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.azure.android.communication.ui.calling.data.model.CallHistoryRecordData
|
||||
import com.azure.android.communication.ui.calling.logger.Logger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.threeten.bp.Instant
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
import org.threeten.bp.ZoneId
|
||||
|
||||
internal interface CallHistoryRepository {
|
||||
suspend fun insert(callId: String, callDateTime: OffsetDateTime)
|
||||
suspend fun getAll(): List<CallHistoryRecordData>
|
||||
}
|
||||
|
||||
internal class CallHistoryRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val logger: Logger
|
||||
) : CallHistoryRepository {
|
||||
|
||||
override suspend fun insert(callId: String, callDateTime: OffsetDateTime) {
|
||||
return withContext(Dispatchers.IO) {
|
||||
// SQLite does not allow concurrent writes. Need to queue them via lock.
|
||||
synchronized(dbAccessLock) {
|
||||
// Using a new db instance instead of caching one as we do not have a
|
||||
// reliable event when to dispose it.
|
||||
DbHelper(context).writableDatabase
|
||||
.use { db ->
|
||||
val values = ContentValues().apply {
|
||||
put(CallHistoryContract.COLUMN_NAME_CALL_ID, callId)
|
||||
put(CallHistoryContract.COLUMN_NAME_CALL_DATE, callDateTime.toInstant().toEpochMilli())
|
||||
}
|
||||
|
||||
val result = db.insert(CallHistoryContract.TABLE_NAME, null, values)
|
||||
if (result == -1L) {
|
||||
logger.warning("Failed to save call history record.")
|
||||
}
|
||||
|
||||
// Execute cleanup separately (not in one transaction) in case of it fails,
|
||||
// so it does not affect insert.
|
||||
cleanupOldRecords(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): List<CallHistoryRecordData> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
// Using a new db instance instead of caching one as we do not have a
|
||||
// reliable event when to dispose it.
|
||||
DbHelper(context).writableDatabase.use { db ->
|
||||
synchronized(dbAccessLock) {
|
||||
cleanupOldRecords(db)
|
||||
}
|
||||
val items = mutableListOf<CallHistoryRecordData>()
|
||||
db.rawQuery(
|
||||
"SELECT ${CallHistoryContract.COLUMN_NAME_ID}, " +
|
||||
"${CallHistoryContract.COLUMN_NAME_CALL_DATE}, " +
|
||||
"${CallHistoryContract.COLUMN_NAME_CALL_ID} " +
|
||||
"FROM ${CallHistoryContract.TABLE_NAME}",
|
||||
null
|
||||
).use {
|
||||
if (it.moveToFirst()) {
|
||||
val idColumnIndex = it.getColumnIndexOrThrow(CallHistoryContract.COLUMN_NAME_ID)
|
||||
val nameColumnIndex = it.getColumnIndexOrThrow(CallHistoryContract.COLUMN_NAME_CALL_ID)
|
||||
val dateColumnIndex = it.getColumnIndexOrThrow(CallHistoryContract.COLUMN_NAME_CALL_DATE)
|
||||
do {
|
||||
items.add(
|
||||
CallHistoryRecordData(
|
||||
id = it.getInt(idColumnIndex),
|
||||
callId = it.getString(nameColumnIndex),
|
||||
callStartedOn = OffsetDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(it.getLong(dateColumnIndex)), ZoneId.systemDefault()
|
||||
),
|
||||
)
|
||||
)
|
||||
} while (it.moveToNext())
|
||||
}
|
||||
}
|
||||
return@withContext items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupOldRecords(db: SQLiteDatabase) {
|
||||
val threshold = OffsetDateTime.now().minusDays(31).toInstant().toEpochMilli()
|
||||
db.delete(
|
||||
CallHistoryContract.TABLE_NAME,
|
||||
"${CallHistoryContract.COLUMN_NAME_CALL_DATE} < ?", arrayOf(threshold.toString())
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val dbAccessLock = Any()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling.data
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.provider.BaseColumns
|
||||
|
||||
internal class DbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(CallHistoryContract.SQL_CREATE_CALL_HISTORY)
|
||||
db.execSQL(CallHistoryContract.SQL_CREATE_CALL_HISTORY_INDEX)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
// If you change the database schema, you must increment the database version.
|
||||
const val DATABASE_VERSION = 1
|
||||
const val DATABASE_NAME = "com.azure.android.communication.ui.calling.CallHistoryReader.db"
|
||||
}
|
||||
}
|
||||
|
||||
internal object CallHistoryContract {
|
||||
|
||||
const val COLUMN_NAME_ID = BaseColumns._ID
|
||||
const val TABLE_NAME = "call_history"
|
||||
|
||||
const val COLUMN_NAME_CALL_ID = "call_id"
|
||||
const val COLUMN_NAME_CALL_DATE = "call_date"
|
||||
|
||||
const val SQL_CREATE_CALL_HISTORY =
|
||||
"CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
|
||||
"$COLUMN_NAME_ID INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"$COLUMN_NAME_CALL_ID TEXT NOT NULL," +
|
||||
"$COLUMN_NAME_CALL_DATE INTEGER NOT NULL)"
|
||||
|
||||
const val SQL_CREATE_CALL_HISTORY_INDEX =
|
||||
"CREATE INDEX IF NOT EXISTS call_dateindex ON $TABLE_NAME($COLUMN_NAME_CALL_DATE);"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling.data.model
|
||||
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
|
||||
internal data class CallHistoryRecordData(
|
||||
val id: Int,
|
||||
val callId: String,
|
||||
val callStartedOn: OffsetDateTime,
|
||||
)
|
|
@ -5,6 +5,7 @@ package com.azure.android.communication.ui.calling.di
|
|||
|
||||
import com.azure.android.communication.ui.calling.CallComposite
|
||||
import com.azure.android.communication.ui.calling.configuration.CallCompositeConfiguration
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepository
|
||||
import com.azure.android.communication.ui.calling.error.ErrorHandler
|
||||
import com.azure.android.communication.ui.calling.handlers.RemoteParticipantHandler
|
||||
import com.azure.android.communication.ui.calling.logger.Logger
|
||||
|
@ -21,6 +22,7 @@ import com.azure.android.communication.ui.calling.redux.Store
|
|||
import com.azure.android.communication.ui.calling.redux.middleware.handler.CallingMiddlewareActionHandler
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryService
|
||||
import com.azure.android.communication.ui.calling.service.NotificationService
|
||||
|
||||
// Dependency Container for the Call Composite Activity
|
||||
|
@ -51,7 +53,11 @@ internal interface DependencyInjectionContainer {
|
|||
val audioFocusManager: AudioFocusManager
|
||||
val networkManager: NetworkManager
|
||||
val debugInfoManager: DebugInfoManager
|
||||
val callHistoryService: CallHistoryService
|
||||
|
||||
// UI
|
||||
val videoViewManager: VideoViewManager
|
||||
|
||||
// Data
|
||||
val callHistoryRepository: CallHistoryRepository
|
||||
}
|
||||
|
|
|
@ -5,10 +5,12 @@ package com.azure.android.communication.ui.calling.di
|
|||
|
||||
import android.content.Context
|
||||
import com.azure.android.communication.ui.calling.CallComposite
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepositoryImpl
|
||||
import com.azure.android.communication.ui.calling.error.ErrorHandler
|
||||
import com.azure.android.communication.ui.calling.getConfig
|
||||
import com.azure.android.communication.ui.calling.handlers.RemoteParticipantHandler
|
||||
import com.azure.android.communication.ui.calling.logger.DefaultLogger
|
||||
import com.azure.android.communication.ui.calling.logger.Logger
|
||||
import com.azure.android.communication.ui.calling.presentation.VideoStreamRendererFactory
|
||||
import com.azure.android.communication.ui.calling.presentation.VideoStreamRendererFactoryImpl
|
||||
import com.azure.android.communication.ui.calling.presentation.VideoViewManager
|
||||
|
@ -45,6 +47,8 @@ import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
|||
import com.azure.android.communication.ui.calling.service.CallingService
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManagerImpl
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryService
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryServiceImpl
|
||||
import com.azure.android.communication.ui.calling.service.NotificationService
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CallingSDK
|
||||
import com.azure.android.communication.ui.calling.service.sdk.CallingSDKEventHandler
|
||||
|
@ -111,7 +115,14 @@ internal class DependencyInjectionContainerImpl(
|
|||
}
|
||||
override val debugInfoManager: DebugInfoManager by lazy {
|
||||
DebugInfoManagerImpl(
|
||||
callHistoryRepository,
|
||||
)
|
||||
}
|
||||
|
||||
override val callHistoryService: CallHistoryService by lazy {
|
||||
CallHistoryServiceImpl(
|
||||
appStore,
|
||||
callHistoryRepository
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -158,6 +169,10 @@ internal class DependencyInjectionContainerImpl(
|
|||
RemoteParticipantHandler(configuration, appStore, callingSDKWrapper)
|
||||
}
|
||||
|
||||
override val callHistoryRepository by lazy {
|
||||
CallHistoryRepositoryImpl(applicationContext, logger)
|
||||
}
|
||||
|
||||
//region Redux
|
||||
// Initial State
|
||||
private val initialState by lazy { AppReduxState(configuration.callConfig?.displayName) }
|
||||
|
@ -199,7 +214,7 @@ internal class DependencyInjectionContainerImpl(
|
|||
//region System
|
||||
private val applicationContext get() = parentContext.applicationContext
|
||||
|
||||
override val logger by lazy { DefaultLogger() }
|
||||
override val logger: Logger by lazy { DefaultLogger() }
|
||||
|
||||
private val callingSDKWrapper: CallingSDK by lazy {
|
||||
customCallingSDK
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling.models;
|
||||
|
||||
import java.util.List;
|
||||
import org.threeten.bp.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* Call history.
|
||||
*/
|
||||
public class CallCompositeCallHistoryRecord {
|
||||
private final OffsetDateTime callStartedOn;
|
||||
private final List<String> callIds;
|
||||
|
||||
CallCompositeCallHistoryRecord(final OffsetDateTime callStartedOn, final List<String> callIds) {
|
||||
this.callStartedOn = callStartedOn;
|
||||
this.callIds = callIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offset date call started on.
|
||||
* @return
|
||||
*/
|
||||
public OffsetDateTime getCallStartedOn() {
|
||||
return callStartedOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Id list associated with particular call.
|
||||
* @return
|
||||
*/
|
||||
public List<String> getCallIds() {
|
||||
return callIds;
|
||||
}
|
||||
}
|
|
@ -3,30 +3,24 @@
|
|||
|
||||
package com.azure.android.communication.ui.calling.models;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Call Composite Debug information.
|
||||
*/
|
||||
public final class CallCompositeDebugInfo {
|
||||
|
||||
private String lastCallId;
|
||||
private final List<CallCompositeCallHistoryRecord> callHistoryRecord;
|
||||
|
||||
CallCompositeDebugInfo() { }
|
||||
|
||||
/**
|
||||
* Set last call id.
|
||||
* @param lastCallId last call id.
|
||||
* @return {@link CallCompositeDebugInfo}
|
||||
*/
|
||||
CallCompositeDebugInfo setLastCallId(final String lastCallId) {
|
||||
this.lastCallId = lastCallId;
|
||||
return this;
|
||||
CallCompositeDebugInfo(final List<CallCompositeCallHistoryRecord> callHistoryRecord) {
|
||||
this.callHistoryRecord = callHistoryRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last call id.
|
||||
* @return {@link String}
|
||||
* The history of calls up to 30 days. Ordered ascending by call started date.
|
||||
* @return
|
||||
*/
|
||||
public String getLastCallId() {
|
||||
return lastCallId;
|
||||
public List<CallCompositeCallHistoryRecord> getCallHistoryRecords() {
|
||||
return callHistoryRecord;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
|
||||
package com.azure.android.communication.ui.calling.models
|
||||
|
||||
internal fun CallCompositeDebugInfo.setCallId(lastKnownCallId: String?) {
|
||||
this.lastCallId = lastKnownCallId
|
||||
}
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
|
||||
internal fun buildCallCompositeDebugInfo(): CallCompositeDebugInfo = CallCompositeDebugInfo()
|
||||
internal fun buildCallCompositeDebugInfo(callHistoryRecordList: List<CallCompositeCallHistoryRecord>) =
|
||||
CallCompositeDebugInfo(callHistoryRecordList)
|
||||
|
||||
internal fun buildCallHistoryRecord(callStartedOn: OffsetDateTime, callIds: List<String>): CallCompositeCallHistoryRecord {
|
||||
return CallCompositeCallHistoryRecord(callStartedOn, callIds)
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ internal class CallCompositeActivity : AppCompatActivity() {
|
|||
private val callingMiddlewareActionHandler get() = container.callingMiddlewareActionHandler
|
||||
private val videoViewManager get() = container.videoViewManager
|
||||
private val instanceId get() = intent.getIntExtra(KEY_INSTANCE_ID, -1)
|
||||
private val diagnosticsService get() = container.debugInfoManager
|
||||
private val callHistoryService get() = container.callHistoryService
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -117,7 +117,7 @@ internal class CallCompositeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
notificationService.start(lifecycleScope)
|
||||
diagnosticsService.start(lifecycleScope)
|
||||
callHistoryService.start(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
|
|
@ -12,7 +12,7 @@ internal class MoreCallOptionsListViewModel(
|
|||
private val unknown = "UNKNOWN"
|
||||
val callId: String
|
||||
get() {
|
||||
val lastKnownCallId = debugInfoManager.debugInfo.lastCallId
|
||||
val lastKnownCallId = debugInfoManager.getDebugInfo().callHistoryRecords.lastOrNull()?.callIds?.lastOrNull()
|
||||
return "Call ID: \"${if (lastKnownCallId.isNullOrEmpty()) unknown else lastKnownCallId}\""
|
||||
}
|
||||
|
||||
|
|
|
@ -3,35 +3,40 @@
|
|||
|
||||
package com.azure.android.communication.ui.calling.presentation.manager
|
||||
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepository
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallHistoryRecord
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeDebugInfo
|
||||
import com.azure.android.communication.ui.calling.models.buildCallCompositeDebugInfo
|
||||
import com.azure.android.communication.ui.calling.models.setCallId
|
||||
import com.azure.android.communication.ui.calling.redux.Store
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import com.azure.android.communication.ui.calling.models.buildCallHistoryRecord
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal interface DebugInfoManager {
|
||||
fun start(coroutineScope: CoroutineScope)
|
||||
val debugInfo: CallCompositeDebugInfo
|
||||
fun getDebugInfo(): CallCompositeDebugInfo
|
||||
}
|
||||
|
||||
internal class DebugInfoManagerImpl(
|
||||
private val store: Store<ReduxState>,
|
||||
private val callHistoryRepository: CallHistoryRepository,
|
||||
) : DebugInfoManager {
|
||||
|
||||
override var debugInfo = buildCallCompositeDebugInfo()
|
||||
|
||||
override fun start(coroutineScope: CoroutineScope) {
|
||||
coroutineScope.launch {
|
||||
store.getStateFlow().collect {
|
||||
if (!it.callState.callId.isNullOrEmpty()) {
|
||||
val newDebugInfo = buildCallCompositeDebugInfo()
|
||||
newDebugInfo.setCallId(it.callState.callId)
|
||||
debugInfo = newDebugInfo
|
||||
}
|
||||
}
|
||||
override fun getDebugInfo(): CallCompositeDebugInfo {
|
||||
val callHistory = runBlocking {
|
||||
withContext(Dispatchers.IO) { getCallHistory() }
|
||||
}
|
||||
return buildCallCompositeDebugInfo(callHistory)
|
||||
}
|
||||
|
||||
private suspend fun getCallHistory(): List<CallCompositeCallHistoryRecord> {
|
||||
return callHistoryRepository.getAll()
|
||||
.groupBy {
|
||||
it.callStartedOn
|
||||
}
|
||||
.map { mapped ->
|
||||
buildCallHistoryRecord(mapped.key, mapped.value.map { it.callId })
|
||||
}
|
||||
.sortedBy {
|
||||
it.callStartedOn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package com.azure.android.communication.ui.calling.redux.reducer
|
|||
import com.azure.android.communication.ui.calling.redux.action.Action
|
||||
import com.azure.android.communication.ui.calling.redux.action.CallingAction
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingState
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
|
||||
internal interface CallStateReducer : Reducer<CallingState>
|
||||
|
||||
|
@ -22,7 +23,7 @@ internal class CallStateReducerImpl : CallStateReducer {
|
|||
callingState.copy(isTranscribing = action.isTranscribing)
|
||||
}
|
||||
is CallingAction.CallStartRequested -> {
|
||||
callingState.copy(joinCallIsRequested = true)
|
||||
callingState.copy(joinCallIsRequested = true, callStartDateTime = OffsetDateTime.now())
|
||||
}
|
||||
is CallingAction.CallIdUpdated -> {
|
||||
callingState.copy(callId = action.callId)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
package com.azure.android.communication.ui.calling.redux.state
|
||||
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
|
||||
internal enum class CallingStatus {
|
||||
NONE,
|
||||
EARLY_MEDIA,
|
||||
|
@ -24,6 +26,8 @@ internal data class CallingState(
|
|||
val joinCallIsRequested: Boolean = false,
|
||||
val isRecording: Boolean = false,
|
||||
val isTranscribing: Boolean = false,
|
||||
// set once for the duration of the call in the CallStateReducer when call start requested.
|
||||
val callStartDateTime: OffsetDateTime? = null,
|
||||
)
|
||||
|
||||
internal fun CallingState.isDisconnected() =
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.calling.service
|
||||
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepository
|
||||
import com.azure.android.communication.ui.calling.redux.Store
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal interface CallHistoryService {
|
||||
fun start(coroutineScope: CoroutineScope)
|
||||
}
|
||||
|
||||
internal class CallHistoryServiceImpl(
|
||||
private val store: Store<ReduxState>,
|
||||
private val callHistoryRepository: CallHistoryRepository,
|
||||
) : CallHistoryService {
|
||||
private val callIdStateFlow = MutableStateFlow<String?>(null)
|
||||
|
||||
override fun start(coroutineScope: CoroutineScope) {
|
||||
coroutineScope.launch {
|
||||
store.getStateFlow().collect {
|
||||
callIdStateFlow.value = it.callState.callId
|
||||
}
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
callIdStateFlow.collect { callId ->
|
||||
val callStartDateTime = store.getCurrentState().callState.callStartDateTime
|
||||
if (callId != null && callStartDateTime != null) {
|
||||
callHistoryRepository.insert(callId, callStartDateTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,22 +4,18 @@
|
|||
package com.azure.android.communication.ui.presentation.manager
|
||||
|
||||
import com.azure.android.communication.ui.ACSBaseTestCoroutine
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepository
|
||||
import com.azure.android.communication.ui.calling.data.model.CallHistoryRecordData
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
|
||||
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManagerImpl
|
||||
import com.azure.android.communication.ui.calling.redux.AppStore
|
||||
import com.azure.android.communication.ui.calling.redux.state.AppReduxState
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingState
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.mock
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
internal class DebugInfoManagerTest : ACSBaseTestCoroutine() {
|
||||
|
@ -30,45 +26,23 @@ internal class DebugInfoManagerTest : ACSBaseTestCoroutine() {
|
|||
|
||||
runScopedTest {
|
||||
// arrange
|
||||
val appState1 = AppReduxState("")
|
||||
val historyList = mutableListOf(
|
||||
CallHistoryRecordData(1, "callId1", OffsetDateTime.now().minusDays(6)),
|
||||
CallHistoryRecordData(2, "callId2", OffsetDateTime.now().minusDays(4)),
|
||||
CallHistoryRecordData(3, "callId3", OffsetDateTime.now().minusDays(3)),
|
||||
CallHistoryRecordData(4, "callId4", OffsetDateTime.now().minusDays(1)),
|
||||
)
|
||||
|
||||
val stateFlow = MutableStateFlow<ReduxState>(appState1)
|
||||
val mockAppStore = mock<AppStore<ReduxState>> {
|
||||
on { getStateFlow() } doAnswer { stateFlow }
|
||||
val callHistoryRepository = mock<CallHistoryRepository> {
|
||||
onBlocking { getAll() } doAnswer { historyList }
|
||||
}
|
||||
|
||||
val debugInfoManager: DebugInfoManager = DebugInfoManagerImpl(mockAppStore)
|
||||
val flowJob = launch {
|
||||
debugInfoManager.start(coroutineScope = this)
|
||||
}
|
||||
val debugInfoManager: DebugInfoManager = DebugInfoManagerImpl(callHistoryRepository)
|
||||
|
||||
val diagnostics1 = debugInfoManager.debugInfo
|
||||
Assert.assertNotNull(diagnostics1)
|
||||
Assert.assertNull(diagnostics1.lastCallId)
|
||||
|
||||
// update state
|
||||
val appState2 = AppReduxState("")
|
||||
val callID = "callID"
|
||||
appState2.callState = CallingState(CallingStatus.CONNECTING, callID)
|
||||
stateFlow.value = appState2
|
||||
|
||||
val diagnostics2 = debugInfoManager.debugInfo
|
||||
Assert.assertNotSame(diagnostics1, diagnostics2)
|
||||
Assert.assertNotNull(diagnostics2)
|
||||
Assert.assertEquals(callID, diagnostics2.lastCallId)
|
||||
|
||||
// redux state loosing CallID
|
||||
|
||||
// update state
|
||||
val appState3 = AppReduxState("")
|
||||
appState3.callState = CallingState(CallingStatus.CONNECTING, null)
|
||||
stateFlow.value = appState3
|
||||
|
||||
val diagnostics3 = debugInfoManager.debugInfo
|
||||
Assert.assertSame(diagnostics2, diagnostics3)
|
||||
Assert.assertNotNull(diagnostics3)
|
||||
Assert.assertEquals(callID, diagnostics3.lastCallId)
|
||||
flowJob.cancel()
|
||||
val debugIndo = debugInfoManager.getDebugInfo()
|
||||
Assert.assertNotNull(debugIndo)
|
||||
Assert.assertEquals(historyList.count(), debugIndo.callHistoryRecords.count())
|
||||
Assert.assertEquals(historyList.last().id, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,5 +40,6 @@ internal class CallingReducerUnitTest {
|
|||
|
||||
// assert
|
||||
Assert.assertEquals(CallingStatus.NONE, newState.callingStatus)
|
||||
Assert.assertNotNull(newState.callStartDateTime)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.service
|
||||
|
||||
import com.azure.android.communication.ui.ACSBaseTestCoroutine
|
||||
import com.azure.android.communication.ui.calling.data.CallHistoryRepository
|
||||
import com.azure.android.communication.ui.calling.redux.AppStore
|
||||
import com.azure.android.communication.ui.calling.redux.state.AppReduxState
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingState
|
||||
import com.azure.android.communication.ui.calling.redux.state.CallingStatus
|
||||
import com.azure.android.communication.ui.calling.redux.state.ReduxState
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryService
|
||||
import com.azure.android.communication.ui.calling.service.CallHistoryServiceImpl
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.eq
|
||||
import org.threeten.bp.OffsetDateTime
|
||||
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
internal class CallHistoryServiceTest : ACSBaseTestCoroutine() {
|
||||
|
||||
@Test
|
||||
@ExperimentalCoroutinesApi
|
||||
fun callHistoryService_onCallStateUpdate_callsRepositoryInsert() {
|
||||
|
||||
runScopedTest {
|
||||
// arrange
|
||||
val appState1 = AppReduxState("")
|
||||
appState1.callState = CallingState(CallingStatus.NONE)
|
||||
|
||||
val stateFlow = MutableStateFlow<ReduxState>(appState1)
|
||||
val mockAppStore = mock<AppStore<ReduxState>> {
|
||||
on { getStateFlow() } doAnswer { stateFlow }
|
||||
on { getCurrentState() } doAnswer { stateFlow.value }
|
||||
}
|
||||
|
||||
val callHistoryRepository = mock<CallHistoryRepository> {
|
||||
onBlocking { insert(any(), any()) } doAnswer { }
|
||||
}
|
||||
|
||||
val callHistoryService: CallHistoryService = CallHistoryServiceImpl(mockAppStore, callHistoryRepository)
|
||||
val flowJob = launch {
|
||||
callHistoryService.start(coroutineScope = this)
|
||||
}
|
||||
|
||||
// update state
|
||||
val appState2 = AppReduxState("")
|
||||
val callID = "callID"
|
||||
appState2.callState = CallingState(CallingStatus.CONNECTING, callID, callStartDateTime = OffsetDateTime.now())
|
||||
stateFlow.value = appState2
|
||||
|
||||
verify(callHistoryRepository, times(1)).insert(eq(callID), any())
|
||||
|
||||
flowJob.cancel()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import org.junit.Test
|
|||
|
||||
internal class DiagnosticConfigUnitTests {
|
||||
private val expectedPrefix = "aca110/"
|
||||
private val expectedVersion = "${expectedPrefix}1.2.0-beta.2"
|
||||
private val expectedVersion = "${expectedPrefix}1.2.0"
|
||||
|
||||
@Test
|
||||
fun test_Expected_Tag() {
|
||||
|
|
|
@ -13,8 +13,8 @@ import com.azure.android.communication.ui.chat.redux.state.ChatStatus
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
|
||||
internal class MessageViewTest : BaseUiTest() {
|
||||
@get:Rule
|
||||
|
|
|
@ -70,7 +70,6 @@ class CallWithChatLauncherActivity : AppCompatActivity(), AlertHandler {
|
|||
}
|
||||
|
||||
binding.run {
|
||||
tokenFunctionUrlText.setText(BuildConfig.TOKEN_FUNCTION_URL)
|
||||
|
||||
if (!deeplinkAcsToken.isNullOrEmpty()) {
|
||||
acsTokenText.setText(deeplinkAcsToken)
|
||||
|
|
|
@ -79,14 +79,4 @@ class CallingCompositeACSTokenTest : BaseUiTest() {
|
|||
|
||||
homeScreen.clickAlertDialogOkButton()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyAcsToken() {
|
||||
val homeScreen = HomeScreenRobot()
|
||||
.setGroupIdOrTeamsMeetingUrl(CallIdentifiersHelper.getGroupId())
|
||||
.setEmptyAcsToken()
|
||||
|
||||
val setupScreen = homeScreen.clickLaunchButton()
|
||||
homeScreen.clickAlertDialogOkButton()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,18 +15,16 @@ import com.azure.android.communication.ui.callingcompositedemoapp.databinding.Ac
|
|||
import com.azure.android.communication.ui.callingcompositedemoapp.features.AdditionalFeatures
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.FeatureFlags
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.conditionallyRegisterDiagnostics
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.launcher.CallingCompositeLauncher
|
||||
import com.microsoft.appcenter.AppCenter
|
||||
import com.microsoft.appcenter.analytics.Analytics
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import com.microsoft.appcenter.distribute.Distribute
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
import java.util.UUID
|
||||
|
||||
class CallLauncherActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityCallLauncherBinding
|
||||
private val callLauncherViewModel: CallLauncherViewModel by viewModels()
|
||||
private val isTokenFunctionOptionSelected: String = "isTokenFunctionOptionSelected"
|
||||
private val isKotlinLauncherOptionSelected: String = "isKotlinLauncherOptionSelected"
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -56,23 +54,7 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
val deeplinkGroupId = data?.getQueryParameter("groupid")
|
||||
val deeplinkTeamsUrl = data?.getQueryParameter("teamsurl")
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.getBoolean(isTokenFunctionOptionSelected)) {
|
||||
callLauncherViewModel.useTokenFunction()
|
||||
} else {
|
||||
callLauncherViewModel.useAcsToken()
|
||||
}
|
||||
|
||||
if (savedInstanceState.getBoolean(isKotlinLauncherOptionSelected)) {
|
||||
callLauncherViewModel.setKotlinLauncher()
|
||||
} else {
|
||||
callLauncherViewModel.setJavaLauncher()
|
||||
}
|
||||
}
|
||||
|
||||
binding.run {
|
||||
tokenFunctionUrlText.setText(BuildConfig.TOKEN_FUNCTION_URL)
|
||||
|
||||
if (!deeplinkAcsToken.isNullOrEmpty()) {
|
||||
acsTokenText.setText(deeplinkAcsToken)
|
||||
} else {
|
||||
|
@ -98,31 +80,9 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
launchButton.setOnClickListener {
|
||||
launchButton.isEnabled = false
|
||||
callLauncherViewModel.doLaunch(
|
||||
tokenFunctionUrlText.text.toString(),
|
||||
acsTokenText.text.toString()
|
||||
)
|
||||
launch()
|
||||
}
|
||||
|
||||
tokenFunctionRadioButton.setOnClickListener {
|
||||
if (tokenFunctionRadioButton.isChecked) {
|
||||
tokenFunctionUrlText.requestFocus()
|
||||
tokenFunctionUrlText.isEnabled = true
|
||||
acsTokenText.isEnabled = false
|
||||
acsTokenRadioButton.isChecked = false
|
||||
callLauncherViewModel.useTokenFunction()
|
||||
}
|
||||
}
|
||||
acsTokenRadioButton.setOnClickListener {
|
||||
if (acsTokenRadioButton.isChecked) {
|
||||
acsTokenText.requestFocus()
|
||||
acsTokenText.isEnabled = true
|
||||
tokenFunctionUrlText.isEnabled = false
|
||||
tokenFunctionRadioButton.isChecked = false
|
||||
callLauncherViewModel.useAcsToken()
|
||||
}
|
||||
}
|
||||
groupCallRadioButton.setOnClickListener {
|
||||
if (groupCallRadioButton.isChecked) {
|
||||
groupIdOrTeamsMeetingLinkText.setText(BuildConfig.GROUP_CALL_ID)
|
||||
|
@ -136,12 +96,8 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
javaButton.setOnClickListener {
|
||||
callLauncherViewModel.setJavaLauncher()
|
||||
}
|
||||
|
||||
kotlinButton.setOnClickListener {
|
||||
callLauncherViewModel.setKotlinLauncher()
|
||||
showCallHistoryButton.setOnClickListener {
|
||||
showCallHistory()
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
@ -150,31 +106,17 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
versionText.text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
|
||||
}
|
||||
}
|
||||
|
||||
callLauncherViewModel.fetchResult.observe(this) {
|
||||
processResult(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
callLauncherViewModel.destroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
saveState(outState)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
// check whether new Activity instance was brought to top of stack,
|
||||
// so that finishing this will get us to the last viewed screen
|
||||
private fun shouldFinish() = BuildConfig.CHECK_TASK_ROOT && !isTaskRoot
|
||||
|
||||
fun showAlert(message: String) {
|
||||
private fun showAlert(message: String, title: String = "Alert") {
|
||||
runOnUiThread {
|
||||
val builder = AlertDialog.Builder(this).apply {
|
||||
setMessage(message)
|
||||
setTitle("Alert")
|
||||
setTitle(title)
|
||||
setPositiveButton("OK") { _, _ ->
|
||||
}
|
||||
}
|
||||
|
@ -182,31 +124,12 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun processResult(result: Result<CallingCompositeLauncher?>) {
|
||||
if (result.isFailure) {
|
||||
result.exceptionOrNull()?.let {
|
||||
if (it.message != null) {
|
||||
val causeMessage = it.cause?.message ?: ""
|
||||
showAlert(it.toString() + causeMessage)
|
||||
binding.launchButton.isEnabled = true
|
||||
} else {
|
||||
showAlert("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
result.getOrNull()?.let { launcherObject ->
|
||||
launch(launcherObject)
|
||||
binding.launchButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launch(launcher: CallingCompositeLauncher) {
|
||||
private fun launch() {
|
||||
val userName = binding.userNameText.text.toString()
|
||||
val acsToken = binding.acsTokenText.text.toString()
|
||||
|
||||
var groupId: UUID? = null
|
||||
if (binding.groupCallRadioButton.isChecked) {
|
||||
val groupId: UUID
|
||||
try {
|
||||
groupId =
|
||||
UUID.fromString(binding.groupIdOrTeamsMeetingLinkText.text.toString().trim())
|
||||
|
@ -215,32 +138,41 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
showAlert(message)
|
||||
return
|
||||
}
|
||||
launcher.launch(
|
||||
this@CallLauncherActivity,
|
||||
userName,
|
||||
groupId,
|
||||
null,
|
||||
::showAlert
|
||||
)
|
||||
}
|
||||
|
||||
var meetingLink: String? = null
|
||||
if (binding.teamsMeetingRadioButton.isChecked) {
|
||||
val meetingLink = binding.groupIdOrTeamsMeetingLinkText.text.toString()
|
||||
|
||||
meetingLink = binding.groupIdOrTeamsMeetingLinkText.text.toString()
|
||||
if (meetingLink.isBlank()) {
|
||||
val message = "Teams meeting link is invalid or empty."
|
||||
showAlert(message)
|
||||
return
|
||||
}
|
||||
|
||||
launcher.launch(
|
||||
this@CallLauncherActivity,
|
||||
userName,
|
||||
null,
|
||||
meetingLink,
|
||||
::showAlert,
|
||||
)
|
||||
}
|
||||
|
||||
callLauncherViewModel.launch(
|
||||
this@CallLauncherActivity,
|
||||
acsToken,
|
||||
userName,
|
||||
groupId,
|
||||
meetingLink,
|
||||
)
|
||||
}
|
||||
|
||||
private fun showCallHistory() {
|
||||
val history = callLauncherViewModel
|
||||
.getCallHistory(this@CallLauncherActivity)
|
||||
.sortedBy { it.callStartedOn }
|
||||
|
||||
val title = "Total calls: ${history.count()}"
|
||||
var message = "Last Call: none"
|
||||
history.lastOrNull()?.let {
|
||||
message = "Last Call: ${it.callStartedOn.format(DateTimeFormatter.ofPattern("MMM dd 'at' hh:mm"))}"
|
||||
it.callIds.forEach { callId ->
|
||||
message += "\nCallId: $callId"
|
||||
}
|
||||
}
|
||||
|
||||
showAlert(message, title)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
@ -256,12 +188,4 @@ class CallLauncherActivity : AppCompatActivity() {
|
|||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun saveState(outState: Bundle?) {
|
||||
outState?.putBoolean(
|
||||
isTokenFunctionOptionSelected,
|
||||
callLauncherViewModel.isTokenFunctionOptionSelected
|
||||
)
|
||||
outState?.putBoolean(isKotlinLauncherOptionSelected, callLauncherViewModel.isKotlinLauncher)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,30 +3,46 @@
|
|||
|
||||
package com.azure.android.communication.ui.callingcompositedemoapp
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.azure.android.communication.ui.calling.CallComposite
|
||||
import com.azure.android.communication.ui.calling.CallCompositeEventHandler
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeErrorEvent
|
||||
import com.microsoft.appcenter.utils.HandlerUtils.runOnUiThread
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
// Handles forwarding of error messages to the CallLauncherActivity
|
||||
//
|
||||
// CallLauncherActivity is loosely coupled and will detach the weak reference after disposed.
|
||||
class CallLauncherActivityErrorHandler(
|
||||
context: Context,
|
||||
private val callComposite: CallComposite,
|
||||
callLauncherActivity: CallLauncherActivity,
|
||||
) :
|
||||
CallCompositeEventHandler<CallCompositeErrorEvent> {
|
||||
|
||||
private val activityWr: WeakReference<CallLauncherActivity> =
|
||||
WeakReference(callLauncherActivity)
|
||||
private val activityWr: WeakReference<Context> = WeakReference(context)
|
||||
|
||||
override fun handle(it: CallCompositeErrorEvent) {
|
||||
println("================= application is logging exception =================")
|
||||
println("call id: " + (callComposite.debugInfo.lastCallId ?: ""))
|
||||
println(it.cause)
|
||||
println(it.errorCode)
|
||||
activityWr.get()
|
||||
?.showAlert("${it.errorCode} ${it.cause?.message}. Call id: ${callComposite.debugInfo.lastCallId ?: ""}")
|
||||
println("====================================================================")
|
||||
|
||||
activityWr.get()?.let { context ->
|
||||
val lastCallId = callComposite.getDebugInfo(context).callHistoryRecords
|
||||
.lastOrNull()?.callIds?.lastOrNull()?.toString() ?: ""
|
||||
|
||||
println("================= application is logging exception =================")
|
||||
println("call id: $lastCallId")
|
||||
println(it.cause)
|
||||
println(it.errorCode)
|
||||
|
||||
runOnUiThread {
|
||||
val builder = AlertDialog.Builder(context).apply {
|
||||
setMessage("${it.errorCode} ${it.cause?.message}. Call id: $lastCallId")
|
||||
setTitle("Alert")
|
||||
setPositiveButton("OK") { _, _ ->
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
println("====================================================================")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,75 +3,90 @@
|
|||
|
||||
package com.azure.android.communication.ui.callingcompositedemoapp
|
||||
|
||||
import android.webkit.URLUtil
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.launcher.CallingCompositeJavaLauncher
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.launcher.CallingCompositeKotlinLauncher
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.launcher.CallingCompositeLauncher
|
||||
import com.azure.android.communication.ui.demoapp.UrlTokenFetcher
|
||||
import java.util.concurrent.Callable
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.ui.calling.CallComposite
|
||||
import com.azure.android.communication.ui.calling.CallCompositeBuilder
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeCallHistoryRecord
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeJoinLocator
|
||||
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.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSetupScreenViewData
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.AdditionalFeatures
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures
|
||||
import java.util.UUID
|
||||
|
||||
class CallLauncherViewModel : ViewModel() {
|
||||
private var token: String? = null
|
||||
private val fetchResultInternal = MutableLiveData<Result<CallingCompositeLauncher?>>()
|
||||
|
||||
val fetchResult: LiveData<Result<CallingCompositeLauncher?>> = fetchResultInternal
|
||||
var isKotlinLauncher = true; private set
|
||||
var isTokenFunctionOptionSelected = false; private set
|
||||
fun launch(
|
||||
context: Context,
|
||||
|
||||
private fun launcher(tokenRefresher: Callable<String>) = if (isKotlinLauncher) {
|
||||
CallingCompositeKotlinLauncher(tokenRefresher)
|
||||
} else {
|
||||
CallingCompositeJavaLauncher(tokenRefresher)
|
||||
}
|
||||
acsToken: String,
|
||||
displayName: String,
|
||||
groupId: UUID?,
|
||||
meetingLink: String?,
|
||||
) {
|
||||
val callComposite = createCallComposite(context)
|
||||
callComposite.addOnErrorEventHandler(CallLauncherActivityErrorHandler(context, callComposite))
|
||||
|
||||
fun destroy() {
|
||||
fetchResultInternal.value = Result.success(null)
|
||||
}
|
||||
|
||||
fun setJavaLauncher() {
|
||||
isKotlinLauncher = false
|
||||
}
|
||||
|
||||
fun setKotlinLauncher() {
|
||||
isKotlinLauncher = true
|
||||
}
|
||||
|
||||
fun useTokenFunction() {
|
||||
isTokenFunctionOptionSelected = true
|
||||
}
|
||||
|
||||
fun useAcsToken() {
|
||||
isTokenFunctionOptionSelected = false
|
||||
}
|
||||
|
||||
fun doLaunch(tokenFunctionURL: String, acsToken: String) {
|
||||
when {
|
||||
isTokenFunctionOptionSelected && urlIsValid(tokenFunctionURL) -> {
|
||||
token = null
|
||||
fetchResultInternal.postValue(
|
||||
Result.success(
|
||||
launcher(
|
||||
UrlTokenFetcher(tokenFunctionURL)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
acsToken.isNotBlank() -> {
|
||||
token = acsToken
|
||||
fetchResultInternal.postValue(
|
||||
Result.success(launcher(CachedTokenFetcher(acsToken)))
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
fetchResultInternal.postValue(
|
||||
Result.failure(IllegalStateException("Invalid Token function URL or acs Token"))
|
||||
)
|
||||
}
|
||||
if (SettingsFeatures.getRemoteParticipantPersonaInjectionSelection()) {
|
||||
callComposite.addOnRemoteParticipantJoinedEventHandler(
|
||||
RemoteParticipantJoinedHandler(callComposite, context)
|
||||
)
|
||||
}
|
||||
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions({ acsToken }, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
|
||||
val locator: CallCompositeJoinLocator =
|
||||
if (groupId != null) CallCompositeGroupCallLocator(groupId)
|
||||
else CallCompositeTeamsMeetingLinkLocator(meetingLink)
|
||||
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(locator, communicationTokenCredential, displayName)
|
||||
|
||||
val localOptions = CallCompositeLocalOptions()
|
||||
.setParticipantViewData(SettingsFeatures.getParticipantViewData(context.applicationContext))
|
||||
.setSetupScreenViewData(
|
||||
CallCompositeSetupScreenViewData()
|
||||
.setTitle(SettingsFeatures.getTitle())
|
||||
.setSubtitle(SettingsFeatures.getSubtitle())
|
||||
)
|
||||
|
||||
callComposite.launch(context, remoteOptions, localOptions)
|
||||
}
|
||||
|
||||
private fun urlIsValid(url: String) = url.isNotBlank() && URLUtil.isValidUrl(url.trim())
|
||||
fun getCallHistory(context: Context): List<CallCompositeCallHistoryRecord> {
|
||||
return (callComposite ?: createCallComposite(context)).getDebugInfo(context).callHistoryRecords
|
||||
}
|
||||
|
||||
private fun createCallComposite(context: Context): CallComposite {
|
||||
SettingsFeatures.initialize(context.applicationContext)
|
||||
|
||||
val selectedLanguage = SettingsFeatures.language()
|
||||
val locale = selectedLanguage?.let { SettingsFeatures.locale(it) }
|
||||
|
||||
val callCompositeBuilder = CallCompositeBuilder()
|
||||
.localization(CallCompositeLocalizationOptions(locale!!, SettingsFeatures.getLayoutDirection()))
|
||||
|
||||
if (AdditionalFeatures.secondaryThemeFeature.active)
|
||||
callCompositeBuilder.theme(R.style.MyCompany_Theme_Calling)
|
||||
|
||||
val callComposite = callCompositeBuilder.build()
|
||||
|
||||
// For test purposes we will keep a static ref to CallComposite
|
||||
CallLauncherViewModel.callComposite = callComposite
|
||||
return callComposite
|
||||
}
|
||||
|
||||
companion object {
|
||||
var callComposite: CallComposite? = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package com.azure.android.communication.ui.callingcompositedemoapp
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import com.azure.android.communication.common.CommunicationIdentifier
|
||||
import com.azure.android.communication.common.CommunicationUserIdentifier
|
||||
|
@ -18,13 +19,13 @@ import java.net.URL
|
|||
|
||||
class RemoteParticipantJoinedHandler(
|
||||
private val callComposite: CallComposite,
|
||||
private val callLauncherActivity: CallLauncherActivity,
|
||||
private val context: Context,
|
||||
) :
|
||||
CallCompositeEventHandler<CallCompositeRemoteParticipantJoinedEvent> {
|
||||
|
||||
override fun handle(event: CallCompositeRemoteParticipantJoinedEvent) {
|
||||
event.identifiers.forEach { communicationIdentifier ->
|
||||
if (callLauncherActivity.resources.getBoolean(R.bool.remote_url_persona_injection)) {
|
||||
if (context.resources.getBoolean(R.bool.remote_url_persona_injection)) {
|
||||
getImageFromServer(communicationIdentifier)
|
||||
} else {
|
||||
selectRandomAvatar(communicationIdentifier)
|
||||
|
@ -90,12 +91,12 @@ class RemoteParticipantJoinedHandler(
|
|||
)
|
||||
images[number].let {
|
||||
val bitMap =
|
||||
BitmapFactory.decodeResource(callLauncherActivity.resources, it)
|
||||
BitmapFactory.decodeResource(context.resources, it)
|
||||
val result = callComposite.setRemoteParticipantViewData(
|
||||
communicationIdentifier,
|
||||
CallCompositeParticipantViewData()
|
||||
.setDisplayName(
|
||||
callLauncherActivity.resources.getResourceEntryName(
|
||||
context.resources.getResourceEntryName(
|
||||
it
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.callingcompositedemoapp.launcher;
|
||||
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential;
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions;
|
||||
import com.azure.android.communication.ui.calling.CallComposite;
|
||||
import com.azure.android.communication.ui.calling.CallCompositeBuilder;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeJoinLocator;
|
||||
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.CallCompositeRemoteOptions;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSetupScreenViewData;
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator;
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.CallLauncherActivity;
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.CallLauncherActivityErrorHandler;
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.R;
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.RemoteParticipantJoinedHandler;
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.AdditionalFeatures;
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
public class CallingCompositeJavaLauncher implements CallingCompositeLauncher {
|
||||
private static CallComposite callComposite;
|
||||
private final Callable<String> tokenRefresher;
|
||||
|
||||
public CallingCompositeJavaLauncher(final Callable<String> tokenRefresher) {
|
||||
this.tokenRefresher = tokenRefresher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(final CallLauncherActivity callLauncherActivity,
|
||||
final String displayName,
|
||||
final UUID groupId,
|
||||
final String meetingLink,
|
||||
final Function1<? super String, Unit> showAlert) {
|
||||
|
||||
final CallCompositeBuilder builder = new CallCompositeBuilder();
|
||||
|
||||
SettingsFeatures.initialize(callLauncherActivity.getApplicationContext());
|
||||
|
||||
final String selectedLanguage = SettingsFeatures.language();
|
||||
final Locale locale = SettingsFeatures.locale(selectedLanguage);
|
||||
|
||||
builder.localization(new CallCompositeLocalizationOptions(locale,
|
||||
SettingsFeatures.getLayoutDirection()));
|
||||
|
||||
if (AdditionalFeatures.Companion.getSecondaryThemeFeature().getActive()) {
|
||||
builder.theme(R.style.MyCompany_Theme_Calling);
|
||||
}
|
||||
|
||||
final CallComposite callComposite = builder.build();
|
||||
callComposite.addOnErrorEventHandler(new CallLauncherActivityErrorHandler(callComposite, callLauncherActivity));
|
||||
|
||||
if (SettingsFeatures.getRemoteParticipantPersonaInjectionSelection()) {
|
||||
callComposite.addOnRemoteParticipantJoinedEventHandler(
|
||||
new RemoteParticipantJoinedHandler(callComposite, callLauncherActivity));
|
||||
}
|
||||
|
||||
final CommunicationTokenRefreshOptions communicationTokenRefreshOptions =
|
||||
new CommunicationTokenRefreshOptions(tokenRefresher, true);
|
||||
final CommunicationTokenCredential communicationTokenCredential =
|
||||
new CommunicationTokenCredential(communicationTokenRefreshOptions);
|
||||
|
||||
final CallCompositeJoinLocator locator = groupId != null
|
||||
? new CallCompositeGroupCallLocator(groupId)
|
||||
: new CallCompositeTeamsMeetingLinkLocator(meetingLink);
|
||||
|
||||
final CallCompositeRemoteOptions remoteOptions =
|
||||
new CallCompositeRemoteOptions(locator, communicationTokenCredential, displayName);
|
||||
|
||||
final CallCompositeLocalOptions localOptions = new CallCompositeLocalOptions()
|
||||
.setParticipantViewData(SettingsFeatures
|
||||
.getParticipantViewData(callLauncherActivity.getApplicationContext()))
|
||||
.setSetupScreenViewData(
|
||||
new CallCompositeSetupScreenViewData()
|
||||
.setTitle(SettingsFeatures.getTitle())
|
||||
.setSubtitle(SettingsFeatures.getSubtitle()));
|
||||
|
||||
callComposite.launch(callLauncherActivity, remoteOptions, localOptions);
|
||||
// For test purposes we will keep a static ref to CallComposite
|
||||
this.callComposite = callComposite;
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.azure.android.communication.ui.callingcompositedemoapp.launcher
|
||||
|
||||
import com.azure.android.communication.common.CommunicationTokenCredential
|
||||
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
|
||||
import com.azure.android.communication.ui.calling.CallComposite
|
||||
import com.azure.android.communication.ui.calling.CallCompositeBuilder
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeGroupCallLocator
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeJoinLocator
|
||||
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.CallCompositeRemoteOptions
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeSetupScreenViewData
|
||||
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.CallLauncherActivity
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.CallLauncherActivityErrorHandler
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.R
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.RemoteParticipantJoinedHandler
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.AdditionalFeatures
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.getLayoutDirection
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.getParticipantViewData
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.getRemoteParticipantPersonaInjectionSelection
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.getSubtitle
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.getTitle
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.initialize
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.language
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.features.SettingsFeatures.Companion.locale
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class CallingCompositeKotlinLauncher(private val tokenRefresher: Callable<String>) :
|
||||
CallingCompositeLauncher {
|
||||
|
||||
override fun launch(
|
||||
callLauncherActivity: CallLauncherActivity,
|
||||
displayName: String,
|
||||
groupId: UUID?,
|
||||
meetingLink: String?,
|
||||
showAlert: ((String) -> Unit)?,
|
||||
) {
|
||||
initialize(callLauncherActivity.applicationContext)
|
||||
val selectedLanguage = language()
|
||||
val locale = selectedLanguage?.let { locale(it) }
|
||||
|
||||
val callComposite =
|
||||
if (AdditionalFeatures.secondaryThemeFeature.active)
|
||||
CallCompositeBuilder().theme(R.style.MyCompany_Theme_Calling)
|
||||
.localization(CallCompositeLocalizationOptions(locale!!, getLayoutDirection()))
|
||||
.build()
|
||||
else
|
||||
CallCompositeBuilder()
|
||||
.localization(CallCompositeLocalizationOptions(locale!!, getLayoutDirection()))
|
||||
.build()
|
||||
|
||||
callComposite.addOnErrorEventHandler(
|
||||
CallLauncherActivityErrorHandler(
|
||||
callComposite,
|
||||
callLauncherActivity
|
||||
)
|
||||
)
|
||||
|
||||
if (getRemoteParticipantPersonaInjectionSelection()) {
|
||||
callComposite.addOnRemoteParticipantJoinedEventHandler(
|
||||
RemoteParticipantJoinedHandler(callComposite, callLauncherActivity)
|
||||
)
|
||||
}
|
||||
|
||||
val communicationTokenRefreshOptions =
|
||||
CommunicationTokenRefreshOptions(tokenRefresher, true)
|
||||
val communicationTokenCredential =
|
||||
CommunicationTokenCredential(communicationTokenRefreshOptions)
|
||||
|
||||
val locator: CallCompositeJoinLocator =
|
||||
if (groupId != null) CallCompositeGroupCallLocator(groupId)
|
||||
else CallCompositeTeamsMeetingLinkLocator(meetingLink)
|
||||
|
||||
val remoteOptions =
|
||||
CallCompositeRemoteOptions(locator, communicationTokenCredential, displayName)
|
||||
|
||||
val localOptions = CallCompositeLocalOptions()
|
||||
.setParticipantViewData(getParticipantViewData(callLauncherActivity.applicationContext))
|
||||
.setSetupScreenViewData(
|
||||
CallCompositeSetupScreenViewData()
|
||||
.setTitle(getTitle())
|
||||
.setSubtitle(getSubtitle())
|
||||
)
|
||||
|
||||
callComposite.launch(callLauncherActivity, remoteOptions, localOptions)
|
||||
|
||||
// For test purposes we will keep a static ref to CallComposite
|
||||
CallingCompositeKotlinLauncher.callComposite = callComposite
|
||||
}
|
||||
|
||||
companion object {
|
||||
var callComposite: CallComposite? = null
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
package com.azure.android.communication.ui.callingcompositedemoapp.launcher;
|
||||
|
||||
import com.azure.android.communication.ui.callingcompositedemoapp.CallLauncherActivity;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
|
||||
public interface CallingCompositeLauncher {
|
||||
void launch(CallLauncherActivity callLauncherActivity,
|
||||
String userName,
|
||||
UUID groupId,
|
||||
String meetingLink,
|
||||
Function1<? super String, Unit> showAlert);
|
||||
}
|
|
@ -17,37 +17,6 @@
|
|||
tools:context=".CallLauncherActivity"
|
||||
>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/tokenFunctionRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/token_function_radio_button_text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tokenFunctionUrlText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:hint="@string/token_url_hint"
|
||||
android:inputType="textUri"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tokenFunctionRadioButton"
|
||||
/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/acsTokenRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/acs_token_radio_button_text"
|
||||
app:layout_constraintStart_toStartOf="@id/tokenFunctionUrlText"
|
||||
app:layout_constraintTop_toBottomOf="@id/tokenFunctionUrlText"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/acsTokenText"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -56,7 +25,7 @@
|
|||
android:hint="@string/acs_token_hint"
|
||||
android:inputType="textUri"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/acsTokenRadioButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
|
@ -104,43 +73,15 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/teamsMeetingRadioButton"
|
||||
/>
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/javaOrKotlinContainer"
|
||||
<Button
|
||||
android:id="@+id/showCallHistoryButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintStart_toStartOf="@id/groupIdOrTeamsMeetingLinkText"
|
||||
app:layout_constraintTop_toBottomOf="@id/groupIdOrTeamsMeetingLinkText"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/launch_type_text"
|
||||
android:textSize="17sp"
|
||||
/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/kotlinButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:checked="true"
|
||||
android:padding="5dp"
|
||||
android:text="@string/kotlin"
|
||||
android:textSize="17sp"
|
||||
/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/javaButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp"
|
||||
android:text="@string/java"
|
||||
android:textSize="17sp"
|
||||
/>
|
||||
</RadioGroup>
|
||||
android:text="call history"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/groupIdOrTeamsMeetingLinkText"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/launchButton"
|
||||
|
@ -149,7 +90,7 @@
|
|||
android:text="@string/launch_button_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/javaOrKotlinContainer"
|
||||
app:layout_constraintTop_toBottomOf="@+id/showCallHistoryButton"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
|
|
Загрузка…
Ссылка в новой задаче