@@ -48,13 +48,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
4848import kotlinx.coroutines.flow.filter
4949import kotlinx.coroutines.flow.first
5050import kotlinx.coroutines.flow.getAndUpdate
51+ import kotlinx.coroutines.flow.update
5152import kotlinx.coroutines.launch
5253
5354/* * Base class that shares logic for managing the Auth token and AppCheck token. */
5455internal sealed class DataConnectCredentialsTokenManager <T : Any >(
5556 private val deferredProvider : com.google.firebase.inject.Deferred <T >,
5657 parentCoroutineScope : CoroutineScope ,
57- blockingDispatcher : CoroutineDispatcher ,
58+ private val blockingDispatcher : CoroutineDispatcher ,
5859 protected val logger : Logger ,
5960) {
6061 val instanceId: String
@@ -74,17 +75,23 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
7475 }
7576 )
7677
77- init {
78- // Call `whenAvailable()` on a non-main thread because it accesses SharedPreferences, which
79- // performs disk i/o, violating the StrictMode policy android.os.strictmode.DiskReadViolation.
80- val coroutineName = CoroutineName (" k6rwgqg9gh $instanceId whenAvailable" )
81- coroutineScope.launch(coroutineName + blockingDispatcher) {
82- deferredProvider.whenAvailable(DeferredProviderHandlerImpl (weakThis))
83- }
84- }
85-
8678 private sealed interface State <out T > {
8779
80+ /* *
81+ * State indicating that the object has just been created and [initialize] has not yet been
82+ * called.
83+ */
84+ object New : State<Nothing>
85+
86+ /* *
87+ * State indicating that [initialize] has been invoked but the token provider is not (yet?)
88+ * available.
89+ */
90+ data class Initialized (override val forceTokenRefresh : Boolean ) :
91+ StateWithForceTokenRefresh <Nothing > {
92+ constructor () : this (false )
93+ }
94+
8895 /* * State indicating that [close] has been invoked. */
8996 object Closed : State<Nothing>
9097
@@ -93,9 +100,6 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
93100 val forceTokenRefresh: Boolean
94101 }
95102
96- /* * State indicating that the token provider is not (yet?) available. */
97- data class New (override val forceTokenRefresh : Boolean ) : StateWithForceTokenRefresh<Nothing>
98-
99103 sealed interface StateWithProvider <out T > : State <T > {
100104 /* * The token provider, [InternalAuthProvider] or [InteropAppCheckTokenProvider] */
101105 val provider: T
@@ -115,7 +119,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
115119 }
116120
117121 /* * The current state of this object. */
118- private val state = MutableStateFlow <State <T >>(State .New (forceTokenRefresh = false ) )
122+ private val state = MutableStateFlow <State <T >>(State .New )
119123
120124 /* *
121125 * Adds the token listener to the given provider.
@@ -137,6 +141,34 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
137141 */
138142 protected abstract suspend fun getToken (provider : T , forceRefresh : Boolean ): GetTokenResult
139143
144+ /* *
145+ * Initializes this object.
146+ *
147+ * Before calling this method, the _only_ other methods that are allowed to be called on this
148+ * object are [awaitTokenProvider] and [close].
149+ *
150+ * This method may only be called once; subsequent calls result in an exception.
151+ */
152+ fun initialize () {
153+ logger.debug { " initialize()" }
154+
155+ state.update { currentState ->
156+ when (currentState) {
157+ is State .New -> State .Initialized ()
158+ is State .Closed ->
159+ throw IllegalStateException (" initialize() cannot be called after close()" )
160+ else -> throw IllegalStateException (" initialize() has already been called" )
161+ }
162+ }
163+
164+ // Call `whenAvailable()` on a non-main thread because it accesses SharedPreferences, which
165+ // performs disk i/o, violating the StrictMode policy android.os.strictmode.DiskReadViolation.
166+ val coroutineName = CoroutineName (" k6rwgqg9gh $instanceId whenAvailable" )
167+ coroutineScope.launch(coroutineName + blockingDispatcher) {
168+ deferredProvider.whenAvailable(DeferredProviderHandlerImpl (weakThis))
169+ }
170+ }
171+
140172 /* *
141173 * Closes this object, releasing its resources, unregistering any registered listeners, and
142174 * cancelling any in-flight token requests.
@@ -155,8 +187,9 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
155187
156188 val oldState = state.getAndUpdate { State .Closed }
157189 when (oldState) {
158- is State .Closed -> {}
159190 is State .New -> {}
191+ is State .Initialized -> {}
192+ is State .Closed -> {}
160193 is State .StateWithProvider -> {
161194 removeTokenListener(oldState.provider)
162195 }
@@ -166,6 +199,9 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
166199 /* *
167200 * Suspends until the token provider becomes available to this object.
168201 *
202+ * This method _may_ be called before [initialize], which is the method that asynchronously gets
203+ * the token provider.
204+ *
169205 * If [close] has been invoked, or is invoked _before_ a token provider becomes available, then
170206 * this method returns normally, as if a token provider _had_ become available.
171207 */
@@ -177,6 +213,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
177213 when (it) {
178214 State .Closed -> true
179215 is State .New -> false
216+ is State .Initialized -> false
180217 is State .Idle -> true
181218 is State .Active -> true
182219 }
@@ -197,25 +234,34 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
197234 val newState =
198235 when (currentState) {
199236 is State .Closed -> State .Closed
200- is State .New -> currentState.copy(forceTokenRefresh = true )
237+ is State .New -> currentState
238+ is State .Initialized -> currentState.copy(forceTokenRefresh = true )
201239 is State .Idle -> currentState.copy(forceTokenRefresh = true )
202240 is State .Active -> State .Idle (currentState.provider, forceTokenRefresh = true )
203241 }
204242
205- check(newState is State .Closed || newState is State .StateWithForceTokenRefresh <T >) {
243+ check(
244+ newState is State .New ||
245+ newState is State .Closed ||
246+ newState is State .StateWithForceTokenRefresh <T >
247+ ) {
206248 " internal error gbazc7qr66: newState should have been Closed or " +
207249 " StateWithForceTokenRefresh, but got: $newState "
208250 }
209- check((newState as ? State .StateWithForceTokenRefresh <T >)?.forceTokenRefresh != = false ) {
210- " internal error fnzwyrsez2: newState.forceTokenRefresh should have been true"
251+ if (newState is State .StateWithForceTokenRefresh <T >) {
252+ check(newState.forceTokenRefresh) {
253+ " internal error fnzwyrsez2: newState.forceTokenRefresh should have been true"
254+ }
211255 }
212256
213257 newState
214258 }
215259
216260 when (oldState) {
217261 is State .Closed -> {}
218- is State .New -> {}
262+ is State .New ->
263+ throw IllegalStateException (" initialize() must be called before forceRefresh()" )
264+ is State .Initialized -> {}
219265 is State .Idle -> {}
220266 is State .Active -> {
221267 val message = " needs token refresh (wgrwbrvjxt)"
@@ -259,14 +305,16 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
259305
260306 val newState: State .Active <T > =
261307 when (oldState) {
308+ is State .New ->
309+ throw IllegalStateException (" initialize() must be called before getToken()" )
262310 is State .Closed -> {
263311 logger.debug {
264312 " $invocationId getToken() throws CredentialsTokenManagerClosedException" +
265313 " because the DataConnectCredentialsTokenManager instance has been closed"
266314 }
267315 throw CredentialsTokenManagerClosedException (this )
268316 }
269- is State .New -> {
317+ is State .Initialized -> {
270318 logger.debug {
271319 " $invocationId getToken() returns null (token provider is not (yet?) available)"
272320 }
@@ -353,22 +401,28 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
353401 val oldState =
354402 state.getAndUpdate { currentState ->
355403 when (currentState) {
404+ is State .New -> currentState
356405 is State .Closed -> State .Closed
357- is State .New -> State .Idle (newProvider, currentState.forceTokenRefresh)
406+ is State .Initialized -> State .Idle (newProvider, currentState.forceTokenRefresh)
358407 is State .Idle -> State .Idle (newProvider, currentState.forceTokenRefresh)
359408 is State .Active -> State .Idle (newProvider, forceTokenRefresh = false )
360409 }
361410 }
362411
363412 when (oldState) {
413+ is State .New ->
414+ throw IllegalStateException (
415+ " internal error sdpzwhmhd3: " +
416+ " initialize() should have been called before onProviderAvailable()"
417+ )
364418 is State .Closed -> {
365419 logger.debug {
366420 " onProviderAvailable(newProvider=$newProvider )" +
367421 " unregistering token listener that was just added"
368422 }
369423 removeTokenListener(newProvider)
370424 }
371- is State .New -> {}
425+ is State .Initialized -> {}
372426 is State .Idle -> {}
373427 is State .Active -> {
374428 val newProviderClassName = newProvider::class .qualifiedName
0 commit comments