diff --git a/.changeset/sample-apps-token-source.md b/.changeset/sample-apps-token-source.md new file mode 100644 index 000000000..9e5542f71 --- /dev/null +++ b/.changeset/sample-apps-token-source.md @@ -0,0 +1,4 @@ +--- +--- + +Use the TokenSource implementations in the sample apps, with a development token server option. Sample-app-only change; no SDK release needed. diff --git a/sample-app-common/build.gradle b/sample-app-common/build.gradle index 851579647..ce856ad54 100644 --- a/sample-app-common/build.gradle +++ b/sample-app-common/build.gradle @@ -12,8 +12,13 @@ def getDefaultToken() { return hasProperty('livekitSampleToken') ? livekitSampleToken : "" } +def getDefaultTokenServerId() { + return hasProperty('livekitSampleTokenServerId') ? livekitSampleTokenServerId : "" +} + final url = getDefaultUrl() final token = getDefaultToken() +final tokenServerId = getDefaultTokenServerId() android { namespace "io.livekit.android.sample.common" @@ -34,11 +39,13 @@ android { buildConfigField "String", "DEFAULT_URL", "\"$url\"" buildConfigField "String", "DEFAULT_TOKEN", "\"$token\"" + buildConfigField "String", "DEFAULT_TOKEN_SERVER_ID", "\"$tokenServerId\"" } debug { buildConfigField "String", "DEFAULT_URL", "\"$url\"" buildConfigField "String", "DEFAULT_TOKEN", "\"$token\"" + buildConfigField "String", "DEFAULT_TOKEN_SERVER_ID", "\"$tokenServerId\"" } } buildFeatures { diff --git a/sample-app-common/src/main/java/io/livekit/android/sample/CallViewModel.kt b/sample-app-common/src/main/java/io/livekit/android/sample/CallViewModel.kt index b218278af..1f6f9bceb 100644 --- a/sample-app-common/src/main/java/io/livekit/android/sample/CallViewModel.kt +++ b/sample-app-common/src/main/java/io/livekit/android/sample/CallViewModel.kt @@ -51,7 +51,14 @@ import io.livekit.android.room.track.screencapture.ScreenCaptureParams import io.livekit.android.room.track.video.CameraCapturerUtils import io.livekit.android.rpc.RpcError import io.livekit.android.sample.model.StressTest +import io.livekit.android.sample.model.TokenSourceArgs import io.livekit.android.sample.service.ForegroundService +import io.livekit.android.token.ConfigurableTokenSource +import io.livekit.android.token.FixedTokenSource +import io.livekit.android.token.TokenRequestOptions +import io.livekit.android.token.TokenSource +import io.livekit.android.token.TokenSourceResponse +import io.livekit.android.token.cached import io.livekit.android.util.LKLog import io.livekit.android.util.flow import kotlinx.coroutines.Dispatchers @@ -68,8 +75,7 @@ import livekit.org.webrtc.CameraXHelper @OptIn(ExperimentalCamera2Interop::class) class CallViewModel( - val url: String, - val token: String, + val tokenSourceArgs: TokenSourceArgs, application: Application, val e2ee: Boolean = false, val e2eeKey: String? = "", @@ -149,6 +155,20 @@ class CallViewModel( private val mutableHandlers = MutableStateFlow>(emptyList()) val handlers: StateFlow> = mutableHandlers + /** + * The token source used to fetch connection details for the room. + * + * Created once and (for the development token server) wrapped with [cached], so + * reconnects reuse the same credentials until the token expires. Otherwise the + * development token server would issue new random values on every fetch when no + * explicit room name/participant identity is set, causing reconnects to join a + * different room. + */ + private val tokenSource: TokenSource = when (tokenSourceArgs) { + is TokenSourceArgs.Literal -> TokenSource.fromLiteral(tokenSourceArgs.url, tokenSourceArgs.token) + is TokenSourceArgs.DevTokenServer -> TokenSource.fromDevelopmentTokenServer(tokenSourceArgs.tokenServerId).cached() + } + init { CameraXHelper.createCameraProvider(ProcessLifecycleOwner.get()).let { @@ -255,12 +275,28 @@ class CallViewModel( } } + /** + * Fetches the connection details used to connect to the room from [tokenSource]. + */ + private suspend fun fetchConnectionDetails(): Result = when (tokenSourceArgs) { + is TokenSourceArgs.Literal -> (tokenSource as FixedTokenSource).fetch() + is TokenSourceArgs.DevTokenServer -> (tokenSource as ConfigurableTokenSource).fetch( + TokenRequestOptions( + roomName = tokenSourceArgs.roomName, + participantName = tokenSourceArgs.participantName, + participantIdentity = tokenSourceArgs.participantIdentity, + agentName = tokenSourceArgs.agentName, + ), + ) + } + private suspend fun connectToRoom() { try { room.e2eeOptions = getE2EEOptions() + val connectionDetails = fetchConnectionDetails().getOrThrow() room.connect( - url = url, - token = token, + url = connectionDetails.serverUrl, + token = connectionDetails.participantToken, ) mutableEnhancedNsEnabled.postValue(room.audioProcessorIsEnabled) @@ -495,6 +531,8 @@ class CallViewModel( private suspend fun quickConnectToRoom(token: String) { try { + // The stress test is only reachable with literal credentials. + val url = (tokenSourceArgs as TokenSourceArgs.Literal).url room.connect( url = url, token = token, diff --git a/sample-app-common/src/main/java/io/livekit/android/sample/MainViewModel.kt b/sample-app-common/src/main/java/io/livekit/android/sample/MainViewModel.kt index 8afad859f..3ce0981b9 100644 --- a/sample-app-common/src/main/java/io/livekit/android/sample/MainViewModel.kt +++ b/sample-app-common/src/main/java/io/livekit/android/sample/MainViewModel.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.livekit.android.sample import android.app.Application @@ -12,6 +28,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { fun getSavedUrl() = preferences.getString(PREFERENCES_KEY_URL, URL) as String fun getSavedToken() = preferences.getString(PREFERENCES_KEY_TOKEN, TOKEN) as String + fun getSavedTokenServerId() = preferences.getString(PREFERENCES_KEY_TOKEN_SERVER_ID, TOKEN_SERVER_ID) as String fun getE2EEOptionsOn() = preferences.getBoolean(PREFERENCES_KEY_E2EE_ON, false) fun getSavedE2EEKey() = preferences.getString(PREFERENCES_KEY_E2EE_KEY, E2EE_KEY) as String @@ -27,6 +44,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } } + fun setSavedTokenServerId(tokenServerId: String) { + preferences.edit { + putString(PREFERENCES_KEY_TOKEN_SERVER_ID, tokenServerId) + } + } + fun setSavedE2EEOn(yesno: Boolean) { preferences.edit { putBoolean(PREFERENCES_KEY_E2EE_ON, yesno) @@ -46,11 +69,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { companion object { private const val PREFERENCES_KEY_URL = "url" private const val PREFERENCES_KEY_TOKEN = "token" + private const val PREFERENCES_KEY_TOKEN_SERVER_ID = "token_server_id" private const val PREFERENCES_KEY_E2EE_ON = "enable_e2ee" private const val PREFERENCES_KEY_E2EE_KEY = "e2ee_key" const val URL = BuildConfig.DEFAULT_URL const val TOKEN = BuildConfig.DEFAULT_TOKEN + const val TOKEN_SERVER_ID = BuildConfig.DEFAULT_TOKEN_SERVER_ID const val E2EE_KEY = "12345678" } } diff --git a/sample-app-common/src/main/java/io/livekit/android/sample/model/TokenSourceArgs.kt b/sample-app-common/src/main/java/io/livekit/android/sample/model/TokenSourceArgs.kt new file mode 100644 index 000000000..afc6e14de --- /dev/null +++ b/sample-app-common/src/main/java/io/livekit/android/sample/model/TokenSourceArgs.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.livekit.android.sample.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + * Describes which token source to connect with, in a parcelable form + * so it can be passed through activity intents. + */ +sealed class TokenSourceArgs : Parcelable { + + @Parcelize + data class Literal( + val url: String, + val token: String, + ) : TokenSourceArgs() + + @Parcelize + data class DevTokenServer( + val tokenServerId: String, + val roomName: String? = null, + val participantName: String? = null, + val participantIdentity: String? = null, + val agentName: String? = null, + ) : TokenSourceArgs() +} diff --git a/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt b/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt index 82523a6be..da4f81081 100644 --- a/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt +++ b/sample-app-compose/src/main/java/io/livekit/android/composesample/CallActivity.kt @@ -76,6 +76,7 @@ import io.livekit.android.room.participant.Participant import io.livekit.android.sample.CallViewModel import io.livekit.android.sample.common.R import io.livekit.android.sample.model.StressTest +import io.livekit.android.sample.model.TokenSourceArgs import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -86,8 +87,7 @@ class CallActivity : AppCompatActivity() { val args = intent.getParcelableExtra(KEY_ARGS) ?: throw NullPointerException("args is null!") CallViewModel( - url = args.url, - token = args.token, + tokenSourceArgs = args.tokenSourceArgs, e2ee = args.e2eeOn, e2eeKey = args.e2eeKey, stressTest = args.stressTest, @@ -511,8 +511,7 @@ class CallActivity : AppCompatActivity() { @Parcelize data class BundleArgs( - val url: String, - val token: String, + val tokenSourceArgs: TokenSourceArgs, val e2eeKey: String, val e2eeOn: Boolean, val stressTest: StressTest, diff --git a/sample-app-compose/src/main/java/io/livekit/android/composesample/MainActivity.kt b/sample-app-compose/src/main/java/io/livekit/android/composesample/MainActivity.kt index f0e4a506c..194b5ba70 100644 --- a/sample-app-compose/src/main/java/io/livekit/android/composesample/MainActivity.kt +++ b/sample-app-compose/src/main/java/io/livekit/android/composesample/MainActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 LiveKit, Inc. + * Copyright 2023-2026 LiveKit, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,8 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Surface import androidx.compose.material.Switch +import androidx.compose.material.Tab +import androidx.compose.material.TabRow import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -47,17 +49,22 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.google.accompanist.pager.ExperimentalPagerApi import io.livekit.android.composesample.ui.theme.AppTheme import io.livekit.android.sample.MainViewModel import io.livekit.android.sample.common.R import io.livekit.android.sample.model.StressTest +import io.livekit.android.sample.model.TokenSourceArgs import io.livekit.android.sample.util.requestNeededPermissions -@ExperimentalPagerApi +private enum class TokenMode { + Literal, + DevServer, +} + class MainActivity : ComponentActivity() { private val viewModel by viewModels() @@ -69,15 +76,15 @@ class MainActivity : ComponentActivity() { MainContent( defaultUrl = viewModel.getSavedUrl(), defaultToken = viewModel.getSavedToken(), + defaultTokenServerId = viewModel.getSavedTokenServerId(), defaultE2eeKey = viewModel.getSavedE2EEKey(), defaultE2eeOn = viewModel.getE2EEOptionsOn(), - onConnect = { url, token, e2eeKey, e2eeOn, stressTest -> + onConnect = { tokenSourceArgs, e2eeKey, e2eeOn, stressTest -> val intent = Intent(this@MainActivity, CallActivity::class.java).apply { putExtra( CallActivity.KEY_ARGS, CallActivity.BundleArgs( - url, - token, + tokenSourceArgs, e2eeKey, e2eeOn, stressTest, @@ -86,9 +93,10 @@ class MainActivity : ComponentActivity() { } startActivity(intent) }, - onSave = { url, token, e2eeKey, e2eeOn -> + onSave = { url, token, tokenServerId, e2eeKey, e2eeOn -> viewModel.setSavedUrl(url) viewModel.setSavedToken(token) + viewModel.setSavedTokenServerId(tokenServerId) viewModel.setSavedE2EEKey(e2eeKey) viewModel.setSavedE2EEOn(e2eeOn) @@ -119,15 +127,22 @@ class MainActivity : ComponentActivity() { defaultUrl: String = MainViewModel.URL, defaultToken: String = MainViewModel.TOKEN, defaultSecondToken: String = MainViewModel.TOKEN, + defaultTokenServerId: String = MainViewModel.TOKEN_SERVER_ID, defaultE2eeKey: String = MainViewModel.E2EE_KEY, defaultE2eeOn: Boolean = false, - onConnect: (url: String, token: String, e2eeKey: String, e2eeOn: Boolean, stressTest: StressTest) -> Unit = { _, _, _, _, _ -> }, - onSave: (url: String, token: String, e2eeKey: String, e2eeOn: Boolean) -> Unit = { _, _, _, _ -> }, + onConnect: (tokenSourceArgs: TokenSourceArgs, e2eeKey: String, e2eeOn: Boolean, stressTest: StressTest) -> Unit = { _, _, _, _ -> }, + onSave: (url: String, token: String, tokenServerId: String, e2eeKey: String, e2eeOn: Boolean) -> Unit = { _, _, _, _, _ -> }, onReset: () -> Unit = {}, ) { AppTheme { + var tokenMode by remember { mutableStateOf(TokenMode.Literal) } var url by remember { mutableStateOf(defaultUrl) } var token by remember { mutableStateOf(defaultToken) } + var tokenServerId by remember { mutableStateOf(defaultTokenServerId) } + var roomName by remember { mutableStateOf("") } + var participantName by remember { mutableStateOf("") } + var participantIdentity by remember { mutableStateOf("") } + var agentName by remember { mutableStateOf("") } var e2eeKey by remember { mutableStateOf(defaultE2eeKey) } var e2eeOn by remember { mutableStateOf(defaultE2eeOn) } var stressTest by remember { mutableStateOf(false) } @@ -154,19 +169,77 @@ class MainActivity : ComponentActivity() { contentDescription = "", ) Spacer(modifier = Modifier.height(20.dp)) - OutlinedTextField( - value = url, - onValueChange = { url = it }, - label = { Text("URL") }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(modifier = Modifier.height(20.dp)) - OutlinedTextField( - value = token, - onValueChange = { token = it }, - label = { Text("Token") }, - modifier = Modifier.fillMaxWidth(), - ) + TabRow( + selectedTabIndex = tokenMode.ordinal, + backgroundColor = Color.Transparent, + ) { + Tab( + selected = tokenMode == TokenMode.Literal, + onClick = { tokenMode = TokenMode.Literal }, + text = { Text("URL & Token") }, + ) + Tab( + selected = tokenMode == TokenMode.DevServer, + onClick = { tokenMode = TokenMode.DevServer }, + text = { Text("Dev Token Server") }, + ) + } + when (tokenMode) { + TokenMode.Literal -> { + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = url, + onValueChange = { url = it }, + label = { Text("URL") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = token, + onValueChange = { token = it }, + label = { Text("Token") }, + modifier = Modifier.fillMaxWidth(), + ) + } + + TokenMode.DevServer -> { + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = tokenServerId, + onValueChange = { tokenServerId = it }, + label = { Text("Token Server ID") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = roomName, + onValueChange = { roomName = it }, + label = { Text("Room Name (optional)") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = participantName, + onValueChange = { participantName = it }, + label = { Text("Participant Name (optional)") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = participantIdentity, + onValueChange = { participantIdentity = it }, + label = { Text("Participant Identity (optional)") }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(20.dp)) + OutlinedTextField( + value = agentName, + onValueChange = { agentName = it }, + label = { Text("Agent Name (optional)") }, + modifier = Modifier.fillMaxWidth(), + ) + } + } if (e2eeOn) { Spacer(modifier = Modifier.height(20.dp)) @@ -179,7 +252,7 @@ class MainActivity : ComponentActivity() { ) } - if (stressTest) { + if (tokenMode == TokenMode.Literal && stressTest) { Spacer(modifier = Modifier.height(20.dp)) OutlinedTextField( value = secondToken, @@ -203,34 +276,51 @@ class MainActivity : ComponentActivity() { ) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - Text("Stress test") - Switch( - checked = stressTest, - onCheckedChange = { stressTest = it }, - ) + if (tokenMode == TokenMode.Literal) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + Text("Stress test") + Switch( + checked = stressTest, + onCheckedChange = { stressTest = it }, + ) + } } Spacer(modifier = Modifier.height(40.dp)) Button( onClick = { - val stressTestCmd = if (stressTest) { - StressTest.SwitchRoom(token, secondToken) - } else { - StressTest.None + when (tokenMode) { + TokenMode.Literal -> { + val stressTestCmd = if (stressTest) { + StressTest.SwitchRoom(token, secondToken) + } else { + StressTest.None + } + onConnect(TokenSourceArgs.Literal(url, token), e2eeKey, e2eeOn, stressTestCmd) + } + + TokenMode.DevServer -> { + val devTokenServerArgs = TokenSourceArgs.DevTokenServer( + tokenServerId = tokenServerId, + roomName = roomName.ifBlank { null }, + participantName = participantName.ifBlank { null }, + participantIdentity = participantIdentity.ifBlank { null }, + agentName = agentName.ifBlank { null }, + ) + onConnect(devTokenServerArgs, e2eeKey, e2eeOn, StressTest.None) + } } - onConnect(url, token, e2eeKey, e2eeOn, stressTestCmd) }, ) { Text("Connect") } Spacer(modifier = Modifier.height(20.dp)) - Button(onClick = { onSave(url, token, e2eeKey, e2eeOn) }) { + Button(onClick = { onSave(url, token, tokenServerId, e2eeKey, e2eeOn) }) { Text("Save Values") } @@ -240,6 +330,7 @@ class MainActivity : ComponentActivity() { onReset() url = MainViewModel.URL token = MainViewModel.TOKEN + tokenServerId = MainViewModel.TOKEN_SERVER_ID }, ) { Text("Reset Values") diff --git a/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt b/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt index 2cd2b85d5..98ee95708 100644 --- a/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt +++ b/sample-app/src/main/java/io/livekit/android/sample/CallActivity.kt @@ -40,6 +40,7 @@ import io.livekit.android.sample.dialog.showAudioProcessorSwitchDialog import io.livekit.android.sample.dialog.showDebugMenuDialog import io.livekit.android.sample.dialog.showSelectAudioDeviceDialog import io.livekit.android.sample.model.StressTest +import io.livekit.android.sample.model.TokenSourceArgs import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -51,8 +52,7 @@ class CallActivity : AppCompatActivity() { ?: throw NullPointerException("args is null!") CallViewModel( - url = args.url, - token = args.token, + tokenSourceArgs = args.tokenSourceArgs, e2ee = args.e2eeOn, e2eeKey = args.e2eeKey, stressTest = args.stressTest, @@ -262,8 +262,7 @@ class CallActivity : AppCompatActivity() { @Parcelize data class BundleArgs( - val url: String, - val token: String, + val tokenSourceArgs: TokenSourceArgs, val e2eeKey: String, val e2eeOn: Boolean, val stressTest: StressTest, diff --git a/sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt b/sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt index 409d24a16..a8d619779 100644 --- a/sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt +++ b/sample-app/src/main/java/io/livekit/android/sample/MainActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 LiveKit, Inc. + * Copyright 2024-2026 LiveKit, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,14 @@ package io.livekit.android.sample import android.content.Intent import android.os.Bundle import android.text.SpannableStringBuilder +import android.view.View import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.tabs.TabLayout import io.livekit.android.sample.databinding.MainActivityBinding import io.livekit.android.sample.model.StressTest +import io.livekit.android.sample.model.TokenSourceArgs import io.livekit.android.sample.util.requestNeededPermissions class MainActivity : AppCompatActivity() { @@ -36,21 +39,49 @@ class MainActivity : AppCompatActivity() { val urlString = viewModel.getSavedUrl() val tokenString = viewModel.getSavedToken() + val tokenServerIdString = viewModel.getSavedTokenServerId() val e2EEOn = viewModel.getE2EEOptionsOn() val e2EEKey = viewModel.getSavedE2EEKey() binding.run { url.editText?.text = SpannableStringBuilder(urlString) token.editText?.text = SpannableStringBuilder(tokenString) + tokenServerId.editText?.text = SpannableStringBuilder(tokenServerIdString) e2eeEnabled.isChecked = e2EEOn e2eeKey.editText?.text = SpannableStringBuilder(e2EEKey) + + tokenModeTabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + val literalMode = tab.position == TAB_POSITION_LITERAL + literalFields.visibility = if (literalMode) View.VISIBLE else View.GONE + devTokenServerFields.visibility = if (literalMode) View.GONE else View.VISIBLE + } + + override fun onTabUnselected(tab: TabLayout.Tab) {} + + override fun onTabReselected(tab: TabLayout.Tab) {} + }) + connectButton.setOnClickListener { + val tokenSourceArgs = if (tokenModeTabs.selectedTabPosition == TAB_POSITION_LITERAL) { + TokenSourceArgs.Literal( + url = url.editText?.text.toString(), + token = token.editText?.text.toString(), + ) + } else { + TokenSourceArgs.DevTokenServer( + tokenServerId = tokenServerId.editText?.text.toString(), + roomName = roomName.editText?.text?.toString()?.ifBlank { null }, + participantName = participantName.editText?.text?.toString()?.ifBlank { null }, + participantIdentity = participantIdentity.editText?.text?.toString()?.ifBlank { null }, + agentName = agentName.editText?.text?.toString()?.ifBlank { null }, + ) + } val intent = Intent(this@MainActivity, CallActivity::class.java).apply { putExtra( CallActivity.KEY_ARGS, CallActivity.BundleArgs( - url = url.editText?.text.toString(), - token = token.editText?.text.toString(), + tokenSourceArgs = tokenSourceArgs, e2eeOn = e2eeEnabled.isChecked, e2eeKey = e2eeKey.editText?.text.toString(), stressTest = StressTest.None, @@ -64,6 +95,7 @@ class MainActivity : AppCompatActivity() { saveButton.setOnClickListener { viewModel.setSavedUrl(url.editText?.text?.toString() ?: "") viewModel.setSavedToken(token.editText?.text?.toString() ?: "") + viewModel.setSavedTokenServerId(tokenServerId.editText?.text?.toString() ?: "") viewModel.setSavedE2EEOn(e2eeEnabled.isChecked) viewModel.setSavedE2EEKey(e2eeKey.editText?.text?.toString() ?: "") @@ -78,6 +110,7 @@ class MainActivity : AppCompatActivity() { viewModel.reset() url.editText?.text = SpannableStringBuilder(MainViewModel.URL) token.editText?.text = SpannableStringBuilder(MainViewModel.TOKEN) + tokenServerId.editText?.text = SpannableStringBuilder(MainViewModel.TOKEN_SERVER_ID) e2eeEnabled.isChecked = false e2eeKey.editText?.text = SpannableStringBuilder("") @@ -93,4 +126,8 @@ class MainActivity : AppCompatActivity() { requestNeededPermissions() } + + companion object { + private const val TAB_POSITION_LITERAL = 0 + } } diff --git a/sample-app/src/main/res/layout/main_activity.xml b/sample-app/src/main/res/layout/main_activity.xml index 03df63682..416dfec20 100644 --- a/sample-app/src/main/res/layout/main_activity.xml +++ b/sample-app/src/main/res/layout/main_activity.xml @@ -1,79 +1,186 @@ - + android:fillViewport="true"> - - - + android:gravity="center_horizontal" + android:orientation="vertical" + android:padding="10dp"> + + - + android:layout_height="wrap_content" + android:layout_marginTop="20dp"> - + - + + + - + android:layout_height="wrap_content" + android:orientation="vertical"> - + - + + + + + + + + + - + + + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone"> - + - + -