Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import io.getstream.result.onSuccessSuspend
import io.getstream.video.android.compose.ui.ComposeStreamCallActivity
import io.getstream.video.android.compose.ui.StreamCallActivityComposeDelegate
import io.getstream.video.android.core.Call
import io.getstream.video.android.core.CallLeaveReason
import io.getstream.video.android.core.MemberState
import io.getstream.video.android.core.RingingState
import io.getstream.video.android.core.StreamVideo
Expand Down Expand Up @@ -144,9 +145,43 @@ class CallActivity : ComposeStreamCallActivity() {
intent.getStringExtra(EXTRA_E2EE_PASSPHRASE)
?.takeIf { it.isNotBlank() }
?.let { enableE2EE(call, it) }
// Lobby already attached a manager and then died (CLEAR_TASK). Adopt it so leave/finish
// can dispose it — Call.leave() only detaches, and the 1 Hz PERF_REPORT timer keeps
// firing until dispose().
if (e2eeManager == null) {
DemoE2eeKeys.manager(call.cid)?.let { existing ->
e2eeManager = existing
e2eeCid = call.cid
}
}
super.join(call, onSuccess, onError)
}

@StreamCallActivityDelicateApi
override fun leave(
call: Call,
callLeaveReason: CallLeaveReason,
onSuccess: (suspend (Call) -> Unit)?,
onError: (suspend (Exception) -> Unit)?,
) {
super.leave(
call,
callLeaveReason,
onSuccess = { left ->
releaseE2EE()
onSuccess?.invoke(left)
},
onError,
)
}

private fun releaseE2EE() {
e2eeManager?.dispose()
e2eeManager = null
e2eeCid?.let { DemoE2eeKeys.forget(it) }
e2eeCid = null
}

private fun enableE2EE(call: Call, passphrase: String) {
// join() cannot suspend, but the derivation is 100k PBKDF2 iterations - keep it off main.
val key = runCatching {
Expand Down Expand Up @@ -195,7 +230,7 @@ class CallActivity : ComposeStreamCallActivity() {
e2eeManager = manager
// Kept so the in-call share sheet can put the passphrase back on the invite link.
e2eeCid = call.cid
DemoE2eeKeys.remember(call.cid, passphrase)
DemoE2eeKeys.remember(call.cid, passphrase, manager)
}

private class StreamDemoUiDelegate : StreamCallActivityComposeDelegate() {
Expand Down Expand Up @@ -298,9 +333,6 @@ class CallActivity : ComposeStreamCallActivity() {
observeCallReadyToJoinJob?.cancel()
observeRingingJob?.cancel()
previousRingingStates.clear()
e2eeManager?.dispose()
e2eeManager = null
e2eeCid?.let { DemoE2eeKeys.forget(it) }
e2eeCid = null
releaseE2EE()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class CallLobbyViewModel @Inject constructor(
e2eeManager = created
previous?.dispose()
// Kept so the in-call share sheet can put the passphrase back on the invite link.
DemoE2eeKeys.remember(call.cid, passphrase)
DemoE2eeKeys.remember(call.cid, passphrase, created)
return Result.success(Unit)
}

Expand Down Expand Up @@ -272,6 +272,9 @@ class CallLobbyViewModel @Inject constructor(

fun leaveCall() {
call.leave()
e2eeManager?.dispose()
e2eeManager = null
DemoE2eeKeys.forget(call.cid)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.getstream.video.android.util

import io.getstream.video.android.core.e2ee.StreamEncryptionManager

/**
* Name of the invite-link query parameter carrying the shared passphrase. Read when joining and
* written when sharing, so it lives in one place — the two sides have to agree on it, and they
Expand All @@ -38,15 +40,31 @@ internal object DemoE2eeKeys {
@Volatile
private var current: Entry? = null

private data class Entry(val cid: String, val passphrase: String)
private data class Entry(
val cid: String,
val passphrase: String,
val manager: StreamEncryptionManager?,
)

fun remember(cid: String, passphrase: String) {
current = Entry(cid, passphrase)
fun remember(
cid: String,
passphrase: String,
manager: StreamEncryptionManager? = null,
) {
current = Entry(cid, passphrase, manager)
}

/** The passphrase stored for [cid], or null when the stored one belongs to another call. */
fun of(cid: String): String? = current?.takeIf { it.cid == cid }?.passphrase

/**
* The manager created for [cid], so [io.getstream.video.android.CallActivity] can adopt it
* after the lobby ViewModel is destroyed. Join starts the call activity with CLEAR_TASK, so
* the lobby cannot dispose this instance.
*/
fun manager(cid: String): StreamEncryptionManager? =
current?.takeIf { it.cid == cid }?.manager

fun forget(cid: String) {
if (current?.cid == cid) current = null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,18 @@ public class StreamEncryptionManager private constructor(
}

/**
* Releases the native manager and wipes its keys. Subsequent key operations are ignored;
* attempting to attach an encryptor or decryptor returns a failure so the SDK cannot treat an
* unprotected track as configured. Dispose only once you are done with every call that uses it.
* Releases the native manager and wipes its keys. Drops the [setEventListener] observer first
* so a listener that captured a ViewModel or Activity is not kept alive by native. Subsequent
* key operations are ignored; attempting to attach an encryptor or decryptor returns a failure
* so the SDK cannot treat an unprotected track as configured. Dispose only once you are done
* with every call that uses it.
*/
public fun dispose() {
if (native.isDisposed) return
val disposedUserId = userId
// Before native.dispose(): setObserver rejects a disposed manager, and a listener that
// captured UI would otherwise stay reachable until the Java wrapper is collected.
setEventListener(null)
safeCall { native.dispose() }
logger.d { "[dispose] released native manager for $disposedUserId" }
}
Expand Down
Loading