2727import com .google .common .base .Strings ;
2828import com .google .common .collect .ImmutableList ;
2929import com .google .common .io .BaseEncoding ;
30+ import com .google .firebase .auth .GoogleOAuthAccessToken ;
3031import com .google .firebase .internal .AuthStateListener ;
3132import com .google .firebase .internal .FirebaseAppStore ;
3233import com .google .firebase .internal .FirebaseExecutors ;
@@ -67,8 +68,10 @@ public class FirebaseApp {
6768
6869 public static final String DEFAULT_APP_NAME = "[DEFAULT]" ;
6970 private static final long TOKEN_REFRESH_INTERVAL_MILLIS = TimeUnit .MINUTES .toMillis (55 );
70- private static final TokenRefresher .Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
71+
72+ static final TokenRefresher .Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
7173 new TokenRefresher .Factory ();
74+ static final Clock DEFAULT_CLOCK = new Clock ();
7275
7376 /**
7477 * Global lock for synchronizing all SDK-wide application state changes. Specifically, any
@@ -79,23 +82,28 @@ public class FirebaseApp {
7982 private final String name ;
8083 private final FirebaseOptions options ;
8184 private final TokenRefresher tokenRefresher ;
85+ private final Clock clock ;
8286
8387 private final AtomicBoolean deleted = new AtomicBoolean ();
8488 private final List <AuthStateListener > authStateListeners = new ArrayList <>();
8589 private final AtomicReference <GetTokenResult > currentToken = new AtomicReference <>();
8690 private final Map <String , FirebaseService > services = new HashMap <>();
8791
92+ private Task <GoogleOAuthAccessToken > previousTokenTask ;
93+
8894 /**
8995 * Per application lock for synchronizing all internal FirebaseApp state changes.
9096 */
9197 private final Object lock = new Object ();
9298
9399 /** Default constructor. */
94- private FirebaseApp (String name , FirebaseOptions options , TokenRefresher .Factory factory ) {
100+ private FirebaseApp (String name , FirebaseOptions options ,
101+ TokenRefresher .Factory factory , Clock clock ) {
95102 checkArgument (!Strings .isNullOrEmpty (name ));
96103 this .name = name ;
97104 this .options = checkNotNull (options );
98- tokenRefresher = checkNotNull (factory ).create (this );
105+ this .tokenRefresher = checkNotNull (factory ).create (this );
106+ this .clock = checkNotNull (clock );
99107 }
100108
101109 /** Returns a list of all FirebaseApps. */
@@ -168,11 +176,11 @@ public static FirebaseApp initializeApp(FirebaseOptions options) {
168176 * @throws IllegalStateException if an app with the same name has already been initialized.
169177 */
170178 public static FirebaseApp initializeApp (FirebaseOptions options , String name ) {
171- return initializeApp (options , name , DEFAULT_TOKEN_REFRESHER_FACTORY );
179+ return initializeApp (options , name , DEFAULT_TOKEN_REFRESHER_FACTORY , DEFAULT_CLOCK );
172180 }
173181
174- static FirebaseApp initializeApp (
175- FirebaseOptions options , String name , TokenRefresher .Factory tokenRefresherFactory ) {
182+ static FirebaseApp initializeApp (FirebaseOptions options , String name ,
183+ TokenRefresher .Factory tokenRefresherFactory , Clock clock ) {
176184 FirebaseAppStore appStore = FirebaseAppStore .initialize ();
177185 String normalizedName = normalize (name );
178186 final FirebaseApp firebaseApp ;
@@ -181,7 +189,7 @@ static FirebaseApp initializeApp(
181189 !instances .containsKey (normalizedName ),
182190 "FirebaseApp name " + normalizedName + " already exists!" );
183191
184- firebaseApp = new FirebaseApp (normalizedName , options , tokenRefresherFactory );
192+ firebaseApp = new FirebaseApp (normalizedName , options , tokenRefresherFactory , clock );
185193 instances .put (normalizedName , firebaseApp );
186194 }
187195
@@ -305,6 +313,13 @@ private void checkNotDeleted() {
305313 checkState (!deleted .get (), "FirebaseApp was deleted %s" , this );
306314 }
307315
316+ private boolean refreshRequired (
317+ @ NonNull Task <GoogleOAuthAccessToken > previousTask , boolean forceRefresh ) {
318+ return (previousTask .isComplete ()
319+ && (forceRefresh || !previousTask .isSuccessful ()
320+ || previousTask .getResult ().getExpiryTime () <= clock .now ()));
321+ }
322+
308323 /**
309324 * Internal-only method to fetch a valid Service Account OAuth2 Token.
310325 *
@@ -313,41 +328,45 @@ private void checkNotDeleted() {
313328 * @return a {@link Task}
314329 */
315330 Task <GetTokenResult > getToken (boolean forceRefresh ) {
316- checkNotDeleted ();
317- return options
318- .getCredential ()
319- .getAccessToken (forceRefresh )
320- .continueWith (
321- new Continuation <String , GetTokenResult >() {
322- @ Override
323- public GetTokenResult then (@ NonNull Task <String > task ) throws Exception {
324- GetTokenResult newToken = new GetTokenResult (task .getResult ());
325- GetTokenResult oldToken = currentToken .get ();
326- List <AuthStateListener > listenersCopy = null ;
327- if (!newToken .equals (oldToken )) {
328- synchronized (lock ) {
329- if (deleted .get ()) {
330- return newToken ;
331- }
332-
333- // Grab the lock before compareAndSet to avoid a potential race
334- // condition with addAuthStateListener. The same lock also ensures serial
335- // access to the token refresher.
336- if (currentToken .compareAndSet (oldToken , newToken )) {
337- listenersCopy = ImmutableList .copyOf (authStateListeners );
338- tokenRefresher .scheduleRefresh (TOKEN_REFRESH_INTERVAL_MILLIS );
339- }
331+ synchronized (lock ) {
332+ checkNotDeleted ();
333+ if (previousTokenTask == null || refreshRequired (previousTokenTask , forceRefresh )) {
334+ previousTokenTask = options .getCredential ().getAccessToken ();
335+ }
336+
337+ return previousTokenTask .continueWith (
338+ new Continuation <GoogleOAuthAccessToken , GetTokenResult >() {
339+ @ Override
340+ public GetTokenResult then (@ NonNull Task <GoogleOAuthAccessToken > task )
341+ throws Exception {
342+ GetTokenResult newToken = new GetTokenResult (task .getResult ().getAccessToken ());
343+ GetTokenResult oldToken = currentToken .get ();
344+ List <AuthStateListener > listenersCopy = null ;
345+ if (!newToken .equals (oldToken )) {
346+ synchronized (lock ) {
347+ if (deleted .get ()) {
348+ return newToken ;
340349 }
341- }
342350
343- if (listenersCopy != null ) {
344- for (AuthStateListener listener : listenersCopy ) {
345- listener .onAuthStateChanged (newToken );
351+ // Grab the lock before compareAndSet to avoid a potential race
352+ // condition with addAuthStateListener. The same lock also ensures serial
353+ // access to the token refresher.
354+ if (currentToken .compareAndSet (oldToken , newToken )) {
355+ listenersCopy = ImmutableList .copyOf (authStateListeners );
356+ tokenRefresher .scheduleRefresh (TOKEN_REFRESH_INTERVAL_MILLIS );
346357 }
347358 }
348- return newToken ;
349359 }
350- });
360+
361+ if (listenersCopy != null ) {
362+ for (AuthStateListener listener : listenersCopy ) {
363+ listener .onAuthStateChanged (newToken );
364+ }
365+ }
366+ return newToken ;
367+ }
368+ });
369+ }
351370 }
352371
353372 boolean isDefaultApp () {
@@ -451,4 +470,10 @@ TokenRefresher create(FirebaseApp app) {
451470 }
452471 }
453472 }
473+
474+ static class Clock {
475+ long now () {
476+ return System .currentTimeMillis ();
477+ }
478+ }
454479}
0 commit comments