@@ -48,13 +48,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
48
48
import kotlinx.coroutines.flow.filter
49
49
import kotlinx.coroutines.flow.first
50
50
import kotlinx.coroutines.flow.getAndUpdate
51
+ import kotlinx.coroutines.flow.update
51
52
import kotlinx.coroutines.launch
52
53
53
54
/* * Base class that shares logic for managing the Auth token and AppCheck token. */
54
55
internal sealed class DataConnectCredentialsTokenManager <T : Any >(
55
56
private val deferredProvider : com.google.firebase.inject.Deferred <T >,
56
57
parentCoroutineScope : CoroutineScope ,
57
- blockingDispatcher : CoroutineDispatcher ,
58
+ private val blockingDispatcher : CoroutineDispatcher ,
58
59
protected val logger : Logger ,
59
60
) {
60
61
val instanceId: String
@@ -74,17 +75,23 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
74
75
}
75
76
)
76
77
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
-
86
78
private sealed interface State <out T > {
87
79
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
+
88
95
/* * State indicating that [close] has been invoked. */
89
96
object Closed : State<Nothing>
90
97
@@ -93,9 +100,6 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
93
100
val forceTokenRefresh: Boolean
94
101
}
95
102
96
- /* * State indicating that the token provider is not (yet?) available. */
97
- data class New (override val forceTokenRefresh : Boolean ) : StateWithForceTokenRefresh<Nothing>
98
-
99
103
sealed interface StateWithProvider <out T > : State <T > {
100
104
/* * The token provider, [InternalAuthProvider] or [InteropAppCheckTokenProvider] */
101
105
val provider: T
@@ -115,7 +119,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
115
119
}
116
120
117
121
/* * 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 )
119
123
120
124
/* *
121
125
* Adds the token listener to the given provider.
@@ -137,6 +141,34 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
137
141
*/
138
142
protected abstract suspend fun getToken (provider : T , forceRefresh : Boolean ): GetTokenResult
139
143
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
+
140
172
/* *
141
173
* Closes this object, releasing its resources, unregistering any registered listeners, and
142
174
* cancelling any in-flight token requests.
@@ -155,8 +187,9 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
155
187
156
188
val oldState = state.getAndUpdate { State .Closed }
157
189
when (oldState) {
158
- is State .Closed -> {}
159
190
is State .New -> {}
191
+ is State .Initialized -> {}
192
+ is State .Closed -> {}
160
193
is State .StateWithProvider -> {
161
194
removeTokenListener(oldState.provider)
162
195
}
@@ -166,6 +199,9 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
166
199
/* *
167
200
* Suspends until the token provider becomes available to this object.
168
201
*
202
+ * This method _may_ be called before [initialize], which is the method that asynchronously gets
203
+ * the token provider.
204
+ *
169
205
* If [close] has been invoked, or is invoked _before_ a token provider becomes available, then
170
206
* this method returns normally, as if a token provider _had_ become available.
171
207
*/
@@ -177,6 +213,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
177
213
when (it) {
178
214
State .Closed -> true
179
215
is State .New -> false
216
+ is State .Initialized -> false
180
217
is State .Idle -> true
181
218
is State .Active -> true
182
219
}
@@ -197,25 +234,34 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
197
234
val newState =
198
235
when (currentState) {
199
236
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 )
201
239
is State .Idle -> currentState.copy(forceTokenRefresh = true )
202
240
is State .Active -> State .Idle (currentState.provider, forceTokenRefresh = true )
203
241
}
204
242
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
+ ) {
206
248
" internal error gbazc7qr66: newState should have been Closed or " +
207
249
" StateWithForceTokenRefresh, but got: $newState "
208
250
}
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
+ }
211
255
}
212
256
213
257
newState
214
258
}
215
259
216
260
when (oldState) {
217
261
is State .Closed -> {}
218
- is State .New -> {}
262
+ is State .New ->
263
+ throw IllegalStateException (" initialize() must be called before forceRefresh()" )
264
+ is State .Initialized -> {}
219
265
is State .Idle -> {}
220
266
is State .Active -> {
221
267
val message = " needs token refresh (wgrwbrvjxt)"
@@ -259,14 +305,16 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
259
305
260
306
val newState: State .Active <T > =
261
307
when (oldState) {
308
+ is State .New ->
309
+ throw IllegalStateException (" initialize() must be called before getToken()" )
262
310
is State .Closed -> {
263
311
logger.debug {
264
312
" $invocationId getToken() throws CredentialsTokenManagerClosedException" +
265
313
" because the DataConnectCredentialsTokenManager instance has been closed"
266
314
}
267
315
throw CredentialsTokenManagerClosedException (this )
268
316
}
269
- is State .New -> {
317
+ is State .Initialized -> {
270
318
logger.debug {
271
319
" $invocationId getToken() returns null (token provider is not (yet?) available)"
272
320
}
@@ -353,22 +401,28 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
353
401
val oldState =
354
402
state.getAndUpdate { currentState ->
355
403
when (currentState) {
404
+ is State .New -> currentState
356
405
is State .Closed -> State .Closed
357
- is State .New -> State .Idle (newProvider, currentState.forceTokenRefresh)
406
+ is State .Initialized -> State .Idle (newProvider, currentState.forceTokenRefresh)
358
407
is State .Idle -> State .Idle (newProvider, currentState.forceTokenRefresh)
359
408
is State .Active -> State .Idle (newProvider, forceTokenRefresh = false )
360
409
}
361
410
}
362
411
363
412
when (oldState) {
413
+ is State .New ->
414
+ throw IllegalStateException (
415
+ " internal error sdpzwhmhd3: " +
416
+ " initialize() should have been called before onProviderAvailable()"
417
+ )
364
418
is State .Closed -> {
365
419
logger.debug {
366
420
" onProviderAvailable(newProvider=$newProvider )" +
367
421
" unregistering token listener that was just added"
368
422
}
369
423
removeTokenListener(newProvider)
370
424
}
371
- is State .New -> {}
425
+ is State .Initialized -> {}
372
426
is State .Idle -> {}
373
427
is State .Active -> {
374
428
val newProviderClassName = newProvider::class .qualifiedName
0 commit comments