@@ -67,12 +67,16 @@ public class FirebaseInstallations implements FirebaseInstallationsApi {
67
67
private final Object lock = new Object ();
68
68
private final ExecutorService backgroundExecutor ;
69
69
private final ExecutorService networkExecutor ;
70
+ /* FID of this Firebase Installations instance. Cached after successfully registering and
71
+ persisting the FID locally. NOTE: cachedFid resets if FID is deleted.*/
72
+ private String cachedFid = null ;
70
73
71
74
@ GuardedBy ("lock" )
72
75
private final List <StateListener > listeners = new ArrayList <>();
73
76
74
77
/* used for thread-level synchronization of generating and persisting fids */
75
78
private static final Object lockGenerateFid = new Object ();
79
+
76
80
/* file used for process-level synchronization of generating and persisting fids */
77
81
private static final String LOCKFILE_NAME_GENERATE_FID = "generatefid.lock" ;
78
82
private static final String CHIME_FIREBASE_APP_NAME = "CHIME_ANDROID_SDK" ;
@@ -213,9 +217,9 @@ String getName() {
213
217
@ Override
214
218
public Task <String > getId () {
215
219
preConditionChecks ();
216
- Task <String > task = addGetIdListener ();
217
- backgroundExecutor . execute ( this :: doGetId );
218
- return task ;
220
+ TaskCompletionSource <String > taskCompletionSource = new TaskCompletionSource <> ();
221
+ taskCompletionSource . trySetResult ( doGetId () );
222
+ return taskCompletionSource . getTask () ;
219
223
}
220
224
221
225
/**
@@ -231,11 +235,7 @@ public Task<String> getId() {
231
235
public Task <InstallationTokenResult > getToken (boolean forceRefresh ) {
232
236
preConditionChecks ();
233
237
Task <InstallationTokenResult > task = addGetAuthTokenListener ();
234
- if (forceRefresh ) {
235
- backgroundExecutor .execute (this ::doGetAuthTokenForceRefresh );
236
- } else {
237
- backgroundExecutor .execute (this ::doGetAuthTokenWithoutForceRefresh );
238
- }
238
+ backgroundExecutor .execute (() -> doGetAuthToken (forceRefresh ));
239
239
return task ;
240
240
}
241
241
@@ -250,15 +250,6 @@ public Task<Void> delete() {
250
250
return Tasks .call (backgroundExecutor , this ::deleteFirebaseInstallationId );
251
251
}
252
252
253
- private Task <String > addGetIdListener () {
254
- TaskCompletionSource <String > taskCompletionSource = new TaskCompletionSource <>();
255
- StateListener l = new GetIdListener (taskCompletionSource );
256
- synchronized (lock ) {
257
- listeners .add (l );
258
- }
259
- return taskCompletionSource .getTask ();
260
- }
261
-
262
253
private Task <InstallationTokenResult > addGetAuthTokenListener () {
263
254
TaskCompletionSource <InstallationTokenResult > taskCompletionSource =
264
255
new TaskCompletionSource <>();
@@ -295,16 +286,15 @@ private void triggerOnException(PersistedInstallationEntry prefs, Exception exce
295
286
}
296
287
}
297
288
298
- private final void doGetId () {
299
- doRegistrationInternal (false );
300
- }
301
-
302
- private final void doGetAuthTokenWithoutForceRefresh () {
303
- doRegistrationInternal (false );
304
- }
305
-
306
- private final void doGetAuthTokenForceRefresh () {
307
- doRegistrationInternal (true );
289
+ private String doGetId () {
290
+ if (cachedFid != null ) {
291
+ return cachedFid ;
292
+ }
293
+ PersistedInstallationEntry prefs = getPrefsWithGeneratedIdMultiProcessSafe ();
294
+ // Execute network calls (CreateInstallations) to the FIS Servers on a separate executor
295
+ // i.e networkExecutor
296
+ networkExecutor .execute (() -> doNetworkCallIfNecessary (false ));
297
+ return prefs .getFirebaseInstallationId ();
308
298
}
309
299
310
300
/**
@@ -316,7 +306,7 @@ private final void doGetAuthTokenForceRefresh() {
316
306
* @param forceRefresh true if this is for a getAuthToken call and if the caller wants to fetch a
317
307
* new auth token from the server even if an unexpired auth token exists on the client.
318
308
*/
319
- private final void doRegistrationInternal (boolean forceRefresh ) {
309
+ private void doGetAuthToken (boolean forceRefresh ) {
320
310
PersistedInstallationEntry prefs = getPrefsWithGeneratedIdMultiProcessSafe ();
321
311
322
312
// Since the caller wants to force an authtoken refresh remove the authtoken from the
@@ -328,11 +318,11 @@ private final void doRegistrationInternal(boolean forceRefresh) {
328
318
triggerOnStateReached (prefs );
329
319
// Execute network calls (CreateInstallations or GenerateAuthToken) to the FIS Servers on
330
320
// a separate executor i.e networkExecutor
331
- networkExecutor .execute (() -> doNetworkCall (forceRefresh ));
321
+ networkExecutor .execute (() -> doNetworkCallIfNecessary (forceRefresh ));
332
322
}
333
323
334
- private void doNetworkCall (boolean forceRefresh ) {
335
- PersistedInstallationEntry prefs = getPrefsWithGeneratedIdMultiProcessSafe ();
324
+ private void doNetworkCallIfNecessary (boolean forceRefresh ) {
325
+ PersistedInstallationEntry prefs = getMultiProcessSafePrefs ();
336
326
// There are two possible cleanup steps to perform at this stage: the FID may need to
337
327
// be registered with the server or the FID is registered but we need a fresh authtoken.
338
328
// Registering will also result in a fresh authtoken. Do the appropriate step here.
@@ -353,6 +343,11 @@ private void doNetworkCall(boolean forceRefresh) {
353
343
// Store the prefs to persist the result of the previous step.
354
344
insertOrUpdatePrefs (prefs );
355
345
346
+ // Update cachedFID, if FID is successfully REGISTERED and persisted.
347
+ if (prefs .isRegistered ()) {
348
+ cachedFid = prefs .getFirebaseInstallationId ();
349
+ }
350
+
356
351
// Let the caller know about the result.
357
352
if (prefs .isErrored ()) {
358
353
triggerOnException (prefs , new FirebaseInstallationsException (Status .BAD_CONFIG ));
@@ -509,6 +504,7 @@ private PersistedInstallationEntry fetchAuthTokenFromServer(
509
504
case AUTH_ERROR :
510
505
// The the server refused to generate a new auth token due to bad credentials, clear the
511
506
// FID to force the generation of a new one.
507
+ cachedFid = null ;
512
508
return prefs .withNoGeneratedFid ();
513
509
default :
514
510
throw new IOException ();
@@ -520,7 +516,8 @@ private PersistedInstallationEntry fetchAuthTokenFromServer(
520
516
* storage.
521
517
*/
522
518
private Void deleteFirebaseInstallationId () throws FirebaseInstallationsException , IOException {
523
- PersistedInstallationEntry entry = persistedInstallation .readPersistedInstallationEntryValue ();
519
+ cachedFid = null ;
520
+ PersistedInstallationEntry entry = getMultiProcessSafePrefs ();
524
521
if (entry .isRegistered ()) {
525
522
// Call the FIS servers to delete this Firebase Installation Id.
526
523
try {
@@ -535,8 +532,34 @@ private Void deleteFirebaseInstallationId() throws FirebaseInstallationsExceptio
535
532
"Failed to delete a Firebase Installation." , Status .BAD_CONFIG );
536
533
}
537
534
}
538
-
539
535
insertOrUpdatePrefs (entry .withNoGeneratedFid ());
540
536
return null ;
541
537
}
538
+
539
+ /**
540
+ * Loads the persisted prefs. This operation is made cross-process and cross-thread safe by
541
+ * wrapping all the processing first in a java synchronization block and wrapping that in a
542
+ * cross-process lock created using FileLocks.
543
+ *
544
+ * @return a persisted prefs
545
+ */
546
+ private PersistedInstallationEntry getMultiProcessSafePrefs () {
547
+ synchronized (lockGenerateFid ) {
548
+ CrossProcessLock lock =
549
+ CrossProcessLock .acquire (firebaseApp .getApplicationContext (), LOCKFILE_NAME_GENERATE_FID );
550
+ try {
551
+ PersistedInstallationEntry prefs =
552
+ persistedInstallation .readPersistedInstallationEntryValue ();
553
+ return prefs ;
554
+
555
+ } finally {
556
+ // It is possible that the lock acquisition failed, resulting in lock being null.
557
+ // We handle this case by going on with our business even if the acquisition failed
558
+ // but we need to be sure to only release if we got a lock.
559
+ if (lock != null ) {
560
+ lock .releaseAndClose ();
561
+ }
562
+ }
563
+ }
564
+ }
542
565
}
0 commit comments