Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions firebase-installations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- [changed] Migrated from SharedPreferences to DataStore (#8355)

# 19.1.1

- [fixed] Addressed FidListener not getting invoked when a FID is created.
Expand Down
1 change: 1 addition & 0 deletions firebase-installations/firebase-installations.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ dependencies {
api(libs.firebase.components)
api("com.google.firebase:firebase-installations-interop:17.3.0")

implementation(libs.androidx.datastore.preferences)
implementation libs.kotlin.stdlib

compileOnly libs.autovalue.annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

import static android.content.ContentValues.TAG;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.datastore.preferences.core.Preferences;
import androidx.datastore.preferences.core.PreferencesKeys;
import com.google.firebase.FirebaseApp;
import com.google.firebase.datastorage.JavaDataStorage;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
Expand All @@ -41,31 +41,28 @@
*/
public class IidStore {
private static final String IID_SHARED_PREFS_NAME = "com.google.android.gms.appid";
private static final String STORE_KEY_PUB = "|S||P|";
private static final String STORE_KEY_ID = "|S|id";
private static final String STORE_KEY_TOKEN = "|T|";
private static final Preferences.Key<String> STORE_KEY_PUB = PreferencesKeys.stringKey("|S||P|");
private static final Preferences.Key<String> STORE_KEY_ID = PreferencesKeys.stringKey("|S|id");
private static final String STORE_KEY_TOKEN_PREFIX = "|T|";
private static final String STORE_KEY_SEPARATOR = "|";
private static final String JSON_TOKEN_KEY = "token";
private static final String JSON_ENCODED_PREFIX = "{";
private static final String[] ALLOWABLE_SCOPES = new String[] {"*", "FCM", "GCM", ""};

@GuardedBy("iidPrefs")
private final SharedPreferences iidPrefs;
private final JavaDataStorage iidDataStore;

private final String defaultSenderId;

public IidStore(@NonNull FirebaseApp firebaseApp) {
iidPrefs =
firebaseApp
.getApplicationContext()
.getSharedPreferences(IID_SHARED_PREFS_NAME, Context.MODE_PRIVATE);
this.iidDataStore =
new JavaDataStorage(firebaseApp.getApplicationContext(), IID_SHARED_PREFS_NAME);

defaultSenderId = getDefaultSenderId(firebaseApp);
}

@VisibleForTesting
public IidStore(@NonNull SharedPreferences iidPrefs, @Nullable String defaultSenderId) {
this.iidPrefs = iidPrefs;
public IidStore(@NonNull JavaDataStorage iidDataStore, @Nullable String defaultSenderId) {
this.iidDataStore = iidDataStore;
this.defaultSenderId = defaultSenderId;
}

Expand Down Expand Up @@ -93,23 +90,22 @@ private static String getDefaultSenderId(FirebaseApp app) {
return projectNumber;
}

private String createTokenKey(@NonNull String senderId, @NonNull String scope) {
return STORE_KEY_TOKEN + senderId + STORE_KEY_SEPARATOR + scope;
private Preferences.Key<String> createTokenKey(@NonNull String senderId, @NonNull String scope) {
return PreferencesKeys.stringKey(
STORE_KEY_TOKEN_PREFIX + senderId + STORE_KEY_SEPARATOR + scope);
}

@Nullable
public String readToken() {
synchronized (iidPrefs) {
for (String scope : ALLOWABLE_SCOPES) {
String tokenKey = createTokenKey(defaultSenderId, scope);
String token = iidPrefs.getString(tokenKey, null);
if (token != null && !token.isEmpty()) {
return token.startsWith(JSON_ENCODED_PREFIX) ? parseIidTokenFromJson(token) : token;
}
for (String scope : ALLOWABLE_SCOPES) {
Preferences.Key<String> tokenKey = createTokenKey(defaultSenderId, scope);
String token = iidDataStore.getSync(tokenKey, null);
if (token != null && !token.isEmpty()) {
return token.startsWith(JSON_ENCODED_PREFIX) ? parseIidTokenFromJson(token) : token;
}

return null;
}

return null;
}

private String parseIidTokenFromJson(String token) {
Expand All @@ -124,47 +120,41 @@ private String parseIidTokenFromJson(String token) {

@Nullable
public String readIid() {
synchronized (iidPrefs) {
// Background: Some versions of the IID-SDK store the Instance-ID in local storage,
// others only store the App-Instance's Public-Key that can be used to calculate the
// Instance-ID.
// Background: Some versions of the IID-SDK store the Instance-ID in local storage,
// others only store the App-Instance's Public-Key that can be used to calculate the
// Instance-ID.

// If such a version was used by this App-Instance, we can directly read the existing
// Instance-ID from storage and return it
String id = readInstanceIdFromLocalStorage();
// If such a version was used by this App-Instance, we can directly read the existing
// Instance-ID from storage and return it
String id = readInstanceIdFromLocalStorage();

if (id != null) {
return id;
}

// If this App-Instance did not store the Instance-ID in local storage, we may be able to find
// its Public-Key in order to calculate the App-Instance's Instance-ID.
return readPublicKeyFromLocalStorageAndCalculateInstanceId();
if (id != null) {
return id;
}

// If this App-Instance did not store the Instance-ID in local storage, we may be able to find
// its Public-Key in order to calculate the App-Instance's Instance-ID.
return readPublicKeyFromLocalStorageAndCalculateInstanceId();
}

@Nullable
private String readInstanceIdFromLocalStorage() {
synchronized (iidPrefs) {
return iidPrefs.getString(STORE_KEY_ID, /* defaultValue= */ null);
}
return iidDataStore.getSync(STORE_KEY_ID, /* defaultValue= */ null);
}

@Nullable
private String readPublicKeyFromLocalStorageAndCalculateInstanceId() {
synchronized (iidPrefs) {
String base64PublicKey = iidPrefs.getString(STORE_KEY_PUB, /* defaultValue= */ null);
if (base64PublicKey == null) {
return null;
}

PublicKey publicKey = parseKey(base64PublicKey);
if (publicKey == null) {
return null;
}
String base64PublicKey = iidDataStore.getSync(STORE_KEY_PUB, /* defaultValue= */ null);
if (base64PublicKey == null) {
return null;
}

return getIdFromPublicKey(publicKey);
PublicKey publicKey = parseKey(base64PublicKey);
if (publicKey == null) {
return null;
}

return getIdFromPublicKey(publicKey);
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import android.content.SharedPreferences;
import androidx.datastore.preferences.core.PreferencesKeys;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.components.Lazy;
import com.google.firebase.concurrent.FirebaseExecutors;
import com.google.firebase.concurrent.TestOnlyExecutors;
import com.google.firebase.datastorage.JavaDataStorage;
import com.google.firebase.installations.FirebaseInstallationsException.Status;
import com.google.firebase.installations.internal.FidListenerHandle;
import com.google.firebase.installations.local.IidStore;
Expand All @@ -55,6 +56,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import kotlin.Unit;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
Expand Down Expand Up @@ -359,103 +361,109 @@ public void testGetId_UnRegisteredId_IssueCreateIdCall() throws Exception {

@Test
public void testReadToken_wildcard() {
SharedPreferences prefs = firebaseApp.getApplicationContext().getSharedPreferences("test", 0);
prefs
.edit()
.putString("|T|123|OTHER", "tokenOTHER")
.putString("|T|unused|*", "tokenFOREIGN")
.putString("|T|123|GCM", "tokenGCM")
.putString("|T|123|FCM", "tokenFCM")
.putString("|T|123|*", "tokenWILDCARD")
.putString("|T|123|", "tokenEMPTY")
.commit();

IidStore iidStore = new IidStore(prefs, "123");
JavaDataStorage dataStorage = new JavaDataStorage(firebaseApp.getApplicationContext(), "test");
dataStorage.editSync(
prefs -> {
prefs.set(PreferencesKeys.stringKey("|T|123|OTHER"), "tokenOTHER");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenFOREIGN");
prefs.set(PreferencesKeys.stringKey("|T|123|GCM"), "tokenGCM");
prefs.set(PreferencesKeys.stringKey("|T|123|FCM"), "tokenFCM");
prefs.set(PreferencesKeys.stringKey("|T|123|*"), "tokenWILDCARD");
prefs.set(PreferencesKeys.stringKey("|T|123|"), "tokenEMPTY");
return Unit.INSTANCE;
});

IidStore iidStore = new IidStore(dataStorage, "123");
assertThat(iidStore.readToken()).isEqualTo("tokenWILDCARD");
}

@Test
public void testReadToken_fcm() {
SharedPreferences prefs = firebaseApp.getApplicationContext().getSharedPreferences("test", 0);
prefs
.edit()
.putString("|T|123|OTHER", "tokenOTHER")
.putString("|T|unused|*", "tokenFOREIGN")
.putString("|T|123|GCM", "tokenGCM")
.putString("|T|123|FCM", "tokenFCM")
.putString("|T|unused|*", "tokenWILDCARD")
.putString("|T|123|", "tokenEMPTY")
.commit();

IidStore iidStore = new IidStore(prefs, "123");
JavaDataStorage dataStorage = new JavaDataStorage(firebaseApp.getApplicationContext(), "test");
dataStorage.editSync(
prefs -> {
prefs.set(PreferencesKeys.stringKey("|T|123|OTHER"), "tokenOTHER");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenFOREIGN");
prefs.set(PreferencesKeys.stringKey("|T|123|GCM"), "tokenGCM");
prefs.set(PreferencesKeys.stringKey("|T|123|FCM"), "tokenFCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenWILDCARD");
prefs.set(PreferencesKeys.stringKey("|T|123|"), "tokenEMPTY");
return Unit.INSTANCE;
});

IidStore iidStore = new IidStore(dataStorage, "123");
assertThat(iidStore.readToken()).isEqualTo("tokenFCM");
}

@Test
public void testReadToken_gcm() {
SharedPreferences prefs = firebaseApp.getApplicationContext().getSharedPreferences("test", 0);
prefs
.edit()
.putString("|T|123|OTHER", "tokenOTHER")
.putString("|T|unused|*", "tokenFOREIGN")
.putString("|T|123|GCM", "tokenGCM")
.putString("|T|unused|FCM", "tokenFCM")
.putString("|T|unused|*", "tokenWILDCARD")
.putString("|T|123|", "tokenEMPTY")
.commit();

IidStore iidStore = new IidStore(prefs, "123");
JavaDataStorage dataStorage = new JavaDataStorage(firebaseApp.getApplicationContext(), "test");
dataStorage.editSync(
prefs -> {
prefs.set(PreferencesKeys.stringKey("|T|123|OTHER"), "tokenOTHER");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenFOREIGN");
prefs.set(PreferencesKeys.stringKey("|T|123|GCM"), "tokenGCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|FCM"), "tokenFCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenWILDCARD");
prefs.set(PreferencesKeys.stringKey("|T|123|"), "tokenEMPTY");
return Unit.INSTANCE;
});

IidStore iidStore = new IidStore(dataStorage, "123");
assertThat(iidStore.readToken()).isEqualTo("tokenGCM");
}

@Test
public void testReadToken_empty() {
SharedPreferences prefs = firebaseApp.getApplicationContext().getSharedPreferences("test", 0);
prefs
.edit()
.putString("|T|123|OTHER", "tokenOTHER")
.putString("|T|unused|*", "tokenFOREIGN")
.putString("|T|unused|GCM", "tokenGCM")
.putString("|T|unused|FCM", "tokenFCM")
.putString("|T|unused|*", "tokenWILDCARD")
.putString("|T|123|", "tokenEMPTY")
.commit();

IidStore iidStore = new IidStore(prefs, "123");
JavaDataStorage dataStorage = new JavaDataStorage(firebaseApp.getApplicationContext(), "test");
dataStorage.editSync(
prefs -> {
prefs.set(PreferencesKeys.stringKey("|T|123|OTHER"), "tokenOTHER");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenFOREIGN");
prefs.set(PreferencesKeys.stringKey("|T|unused|GCM"), "tokenGCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|FCM"), "tokenFCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenWILDCARD");
prefs.set(PreferencesKeys.stringKey("|T|123|"), "tokenEMPTY");
return Unit.INSTANCE;
});

IidStore iidStore = new IidStore(dataStorage, "123");
assertThat(iidStore.readToken()).isEqualTo("tokenEMPTY");
}

@Test
public void testReadToken_null() {
SharedPreferences prefs = firebaseApp.getApplicationContext().getSharedPreferences("test", 0);
prefs
.edit()
.putString("|T|123|OTHER", "tokenOTHER")
.putString("|T|unused|*", "tokenFOREIGN")
.putString("|T|unused|GCM", "tokenGCM")
.putString("|T|unused|FCM", "tokenFCM")
.putString("|T|unused|*", "tokenWILDCARD")
.putString("|T|123|BLAH", "tokenEMPTY")
.commit();

IidStore iidStore = new IidStore(prefs, "123");
JavaDataStorage dataStorage = new JavaDataStorage(firebaseApp.getApplicationContext(), "test");
dataStorage.editSync(
prefs -> {
prefs.set(PreferencesKeys.stringKey("|T|123|OTHER"), "tokenOTHER");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenFOREIGN");
prefs.set(PreferencesKeys.stringKey("|T|unused|GCM"), "tokenGCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|FCM"), "tokenFCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenWILDCARD");
prefs.set(PreferencesKeys.stringKey("|T|123|BLAH"), "tokenEMPTY");
return Unit.INSTANCE;
});

IidStore iidStore = new IidStore(dataStorage, "123");
assertNull(iidStore.readToken());
}

@Test
public void testReadToken_withJsonformatting() {
SharedPreferences prefs = firebaseApp.getApplicationContext().getSharedPreferences("test", 0);
prefs
.edit()
.putString("|T|123|OTHER", "tokenOTHER")
.putString("|T|unused|*", "tokenFOREIGN")
.putString("|T|unused|GCM", "tokenGCM")
.putString("|T|unused|FCM", "tokenFCM")
.putString("|T|123|*", "{\"token\" : \"thetoken\"}")
.putString("|T|123|BLAH", "tokenEMPTY")
.commit();

IidStore iidStore = new IidStore(prefs, "123");
JavaDataStorage dataStorage = new JavaDataStorage(firebaseApp.getApplicationContext(), "test");
dataStorage.editSync(
prefs -> {
prefs.set(PreferencesKeys.stringKey("|T|123|OTHER"), "tokenOTHER");
prefs.set(PreferencesKeys.stringKey("|T|unused|*"), "tokenFOREIGN");
prefs.set(PreferencesKeys.stringKey("|T|unused|GCM"), "tokenGCM");
prefs.set(PreferencesKeys.stringKey("|T|unused|FCM"), "tokenFCM");
prefs.set(PreferencesKeys.stringKey("|T|123|*"), "{\"token\" : \"thetoken\"}");
prefs.set(PreferencesKeys.stringKey("|T|123|"), "tokenEMPTY");
return Unit.INSTANCE;
});

IidStore iidStore = new IidStore(dataStorage, "123");
assertThat(iidStore.readToken()).isEqualTo("thetoken");
}

Expand Down
Loading