Skip to content

Commit ddccf8e

Browse files
authored
Adding a FidListener that propagates fid changes to the clients. (#2195)
* Adding a FidListener that propagates fid changes to the clients. * Addressing vlad's comments. * Adding unregister method for listeners * Fixing the FidListenerHandle * Adding test to validate unregistering the listener * Fixing api.txt * Fixing gradle errors * Minor fix * Fixing minor nit
1 parent 4468726 commit ddccf8e

File tree

8 files changed

+198
-4
lines changed

8 files changed

+198
-4
lines changed

firebase-installations-interop/firebase-installations-interop.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ android {
4040

4141
dependencies {
4242
implementation 'com.google.android.gms:play-services-tasks:17.0.0'
43+
implementation project(path: ':firebase-annotations')
4344

44-
compileOnly "com.google.auto.value:auto-value-annotations:1.6.5"
45+
compileOnly "com.google.auto.value:auto-value-annotations:1.6.5"
4546
annotationProcessor "com.google.auto.value:auto-value:1.6.2"
4647
}

firebase-installations-interop/src/main/java/com/google/firebase/installations/FirebaseInstallationsApi.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
import androidx.annotation.NonNull;
1818
import com.google.android.gms.tasks.Task;
19+
import com.google.firebase.annotations.DeferredApi;
20+
import com.google.firebase.installations.internal.FidListener;
21+
import com.google.firebase.installations.internal.FidListenerHandle;
1922

2023
/**
2124
* This is an interface of {@code FirebaseInstallations} that is only exposed to 2p via component
@@ -51,4 +54,13 @@ public interface FirebaseInstallationsApi {
5154
*/
5255
@NonNull
5356
Task<Void> delete();
57+
58+
/**
59+
* Register a listener to receive fid changes.
60+
*
61+
* @param listener implementation of the {@code FidListener} to handle fid changes.
62+
* @hide
63+
*/
64+
@DeferredApi
65+
FidListenerHandle registerFidListener(@NonNull FidListener listener);
5466
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.installations.internal;
16+
17+
import androidx.annotation.NonNull;
18+
19+
/**
20+
* Provides a call-back interface {@link FidListener} that updates on Fid changes.
21+
*
22+
* @hide
23+
*/
24+
public interface FidListener {
25+
/**
26+
* This method gets invoked when a Fid changes.
27+
*
28+
* @param fid represents the newly generated installation id.
29+
*/
30+
void onFidChanged(@NonNull String fid);
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.installations.internal;
16+
17+
/**
18+
* Interface for un-registering a previously registered listener {@link FidListener}.
19+
*
20+
* @hide
21+
*/
22+
public interface FidListenerHandle {
23+
24+
/**
25+
* Unregisters a previously registered {@link FidListener} being tracked by this {@code
26+
* FidListenerHandle}. After the initial call, subsequent calls have no effect.
27+
*
28+
* @hide
29+
*/
30+
void unregister();
31+
}

firebase-installations/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package com.google.firebase.installations {
77
method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstance();
88
method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstance(@NonNull com.google.firebase.FirebaseApp);
99
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.installations.InstallationTokenResult> getToken(boolean);
10+
method @NonNull public com.google.firebase.installations.internal.FidListenerHandle registerFidListener(@NonNull com.google.firebase.installations.internal.FidListener);
1011
}
1112

1213
}

firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import com.google.firebase.heartbeatinfo.HeartBeatInfo;
2929
import com.google.firebase.inject.Provider;
3030
import com.google.firebase.installations.FirebaseInstallationsException.Status;
31+
import com.google.firebase.installations.internal.FidListener;
32+
import com.google.firebase.installations.internal.FidListenerHandle;
3133
import com.google.firebase.installations.local.IidStore;
3234
import com.google.firebase.installations.local.PersistedInstallation;
3335
import com.google.firebase.installations.local.PersistedInstallationEntry;
@@ -37,8 +39,10 @@
3739
import com.google.firebase.platforminfo.UserAgentPublisher;
3840
import java.io.IOException;
3941
import java.util.ArrayList;
42+
import java.util.HashSet;
4043
import java.util.Iterator;
4144
import java.util.List;
45+
import java.util.Set;
4246
import java.util.concurrent.ExecutorService;
4347
import java.util.concurrent.LinkedBlockingQueue;
4448
import java.util.concurrent.ThreadFactory;
@@ -72,6 +76,9 @@ public class FirebaseInstallations implements FirebaseInstallationsApi {
7276
@GuardedBy("this")
7377
private String cachedFid;
7478

79+
@GuardedBy("FirebaseInstallations.this")
80+
private Set<FidListener> fidListeners = new HashSet<>();
81+
7582
@GuardedBy("lock")
7683
private final List<StateListener> listeners = new ArrayList<>();
7784

@@ -271,6 +278,25 @@ public Task<Void> delete() {
271278
return Tasks.call(backgroundExecutor, this::deleteFirebaseInstallationId);
272279
}
273280

281+
/**
282+
* Register a callback {@link FidListener} to receive fid changes.
283+
*
284+
* @hide
285+
*/
286+
@NonNull
287+
@Override
288+
public synchronized FidListenerHandle registerFidListener(@NonNull FidListener listener) {
289+
fidListeners.add(listener);
290+
return new FidListenerHandle() {
291+
@Override
292+
public void unregister() {
293+
synchronized (FirebaseInstallations.this) {
294+
fidListeners.remove(listener);
295+
}
296+
}
297+
};
298+
}
299+
274300
private Task<String> addGetIdListener() {
275301
TaskCompletionSource<String> taskCompletionSource = new TaskCompletionSource<>();
276302
StateListener l = new GetIdListener(taskCompletionSource);
@@ -356,11 +382,12 @@ private void doNetworkCallIfNecessary(boolean forceRefresh) {
356382
// There are two possible cleanup steps to perform at this stage: the FID may need to
357383
// be registered with the server or the FID is registered but we need a fresh authtoken.
358384
// Registering will also result in a fresh authtoken. Do the appropriate step here.
385+
PersistedInstallationEntry updatedPrefs;
359386
try {
360387
if (prefs.isErrored() || prefs.isUnregistered()) {
361-
prefs = registerFidWithServer(prefs);
388+
updatedPrefs = registerFidWithServer(prefs);
362389
} else if (forceRefresh || utils.isAuthTokenExpired(prefs)) {
363-
prefs = fetchAuthTokenFromServer(prefs);
390+
updatedPrefs = fetchAuthTokenFromServer(prefs);
364391
} else {
365392
// nothing more to do, get out now
366393
return;
@@ -371,7 +398,12 @@ private void doNetworkCallIfNecessary(boolean forceRefresh) {
371398
}
372399

373400
// Store the prefs to persist the result of the previous step.
374-
insertOrUpdatePrefs(prefs);
401+
insertOrUpdatePrefs(updatedPrefs);
402+
403+
// Update FidListener if a fid has changed.
404+
updateFidListener(prefs, updatedPrefs);
405+
406+
prefs = updatedPrefs;
375407

376408
// Update cachedFID, if FID is successfully REGISTERED and persisted.
377409
if (prefs.isRegistered()) {
@@ -390,6 +422,17 @@ private void doNetworkCallIfNecessary(boolean forceRefresh) {
390422
}
391423
}
392424

425+
private synchronized void updateFidListener(
426+
PersistedInstallationEntry prefs, PersistedInstallationEntry updatedPrefs) {
427+
if (fidListeners.size() != 0
428+
&& !prefs.getFirebaseInstallationId().equals(updatedPrefs.getFirebaseInstallationId())) {
429+
// Update all the registered FidListener about fid changes.
430+
for (FidListener listener : fidListeners) {
431+
listener.onFidChanged(updatedPrefs.getFirebaseInstallationId());
432+
}
433+
}
434+
}
435+
393436
/**
394437
* Inserting or Updating the prefs. This operation is made cross-process and cross-thread safe by
395438
* wrapping all the processing first in a java synchronization block and wrapping that in a
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.installations;
16+
17+
import androidx.annotation.NonNull;
18+
import com.google.firebase.installations.internal.FidListener;
19+
20+
class FakeFidListener implements FidListener {
21+
private String currentFid;
22+
23+
@Override
24+
public void onFidChanged(@NonNull String fid) {
25+
currentFid = fid;
26+
}
27+
28+
public String getLatestFid() {
29+
return currentFid;
30+
}
31+
}

firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.google.firebase.FirebaseApp;
4040
import com.google.firebase.FirebaseOptions;
4141
import com.google.firebase.installations.FirebaseInstallationsException.Status;
42+
import com.google.firebase.installations.internal.FidListenerHandle;
4243
import com.google.firebase.installations.local.IidStore;
4344
import com.google.firebase.installations.local.PersistedInstallation;
4445
import com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus;
@@ -74,6 +75,7 @@ public class FirebaseInstallationsTest {
7475
@Mock private RandomFidGenerator mockFidGenerator;
7576

7677
public static final String TEST_FID_1 = "cccccccccccccccccccccc";
78+
public static final String TEST_FID_2 = "dccccccccccccccccccccd";
7779

7880
public static final String TEST_PROJECT_ID = "777777777777";
7981

@@ -453,6 +455,48 @@ public void testReadToken_withJsonformatting() {
453455
assertThat(iidStore.readToken(), equalTo("thetoken"));
454456
}
455457

458+
@Test
459+
public void testFidListener_fidChanged_successful() throws Exception {
460+
when(mockIidStore.readIid()).thenReturn(null);
461+
when(mockIidStore.readToken()).thenReturn(null);
462+
when(mockBackend.createFirebaseInstallation(
463+
anyString(), anyString(), anyString(), anyString(), any()))
464+
.thenReturn(
465+
TEST_INSTALLATION_RESPONSE
466+
.toBuilder()
467+
.setUri("/projects/" + TEST_PROJECT_ID + "/installations/" + TEST_FID_2)
468+
.setFid(TEST_FID_2)
469+
.build());
470+
471+
FakeFidListener fidListener = new FakeFidListener();
472+
FakeFidListener fidListener2 = new FakeFidListener();
473+
474+
// Register the FidListeners
475+
firebaseInstallations.registerFidListener(fidListener);
476+
FidListenerHandle listenerHandle = firebaseInstallations.registerFidListener(fidListener2);
477+
478+
// Do the actual getId() call under test.
479+
// Confirm both that it returns the expected ID, as does reading the prefs from storage.
480+
TestOnCompleteListener<String> onCompleteListener = new TestOnCompleteListener<>();
481+
Task<String> task = firebaseInstallations.getId();
482+
483+
// Unregister FidListener2
484+
listenerHandle.unregister();
485+
486+
task.addOnCompleteListener(executor, onCompleteListener);
487+
String fid = onCompleteListener.await();
488+
assertWithMessage("getId Task failed.").that(fid).isEqualTo(TEST_FID_1);
489+
490+
// Waiting for Task that registers FID on the FIS Servers
491+
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
492+
PersistedInstallationEntry entry = persistedInstallation.readPersistedInstallationEntryValue();
493+
assertThat(entry.getFirebaseInstallationId(), equalTo(TEST_FID_2));
494+
495+
// Verify FidListener receives fid changes.
496+
assertThat(fidListener.getLatestFid(), equalTo(TEST_FID_2));
497+
assertNull(fidListener2.getLatestFid());
498+
}
499+
456500
@Test
457501
public void testGetId_migrateIid_successful() throws Exception {
458502
when(mockIidStore.readIid()).thenReturn(TEST_INSTANCE_ID_1);

0 commit comments

Comments
 (0)