diff --git a/firebase-installations/CHANGELOG.md b/firebase-installations/CHANGELOG.md index e47db60b0db..03aab22131c 100644 --- a/firebase-installations/CHANGELOG.md +++ b/firebase-installations/CHANGELOG.md @@ -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. diff --git a/firebase-installations/firebase-installations.gradle b/firebase-installations/firebase-installations.gradle index 931132a11ed..b76723c38b2 100644 --- a/firebase-installations/firebase-installations.gradle +++ b/firebase-installations/firebase-installations.gradle @@ -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 diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/local/IidStore.java b/firebase-installations/src/main/java/com/google/firebase/installations/local/IidStore.java index 84a0a39968d..869fd2dcd48 100644 --- a/firebase-installations/src/main/java/com/google/firebase/installations/local/IidStore.java +++ b/firebase-installations/src/main/java/com/google/firebase/installations/local/IidStore.java @@ -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; @@ -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 STORE_KEY_PUB = PreferencesKeys.stringKey("|S||P|"); + private static final Preferences.Key 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; } @@ -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 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 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) { @@ -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 diff --git a/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java b/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java index 8fd55f580aa..9fd6fa47981 100644 --- a/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java +++ b/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java @@ -32,7 +32,7 @@ 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; @@ -40,6 +40,7 @@ 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; @@ -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; @@ -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"); }