Skip to content

Commit 30b48c3

Browse files
committed
Implementing cache for FIS SDK (#694)
* Implementing cache for FIS SDK * Implementing cache for FIS SDK * Addressing Di's comments. * Addressing Di's comments.
1 parent af1e510 commit 30b48c3

File tree

6 files changed

+313
-2
lines changed

6 files changed

+313
-2
lines changed

firebase-installations/firebase-installations.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ dependencies {
5353
testImplementation 'junit:junit:4.12'
5454
testImplementation "org.robolectric:robolectric:$robolectricVersion"
5555

56+
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
5657
androidTestImplementation 'androidx.test:runner:1.2.0'
57-
implementation 'com.google.guava:guava:16.0.+'
58+
androidTestImplementation "com.google.truth:truth:$googleTruthVersion"
59+
androidTestImplementation 'junit:junit:4.12'
60+
androidTestImplementation "androidx.annotation:annotation:1.0.0"
5861
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Copyright 2019 Google LLC -->
3+
<!-- -->
4+
<!-- Licensed under the Apache License, Version 2.0 (the "License"); -->
5+
<!-- you may not use this file except in compliance with the License. -->
6+
<!-- You may obtain a copy of the License at -->
7+
<!-- -->
8+
<!-- http://www.apache.org/licenses/LICENSE-2.0 -->
9+
<!-- -->
10+
<!-- Unless required by applicable law or agreed to in writing, software -->
11+
<!-- distributed under the License is distributed on an "AS IS" BASIS, -->
12+
<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -->
13+
<!-- See the License for the specific language governing permissions and -->
14+
<!-- limitations under the License. -->
15+
16+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
17+
package="com.google.firebase.installation">
18+
19+
<application>
20+
<uses-library android:name="android.test.runner"/>
21+
</application>
22+
23+
<instrumentation
24+
android:name="androidx.test.runner.AndroidJUnitRunner"
25+
android:targetPackage="com.google.firebase.installation" />
26+
</manifest>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2019 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.installation.local;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static org.junit.Assert.assertNull;
19+
import static org.junit.Assert.assertTrue;
20+
21+
import androidx.test.core.app.ApplicationProvider;
22+
import androidx.test.runner.AndroidJUnit4;
23+
import com.google.firebase.FirebaseApp;
24+
import com.google.firebase.FirebaseOptions;
25+
import com.google.firebase.installations.local.FiidCache;
26+
import com.google.firebase.installations.local.FiidCacheEntryValue;
27+
import org.junit.After;
28+
import org.junit.Before;
29+
import org.junit.Test;
30+
import org.junit.runner.RunWith;
31+
32+
/** Instrumented tests for {@link FiidCache} */
33+
@RunWith(AndroidJUnit4.class)
34+
public class FiidCacheTest {
35+
36+
private FirebaseApp firebaseApp0;
37+
private FirebaseApp firebaseApp1;
38+
private FiidCache cache0;
39+
private FiidCache cache1;
40+
private final String AUTH_TOKEN = "auth_token";
41+
private final String REFRESH_TOKEN = "refresh_token";
42+
43+
private final long TIMESTAMP_IN_SECONDS = 100L;
44+
45+
@Before
46+
public void setUp() {
47+
FirebaseApp.clearInstancesForTest();
48+
firebaseApp0 =
49+
FirebaseApp.initializeApp(
50+
ApplicationProvider.getApplicationContext(),
51+
new FirebaseOptions.Builder().setApplicationId("1:123456789:android:abcdef").build());
52+
firebaseApp1 =
53+
FirebaseApp.initializeApp(
54+
ApplicationProvider.getApplicationContext(),
55+
new FirebaseOptions.Builder().setApplicationId("1:987654321:android:abcdef").build(),
56+
"firebase_app_1");
57+
cache0 = new FiidCache(firebaseApp0);
58+
cache1 = new FiidCache(firebaseApp1);
59+
}
60+
61+
@After
62+
public void cleanUp() throws Exception {
63+
cache0.clear();
64+
cache1.clear();
65+
}
66+
67+
@Test
68+
public void testReadCacheEntry_Null() {
69+
assertNull(cache0.readCacheEntryValue());
70+
assertNull(cache1.readCacheEntryValue());
71+
}
72+
73+
@Test
74+
public void testUpdateAndReadCacheEntry() throws Exception {
75+
assertTrue(
76+
cache0.insertOrUpdateCacheEntry(
77+
FiidCacheEntryValue.create(
78+
"123456",
79+
FiidCache.CacheStatus.UNREGISTERED,
80+
AUTH_TOKEN,
81+
REFRESH_TOKEN,
82+
TIMESTAMP_IN_SECONDS,
83+
TIMESTAMP_IN_SECONDS)));
84+
FiidCacheEntryValue entryValue = cache0.readCacheEntryValue();
85+
assertThat(entryValue.getFirebaseInstallationId()).isEqualTo("123456");
86+
assertThat(entryValue.getAuthToken()).isEqualTo(AUTH_TOKEN);
87+
assertThat(entryValue.getRefreshToken()).isEqualTo(REFRESH_TOKEN);
88+
assertThat(entryValue.getCacheStatus()).isEqualTo(FiidCache.CacheStatus.UNREGISTERED);
89+
assertThat(entryValue.getExpiresInSecs()).isEqualTo(TIMESTAMP_IN_SECONDS);
90+
assertThat(entryValue.getTokenCreationEpochInSecs()).isEqualTo(TIMESTAMP_IN_SECONDS);
91+
assertNull(cache1.readCacheEntryValue());
92+
93+
assertTrue(
94+
cache0.insertOrUpdateCacheEntry(
95+
FiidCacheEntryValue.create(
96+
"123456",
97+
FiidCache.CacheStatus.REGISTERED,
98+
AUTH_TOKEN,
99+
REFRESH_TOKEN,
100+
200L,
101+
TIMESTAMP_IN_SECONDS)));
102+
entryValue = cache0.readCacheEntryValue();
103+
assertThat(entryValue.getFirebaseInstallationId()).isEqualTo("123456");
104+
assertThat(entryValue.getAuthToken()).isEqualTo(AUTH_TOKEN);
105+
assertThat(entryValue.getRefreshToken()).isEqualTo(REFRESH_TOKEN);
106+
assertThat(entryValue.getCacheStatus()).isEqualTo(FiidCache.CacheStatus.REGISTERED);
107+
assertThat(entryValue.getExpiresInSecs()).isEqualTo(TIMESTAMP_IN_SECONDS);
108+
assertThat(entryValue.getTokenCreationEpochInSecs()).isEqualTo(200L);
109+
}
110+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
package com.google.firebase.installations;
1616

1717
import androidx.annotation.NonNull;
18+
import androidx.annotation.VisibleForTesting;
1819
import com.google.android.gms.common.internal.Preconditions;
1920
import com.google.android.gms.tasks.Task;
2021
import com.google.android.gms.tasks.Tasks;
21-
import com.google.common.annotations.VisibleForTesting;
2222
import com.google.firebase.FirebaseApp;
2323

2424
/**
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2019 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.local;
16+
17+
import android.content.Context;
18+
import android.content.SharedPreferences;
19+
import androidx.annotation.NonNull;
20+
import androidx.annotation.Nullable;
21+
import com.google.firebase.FirebaseApp;
22+
23+
/**
24+
* A layer that locally caches a few Firebase Installation attributes on top the Firebase
25+
* Installation backend API.
26+
*/
27+
public class FiidCache {
28+
// Status of each cache entry
29+
// NOTE: never change the ordinal of the enum values because the enum values are stored in cache
30+
// as their ordinal numbers.
31+
public enum CacheStatus {
32+
// Cache entry is synced to Firebase backend
33+
REGISTERED,
34+
// Cache entry is waiting for Firebase backend response or internal network retry
35+
UNREGISTERED,
36+
// Cache entry is in error state when syncing with Firebase backend
37+
REGISTER_ERROR,
38+
// Cache entry is in delete state before syncing with Firebase backend
39+
DELETED
40+
}
41+
42+
private static final String SHARED_PREFS_NAME = "FiidCache";
43+
44+
private static final String FIREBASE_INSTALLATION_ID_KEY = "Fid";
45+
private static final String AUTH_TOKEN_KEY = "AuthToken";
46+
private static final String REFRESH_TOKEN_KEY = "RefreshToken";
47+
private static final String TOKEN_CREATION_TIME_IN_SECONDS_KEY = "TokenCreationEpochInSecs";
48+
private static final String EXPIRES_IN_SECONDS_KEY = "ExpiresInSecs";
49+
private static final String CACHE_STATUS_KEY = "Status";
50+
51+
private final SharedPreferences prefs;
52+
private final String persistenceKey;
53+
54+
public FiidCache(@NonNull FirebaseApp firebaseApp) {
55+
// Different FirebaseApp in the same Android application should have the same application
56+
// context and same dir path
57+
prefs =
58+
firebaseApp
59+
.getApplicationContext()
60+
.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
61+
persistenceKey = firebaseApp.getPersistenceKey();
62+
}
63+
64+
@Nullable
65+
public synchronized FiidCacheEntryValue readCacheEntryValue() {
66+
String iid = prefs.getString(getSharedPreferencesKey(FIREBASE_INSTALLATION_ID_KEY), null);
67+
int status = prefs.getInt(getSharedPreferencesKey(CACHE_STATUS_KEY), -1);
68+
String authToken = prefs.getString(getSharedPreferencesKey(AUTH_TOKEN_KEY), null);
69+
String refreshToken = prefs.getString(getSharedPreferencesKey(REFRESH_TOKEN_KEY), null);
70+
long tokenCreationTime =
71+
prefs.getLong(getSharedPreferencesKey(TOKEN_CREATION_TIME_IN_SECONDS_KEY), 0);
72+
long expiresIn = prefs.getLong(getSharedPreferencesKey(EXPIRES_IN_SECONDS_KEY), 0);
73+
74+
if (iid == null || status == -1) {
75+
return null;
76+
}
77+
78+
return FiidCacheEntryValue.create(
79+
iid, CacheStatus.values()[status], authToken, refreshToken, tokenCreationTime, expiresIn);
80+
}
81+
82+
@NonNull
83+
public synchronized boolean insertOrUpdateCacheEntry(@NonNull FiidCacheEntryValue entryValue) {
84+
SharedPreferences.Editor editor = prefs.edit();
85+
editor.putString(
86+
getSharedPreferencesKey(FIREBASE_INSTALLATION_ID_KEY),
87+
entryValue.getFirebaseInstallationId());
88+
editor.putInt(getSharedPreferencesKey(CACHE_STATUS_KEY), entryValue.getCacheStatus().ordinal());
89+
editor.putString(getSharedPreferencesKey(AUTH_TOKEN_KEY), entryValue.getAuthToken());
90+
editor.putString(getSharedPreferencesKey(REFRESH_TOKEN_KEY), entryValue.getRefreshToken());
91+
editor.putLong(
92+
getSharedPreferencesKey(TOKEN_CREATION_TIME_IN_SECONDS_KEY),
93+
entryValue.getTokenCreationEpochInSecs());
94+
editor.putLong(getSharedPreferencesKey(EXPIRES_IN_SECONDS_KEY), entryValue.getExpiresInSecs());
95+
return editor.commit();
96+
}
97+
98+
@NonNull
99+
public synchronized boolean clear() {
100+
SharedPreferences.Editor editor = prefs.edit();
101+
editor.remove(getSharedPreferencesKey(FIREBASE_INSTALLATION_ID_KEY));
102+
editor.remove(getSharedPreferencesKey(CACHE_STATUS_KEY));
103+
editor.remove(getSharedPreferencesKey(AUTH_TOKEN_KEY));
104+
editor.remove(getSharedPreferencesKey(REFRESH_TOKEN_KEY));
105+
editor.remove(getSharedPreferencesKey(TOKEN_CREATION_TIME_IN_SECONDS_KEY));
106+
editor.remove(getSharedPreferencesKey(EXPIRES_IN_SECONDS_KEY));
107+
return editor.commit();
108+
}
109+
110+
private String getSharedPreferencesKey(String key) {
111+
return String.format("%s|%s", persistenceKey, key);
112+
}
113+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2019 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.local;
16+
17+
import androidx.annotation.NonNull;
18+
import com.google.auto.value.AutoValue;
19+
20+
/**
21+
* This class represents a cache entry value in {@link FiidCache}, which contains a Firebase
22+
* instance id and the cache status of this entry.
23+
*/
24+
@AutoValue
25+
public abstract class FiidCacheEntryValue {
26+
27+
@NonNull
28+
public abstract String getFirebaseInstallationId();
29+
30+
@NonNull
31+
public abstract FiidCache.CacheStatus getCacheStatus();
32+
33+
@NonNull
34+
public abstract String getAuthToken();
35+
36+
@NonNull
37+
public abstract String getRefreshToken();
38+
39+
public abstract long getExpiresInSecs();
40+
41+
public abstract long getTokenCreationEpochInSecs();
42+
43+
@NonNull
44+
public static FiidCacheEntryValue create(
45+
@NonNull String firebaseInstallationId,
46+
@NonNull FiidCache.CacheStatus cacheStatus,
47+
@NonNull String authToken,
48+
@NonNull String refreshToken,
49+
long tokenCreationEpochInSecs,
50+
long expiresInSecs) {
51+
return new AutoValue_FiidCacheEntryValue(
52+
firebaseInstallationId,
53+
cacheStatus,
54+
authToken,
55+
refreshToken,
56+
expiresInSecs,
57+
tokenCreationEpochInSecs);
58+
}
59+
}

0 commit comments

Comments
 (0)