Skip to content

Commit fd0a4fa

Browse files
authored
Replace the custom installation id cache SharedPref implementation by file implementation. (#1056)
* Implement Firebase segmentation SDK device local cache * [Firebase Segmentation] Add custom installation id cache layer and tests for it. * Add test for updating cache * Switch to use SQLiteOpenHelper * Switch to use SharedPreferences from SQLite. * Change the cache class to be singleton * Wrap shared pref commit in a async task. * Address comments * Google format fix * Replace some deprecated code. * Package refactor * nit * nit * Add the state machine of updating custom installation id in the local cache and update to Firebase Segmentation backend. CL also contains unit tests. (The http client is not implemented yet.) * minor format fix * Address comments #1 * Http client in Firebase Segmentation SDK to call backend service. * Revert unintentional change * Fix connected device test * Fix connected device test * 1. Add a few annotations to make java code Kotlin friendly 2. Some fixes for the http request format * Fix java format * Fix API version * Change the segmentation API implementation to synchronous and put the entire synchronous code block in async task. * Fix a async getResult race issue. * OkHttpClient -> HttpsUrlConnection * Use gzip for compressing content and fix ourput stream memory leak risk. * Addressed a few comments * FirebaseSegmentation SDK 1. Clean up http client response code. 2. When updateCustomInstallationId is called, on non-retryable server errors, the SDK should clean up the local cache. Instead, for retryable errors, SDK can keep the local cache for retrying update later. * Restrict Firebase API key to Android app package name. * Explicitly add internet permission * Disable registrar test for FirebaseSegmentation. * Disable test lab * Add api info for segmentation API * [FLoC] Replace the custom installation id cache SharedPref implementation by file implementation.
1 parent 1a51f89 commit fd0a4fa

File tree

3 files changed

+145
-37
lines changed

3 files changed

+145
-37
lines changed

firebase-segmentation/api.txt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Signature format: 2.0
2+
package com.google.firebase.segmentation {
3+
4+
public class FirebaseSegmentation {
5+
method @NonNull public static com.google.firebase.segmentation.FirebaseSegmentation getInstance();
6+
method @NonNull public static com.google.firebase.segmentation.FirebaseSegmentation getInstance(@NonNull com.google.firebase.FirebaseApp);
7+
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setCustomInstallationId(@Nullable String);
8+
field public static final String TAG = "FirebaseSegmentation";
9+
}
10+
11+
public class FirebaseSegmentationRegistrar implements com.google.firebase.components.ComponentRegistrar {
12+
ctor public FirebaseSegmentationRegistrar();
13+
method @NonNull public java.util.List<com.google.firebase.components.Component<?>> getComponents();
14+
}
15+
16+
public class SetCustomInstallationIdException extends com.google.firebase.FirebaseException {
17+
method @NonNull public com.google.firebase.segmentation.SetCustomInstallationIdException.Status getStatus();
18+
}
19+
20+
public enum SetCustomInstallationIdException.Status {
21+
enum_constant public static final com.google.firebase.segmentation.SetCustomInstallationIdException.Status BACKEND_ERROR;
22+
enum_constant public static final com.google.firebase.segmentation.SetCustomInstallationIdException.Status CLIENT_ERROR;
23+
enum_constant public static final com.google.firebase.segmentation.SetCustomInstallationIdException.Status DUPLICATED_CUSTOM_INSTALLATION_ID;
24+
enum_constant public static final com.google.firebase.segmentation.SetCustomInstallationIdException.Status UNKOWN;
25+
}
26+
27+
}
28+
29+
package com.google.firebase.segmentation.local {
30+
31+
public class CustomInstallationIdCache {
32+
ctor public CustomInstallationIdCache(@NonNull com.google.firebase.FirebaseApp);
33+
method @NonNull public boolean clear();
34+
method @NonNull public boolean insertOrUpdateCacheEntry(@NonNull com.google.firebase.segmentation.local.CustomInstallationIdCacheEntryValue);
35+
method @Nullable public com.google.firebase.segmentation.local.CustomInstallationIdCacheEntryValue readCacheEntryValue();
36+
}
37+
38+
public enum CustomInstallationIdCache.CacheStatus {
39+
enum_constant public static final com.google.firebase.segmentation.local.CustomInstallationIdCache.CacheStatus PENDING_CLEAR;
40+
enum_constant public static final com.google.firebase.segmentation.local.CustomInstallationIdCache.CacheStatus PENDING_UPDATE;
41+
enum_constant public static final com.google.firebase.segmentation.local.CustomInstallationIdCache.CacheStatus SYNCED;
42+
}
43+
44+
public abstract class CustomInstallationIdCacheEntryValue {
45+
ctor public CustomInstallationIdCacheEntryValue();
46+
method @NonNull public static com.google.firebase.segmentation.local.CustomInstallationIdCacheEntryValue create(@NonNull String, @NonNull String, @NonNull com.google.firebase.segmentation.local.CustomInstallationIdCache.CacheStatus);
47+
method @NonNull public abstract com.google.firebase.segmentation.local.CustomInstallationIdCache.CacheStatus getCacheStatus();
48+
method @NonNull public abstract String getCustomInstallationId();
49+
method @NonNull public abstract String getFirebaseInstanceId();
50+
}
51+
52+
}
53+
54+
package com.google.firebase.segmentation.remote {
55+
56+
public class SegmentationServiceClient {
57+
ctor public SegmentationServiceClient(@NonNull android.content.Context);
58+
method @NonNull public com.google.firebase.segmentation.remote.SegmentationServiceClient.Code clearCustomInstallationId(long, @NonNull String, @NonNull String, @NonNull String);
59+
method @NonNull public com.google.firebase.segmentation.remote.SegmentationServiceClient.Code updateCustomInstallationId(long, @NonNull String, @NonNull String, @NonNull String, @NonNull String);
60+
}
61+
62+
public enum SegmentationServiceClient.Code {
63+
enum_constant public static final com.google.firebase.segmentation.remote.SegmentationServiceClient.Code CONFLICT;
64+
enum_constant public static final com.google.firebase.segmentation.remote.SegmentationServiceClient.Code HTTP_CLIENT_ERROR;
65+
enum_constant public static final com.google.firebase.segmentation.remote.SegmentationServiceClient.Code NETWORK_ERROR;
66+
enum_constant public static final com.google.firebase.segmentation.remote.SegmentationServiceClient.Code OK;
67+
enum_constant public static final com.google.firebase.segmentation.remote.SegmentationServiceClient.Code SERVER_ERROR;
68+
enum_constant public static final com.google.firebase.segmentation.remote.SegmentationServiceClient.Code UNAUTHORIZED;
69+
}
70+
71+
}
72+

firebase-segmentation/firebase-segmentation.gradle

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ plugins {
1616
id 'firebase-library'
1717
}
1818

19-
firebaseLibrary {
20-
testLab.enabled = true
21-
}
22-
2319
android {
2420
compileSdkVersion project.targetSdkVersion
2521

@@ -48,7 +44,7 @@ dependencies {
4844
exclude group: "com.google.firebase", module: "firebase-common"
4945
}
5046

51-
implementation 'androidx.appcompat:appcompat:1.0.2'
47+
implementation 'androidx.appcompat:appcompat:1.1.0'
5248
implementation 'androidx.multidex:multidex:2.0.1'
5349
implementation 'com.google.android.gms:play-services-tasks:17.0.0'
5450

@@ -59,7 +55,7 @@ dependencies {
5955
testImplementation 'junit:junit:4.12'
6056
testImplementation "org.robolectric:robolectric:$robolectricVersion"
6157

62-
androidTestImplementation "androidx.annotation:annotation:1.0.0"
58+
androidTestImplementation "androidx.annotation:annotation:1.1.0"
6359
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
6460
androidTestImplementation 'androidx.test:rules:1.2.0'
6561
androidTestImplementation 'androidx.test:runner:1.2.0'

firebase-segmentation/src/main/java/com/google/firebase/segmentation/local/CustomInstallationIdCache.java

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@
1414

1515
package com.google.firebase.segmentation.local;
1616

17-
import android.content.Context;
18-
import android.content.SharedPreferences;
1917
import androidx.annotation.NonNull;
2018
import androidx.annotation.Nullable;
2119
import com.google.firebase.FirebaseApp;
20+
import java.io.ByteArrayOutputStream;
21+
import java.io.File;
22+
import java.io.FileInputStream;
23+
import java.io.FileOutputStream;
24+
import java.io.IOException;
25+
import org.json.JSONException;
26+
import org.json.JSONObject;
2227

2328
/**
2429
* A layer that locally caches a few Firebase Segmentation attributes on top the Segmentation
@@ -40,59 +45,94 @@ public enum CacheStatus {
4045
PENDING_CLEAR
4146
}
4247

43-
private static final String SHARED_PREFS_NAME = "CustomInstallationIdCache";
48+
private static final String DATA_FILE_NAME_PREFIX = "PersistedCustomInstallationId";
4449

4550
private static final String CUSTOM_INSTALLATION_ID_KEY = "Cid";
4651
private static final String INSTANCE_ID_KEY = "Iid";
4752
private static final String CACHE_STATUS_KEY = "Status";
4853

49-
private final SharedPreferences prefs;
50-
private final String persistenceKey;
54+
private final File dataFile;
55+
private final FirebaseApp firebaseApp;
5156

5257
public CustomInstallationIdCache(@NonNull FirebaseApp firebaseApp) {
53-
// Different FirebaseApp in the same Android application should have the same application
54-
// context and same dir path
55-
prefs =
56-
firebaseApp
57-
.getApplicationContext()
58-
.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
59-
persistenceKey = firebaseApp.getPersistenceKey();
58+
this.firebaseApp = firebaseApp;
59+
// Store custom installation id in different file for different FirebaseApp.
60+
dataFile =
61+
new File(
62+
firebaseApp.getApplicationContext().getFilesDir(),
63+
String.format("%s.%s.json", DATA_FILE_NAME_PREFIX, firebaseApp.getPersistenceKey()));
6064
}
6165

6266
@Nullable
6367
public synchronized CustomInstallationIdCacheEntryValue readCacheEntryValue() {
64-
String cid = prefs.getString(getSharedPreferencesKey(CUSTOM_INSTALLATION_ID_KEY), null);
65-
String iid = prefs.getString(getSharedPreferencesKey(INSTANCE_ID_KEY), null);
66-
int status = prefs.getInt(getSharedPreferencesKey(CACHE_STATUS_KEY), -1);
68+
JSONObject cidInfo = readCidInfoFromFile();
69+
String cid = cidInfo.optString(CUSTOM_INSTALLATION_ID_KEY, null);
70+
String iid = cidInfo.optString(INSTANCE_ID_KEY, null);
71+
int status = cidInfo.optInt(CACHE_STATUS_KEY, -1);
6772

6873
if (cid == null || iid == null || status == -1) {
6974
return null;
7075
}
71-
7276
return CustomInstallationIdCacheEntryValue.create(cid, iid, CacheStatus.values()[status]);
7377
}
7478

79+
private JSONObject readCidInfoFromFile() {
80+
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
81+
final byte[] tmpBuf = new byte[16 * 1024];
82+
try (FileInputStream fis = new FileInputStream(dataFile)) {
83+
while (true) {
84+
int numRead = fis.read(tmpBuf, 0, tmpBuf.length);
85+
if (numRead < 0) {
86+
break;
87+
}
88+
baos.write(tmpBuf, 0, numRead);
89+
}
90+
return new JSONObject(baos.toString());
91+
} catch (IOException | JSONException e) {
92+
return new JSONObject();
93+
}
94+
}
95+
96+
/**
97+
* Write the prefs to a JSON object, serialize them into a JSON string and write the bytes to a
98+
* temp file. After writing and closing the temp file, rename it over to the actual
99+
* DATA_FILE_NAME.
100+
*/
75101
@NonNull
76102
public synchronized boolean insertOrUpdateCacheEntry(
77103
@NonNull CustomInstallationIdCacheEntryValue entryValue) {
78-
SharedPreferences.Editor editor = prefs.edit();
79-
editor.putString(
80-
getSharedPreferencesKey(CUSTOM_INSTALLATION_ID_KEY), entryValue.getCustomInstallationId());
81-
editor.putString(getSharedPreferencesKey(INSTANCE_ID_KEY), entryValue.getFirebaseInstanceId());
82-
editor.putInt(getSharedPreferencesKey(CACHE_STATUS_KEY), entryValue.getCacheStatus().ordinal());
83-
return editor.commit();
104+
try {
105+
// Write the prefs into a JSON object
106+
JSONObject json = new JSONObject();
107+
json.put(CUSTOM_INSTALLATION_ID_KEY, entryValue.getCustomInstallationId());
108+
json.put(INSTANCE_ID_KEY, entryValue.getFirebaseInstanceId());
109+
json.put(CACHE_STATUS_KEY, entryValue.getCacheStatus().ordinal());
110+
File tmpFile =
111+
File.createTempFile(
112+
String.format("%s.%s", DATA_FILE_NAME_PREFIX, firebaseApp.getPersistenceKey()),
113+
"tmp",
114+
firebaseApp.getApplicationContext().getFilesDir());
115+
116+
// Serialize the JSON object into a string and write the bytes to a temp file
117+
FileOutputStream fos = new FileOutputStream(tmpFile);
118+
fos.write(json.toString().getBytes("UTF-8"));
119+
fos.close();
120+
121+
// Snapshot the temp file to the actual file
122+
if (!tmpFile.renameTo(dataFile)) {
123+
throw new IOException("unable to rename the tmpfile to " + dataFile.getPath());
124+
}
125+
} catch (JSONException | IOException e) {
126+
return false;
127+
}
128+
return true;
84129
}
85130

86131
@NonNull
87132
public synchronized boolean clear() {
88-
SharedPreferences.Editor editor = prefs.edit();
89-
editor.remove(getSharedPreferencesKey(CUSTOM_INSTALLATION_ID_KEY));
90-
editor.remove(getSharedPreferencesKey(INSTANCE_ID_KEY));
91-
editor.remove(getSharedPreferencesKey(CACHE_STATUS_KEY));
92-
return editor.commit();
93-
}
94-
95-
private String getSharedPreferencesKey(String key) {
96-
return String.format("%s|%s", persistenceKey, key);
133+
if (!dataFile.exists()) {
134+
return true;
135+
}
136+
return dataFile.delete();
97137
}
98138
}

0 commit comments

Comments
 (0)