@@ -32,7 +32,6 @@ import com.google.firebase.inject.Deferred.DeferredHandler
3232import com.google.firebase.inject.Provider
3333import com.google.firebase.internal.api.FirebaseNoSignedInUserException
3434import java.lang.ref.WeakReference
35- import kotlin.coroutines.coroutineContext
3635import kotlinx.coroutines.CancellationException
3736import kotlinx.coroutines.CoroutineDispatcher
3837import kotlinx.coroutines.CoroutineName
@@ -41,8 +40,11 @@ import kotlinx.coroutines.CoroutineStart
4140import kotlinx.coroutines.Deferred
4241import kotlinx.coroutines.async
4342import kotlinx.coroutines.cancel
43+ import kotlinx.coroutines.currentCoroutineContext
4444import kotlinx.coroutines.ensureActive
4545import kotlinx.coroutines.flow.MutableStateFlow
46+ import kotlinx.coroutines.flow.StateFlow
47+ import kotlinx.coroutines.flow.asStateFlow
4648import kotlinx.coroutines.flow.filter
4749import kotlinx.coroutines.flow.first
4850import kotlinx.coroutines.flow.getAndUpdate
@@ -110,6 +112,17 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
110112 /* * The current state of this object. */
111113 private val state = MutableStateFlow <State <T , R >>(State .New )
112114
115+ private val _token = MutableStateFlow <R ?>(null )
116+
117+ /* *
118+ * The last token returned from [getToken]
119+ *
120+ * Returns null if [getToken] has never been called or never completed successfully.
121+ *
122+ * After [close] the value of this flow is _not_ cleared, and will remain unchanged indefinitely.
123+ */
124+ val token: StateFlow <R ?> = _token .asStateFlow()
125+
113126 /* *
114127 * Adds the token listener to the given provider.
115128 *
@@ -130,6 +143,9 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
130143 */
131144 protected abstract suspend fun getToken (provider : T , forceRefresh : Boolean ): R
132145
146+ /* * Invoked synchronously by [close]. */
147+ protected abstract fun onClose ()
148+
133149 /* *
134150 * Initializes this object.
135151 *
@@ -173,6 +189,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
173189
174190 weakThis.clear()
175191 coroutineScope.cancel()
192+ onClose()
176193
177194 val oldState = state.getAndUpdate { State .Closed }
178195 when (oldState) {
@@ -339,7 +356,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
339356
340357 // Ensure that any exception checking below is due to an exception that happened in the
341358 // coroutine that called getToken(), not from the calling coroutine being cancelled.
342- coroutineContext .ensureActive()
359+ currentCoroutineContext() .ensureActive()
343360
344361 val sequencedResult = jobResult.getOrNull()
345362 if (sequencedResult != = null && sequencedResult.sequenceNumber < attemptSequenceNumber) {
@@ -357,6 +374,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
357374 logger.debug {
358375 " $invocationId getToken() returns null (FirebaseAuth reports no signed-in user)"
359376 }
377+ _token .value = null
360378 return null
361379 } else if (exception is CancellationException ) {
362380 logger.warn(exception) {
@@ -371,17 +389,16 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
371389 }
372390
373391 val tokenResult: R = sequencedResult!! .ref.getOrThrow()
374- logger.debug {
375- " $invocationId getToken() returns retrieved token: " +
376- tokenResult.token?.toScrubbedAccessToken()
377- }
392+ logger.debug { " $invocationId getToken() returns $tokenResult " }
393+ _token .value = tokenResult
378394 return tokenResult
379395 }
380396 }
381397
382398 private sealed class GetTokenRetry (message : String ) : Exception(message)
383399 private class ForceRefresh (message : String ) : GetTokenRetry(message)
384400 private class NewProvider (message : String ) : GetTokenRetry(message)
401+ private class NewToken (message : String ) : GetTokenRetry(message)
385402
386403 @DeferredApi
387404 private fun onProviderAvailable (newProvider : T ) {
@@ -422,6 +439,37 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, R : GetTokenRe
422439 }
423440 }
424441
442+ protected fun onTokenChanged (newToken : String? ) {
443+ if (token.value?.token == newToken) {
444+ return
445+ }
446+
447+ val invocationId = idStringGenerator.next(" otc" )
448+ logger.debug { " $invocationId onTokenChanged(newToken=${newToken?.toScrubbedAccessToken()} )" }
449+
450+ while (true ) {
451+ val currentState = state.value
452+
453+ val activeState =
454+ when (currentState) {
455+ State .New -> return
456+ is State .Initialized -> break
457+ is State .Idle -> break
458+ is State .Active -> currentState
459+ State .Closed -> return
460+ }
461+
462+ val newState = State .Idle (activeState.provider, forceTokenRefresh = false )
463+ if (state.compareAndSet(currentState, newState)) {
464+ val message = " $invocationId a new token is available (j567n2577q)"
465+ activeState.job.cancel(message, NewToken (message))
466+ break
467+ }
468+ }
469+
470+ coroutineScope.launch(CoroutineName (invocationId)) { getToken(invocationId) }
471+ }
472+
425473 /* *
426474 * An implementation of [DeferredHandler] to be registered with the [Deferred] given to the
427475 * constructor.
0 commit comments