From a2020ac7f6c4c7c57ef80eaf2c7a052788e37b73 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Mon, 24 Mar 2025 13:32:01 -0500
Subject: [PATCH 01/20] Implement temp DataStore class

---
 firebase-common/firebase-common.gradle.kts    |  1 +
 .../google/firebase/datastore/DataStore.kt    | 68 +++++++++++++++++++
 2 files changed, 69 insertions(+)
 create mode 100644 firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt

diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts
index 88bfa58e27d..c0c379a6ace 100644
--- a/firebase-common/firebase-common.gradle.kts
+++ b/firebase-common/firebase-common.gradle.kts
@@ -57,6 +57,7 @@ dependencies {
 
   api("com.google.firebase:firebase-components:18.0.0")
   api("com.google.firebase:firebase-annotations:16.2.0")
+  implementation("androidx.datastore:datastore-preferences:1.1.3")
   implementation(libs.androidx.annotation)
   implementation(libs.androidx.futures)
   implementation(libs.kotlin.stdlib)
diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
new file mode 100644
index 00000000000..0828e5c0519
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -0,0 +1,68 @@
+package com.google.firebase.datastore
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.SharedPreferencesMigration
+import androidx.datastore.preferences.core.MutablePreferences
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.runBlocking
+
+/**
+ * Don't use this unless you're bridging Java code Use your own [dataStore] directly in Kotlin code,
+ * or if you're writing new code.
+ */
+class DataStorage(val context: Context, val name: String) {
+  private val transforming = ThreadLocal<Boolean>()
+
+  private val Context.dataStore: DataStore<Preferences> by
+    preferencesDataStore(
+      name = name,
+      produceMigrations = { listOf(SharedPreferencesMigration(it, name)) }
+    )
+
+  private val dataStore = context.dataStore
+
+  fun <T> getSync(key: Preferences.Key<T>, defaultValue: T): T = runBlocking {
+    dataStore.data.firstOrNull()?.get(key) ?: defaultValue
+  }
+
+  fun <T> contains(key: Preferences.Key<T>): Boolean = runBlocking {
+    dataStore.data.firstOrNull()?.contains(key) ?: false
+  }
+
+  fun <T> putSync(key: Preferences.Key<T>, value: T): Preferences = runBlocking {
+    dataStore.edit { it[key] = value }
+  }
+
+  /** Do not modify the returned map (should be obvious, since it's immutable though) */
+  fun getAllSync(): Map<Preferences.Key<*>, Any> = runBlocking {
+    dataStore.data.firstOrNull()?.asMap() ?: emptyMap()
+  }
+
+  /** Edit calls should not be called within edit calls. Is there a way to prevent this? */
+  fun editSync(transform: (MutablePreferences) -> Unit): Preferences = runBlocking {
+    if (transforming.get() == true) {
+      throw IllegalStateException(
+        """
+        Don't call DataStorage.edit() from within an existing edit() callback.
+        This causes deadlocks, and is generally indicative of a code smell.
+        Instead, either pass around the initial `MutablePreferences` instance, or don't do everything in a single callback. 
+      """
+          .trimIndent()
+      )
+    }
+    transforming.set(true)
+    try {
+      dataStore.edit { transform(it) }
+    } finally {
+      transforming.set(false)
+    }
+  }
+}
+
+/** Helper for Java code */
+fun <T> Preferences.getOrDefault(key: Preferences.Key<T>, defaultValue: T) =
+  get(key) ?: defaultValue

From 58c97dfc6b4fc39ded5e5552f95e3378757dfe14 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Mon, 24 Mar 2025 13:32:09 -0500
Subject: [PATCH 02/20] Migrate common

---
 .../heartbeatinfo/HeartBeatInfoStorage.java   | 231 ++++++++++--------
 .../DefaultHeartBeatControllerTest.java       |  14 +-
 .../HeartBeatInfoStorageTest.java             |  47 ++--
 3 files changed, 157 insertions(+), 135 deletions(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
index a8a9fee5104..c2f04ccf0a0 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
@@ -15,10 +15,13 @@
 package com.google.firebase.heartbeatinfo;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.os.Build;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
+import androidx.datastore.preferences.core.*;
+import androidx.datastore.preferences.core.Preferences;
+import com.google.firebase.datastore.DataStorage;
+import com.google.firebase.datastore.DataStoreKt;
 import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -40,91 +43,97 @@
 class HeartBeatInfoStorage {
   private static HeartBeatInfoStorage instance = null;
 
-  private static final String GLOBAL = "fire-global";
+  private static final Preferences.Key<Long> GLOBAL = PreferencesKeys.longKey("fire-global");
 
   private static final String PREFERENCES_NAME = "FirebaseAppHeartBeat";
 
   private static final String HEARTBEAT_PREFERENCES_NAME = "FirebaseHeartBeat";
 
-  private static final String HEART_BEAT_COUNT_TAG = "fire-count";
+  private static final Preferences.Key<Long> HEART_BEAT_COUNT_TAG =
+      PreferencesKeys.longKey("fire-count");
 
-  private static final String LAST_STORED_DATE = "last-used-date";
+  private static final Preferences.Key<String> LAST_STORED_DATE =
+      PreferencesKeys.stringKey("last-used-date");
 
   // As soon as you hit the limit of heartbeats. The number of stored heartbeats is halved.
   private static final int HEART_BEAT_COUNT_LIMIT = 30;
 
-  private final SharedPreferences firebaseSharedPreferences;
+  private final DataStorage firebaseDataStore;
 
   public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
-    this.firebaseSharedPreferences =
-        applicationContext.getSharedPreferences(
-            HEARTBEAT_PREFERENCES_NAME + persistenceKey, Context.MODE_PRIVATE);
+    this.firebaseDataStore =
+        new DataStorage(applicationContext, HEARTBEAT_PREFERENCES_NAME + persistenceKey);
   }
 
   @VisibleForTesting
   @RestrictTo(RestrictTo.Scope.TESTS)
-  HeartBeatInfoStorage(SharedPreferences firebaseSharedPreferences) {
-    this.firebaseSharedPreferences = firebaseSharedPreferences;
+  HeartBeatInfoStorage(DataStorage dataStorage) {
+    this.firebaseDataStore = dataStorage;
   }
 
   @VisibleForTesting
   @RestrictTo(RestrictTo.Scope.TESTS)
   int getHeartBeatCount() {
-    return (int) this.firebaseSharedPreferences.getLong(HEART_BEAT_COUNT_TAG, 0);
+    return this.firebaseDataStore.getSync(HEART_BEAT_COUNT_TAG, 0L).intValue();
   }
 
   synchronized void deleteAllHeartBeats() {
-    SharedPreferences.Editor editor = firebaseSharedPreferences.edit();
-    int counter = 0;
-    for (Map.Entry<String, ?> entry : this.firebaseSharedPreferences.getAll().entrySet()) {
-      if (entry.getValue() instanceof Set) {
-        // All other heartbeats other than the heartbeats stored today will be deleted.
-        Set<String> dates = (Set<String>) entry.getValue();
-        String today = getFormattedDate(System.currentTimeMillis());
-        String key = entry.getKey();
-        if (dates.contains(today)) {
-          Set<String> userAgentDateSet = new HashSet<>();
-          userAgentDateSet.add(today);
-          counter += 1;
-          editor.putStringSet(key, userAgentDateSet);
-        } else {
-          editor.remove(key);
-        }
-      }
-    }
-    if (counter == 0) {
-      editor.remove(HEART_BEAT_COUNT_TAG);
-    } else {
-      editor.putLong(HEART_BEAT_COUNT_TAG, counter);
-    }
+    firebaseDataStore.editSync(
+        (pref) -> {
+          long counter = 0;
+          for (Map.Entry<Preferences.Key<?>, Object> entry : pref.asMap().entrySet()) {
+            if (entry.getValue() instanceof Set) {
+              // All other heartbeats other than the heartbeats stored today will be deleted.
+              Preferences.Key<Set<String>> key = (Preferences.Key<Set<String>>) entry.getKey();
+              Set<String> dates = (Set<String>) entry.getValue();
+              String today = getFormattedDate(System.currentTimeMillis());
+
+              if (dates.contains(today)) {
+                pref.set(key, Set.of(today));
+                counter += 1;
+              } else {
+                pref.remove(key);
+              }
+            }
+          }
+          if (counter == 0) {
+            pref.remove(HEART_BEAT_COUNT_TAG);
+          } else {
+            pref.set(HEART_BEAT_COUNT_TAG, counter);
+          }
 
-    editor.commit();
+          return null;
+        });
   }
 
   synchronized List<HeartBeatResult> getAllHeartBeats() {
     ArrayList<HeartBeatResult> heartBeatResults = new ArrayList<>();
-    for (Map.Entry<String, ?> entry : this.firebaseSharedPreferences.getAll().entrySet()) {
+    String today = getFormattedDate(System.currentTimeMillis());
+
+    for (Map.Entry<Preferences.Key<?>, Object> entry :
+        this.firebaseDataStore.getAllSync().entrySet()) {
       if (entry.getValue() instanceof Set) {
         Set<String> dates = new HashSet<>((Set<String>) entry.getValue());
-        String today = getFormattedDate(System.currentTimeMillis());
         dates.remove(today);
         if (!dates.isEmpty()) {
-          heartBeatResults.add(
-              HeartBeatResult.create(entry.getKey(), new ArrayList<String>(dates)));
+          heartBeatResults.add(HeartBeatResult.create(entry.getKey().getName(), new ArrayList<>()));
         }
       }
     }
+
     updateGlobalHeartBeat(System.currentTimeMillis());
+
     return heartBeatResults;
   }
 
-  private synchronized String getStoredUserAgentString(String dateString) {
-    for (Map.Entry<String, ?> entry : firebaseSharedPreferences.getAll().entrySet()) {
+  private synchronized Preferences.Key<Set<String>> getStoredUserAgentString(
+      MutablePreferences preferences, String dateString) {
+    for (Map.Entry<Preferences.Key<?>, Object> entry : preferences.asMap().entrySet()) {
       if (entry.getValue() instanceof Set) {
         Set<String> dateSet = (Set<String>) entry.getValue();
         for (String date : dateSet) {
           if (dateString.equals(date)) {
-            return entry.getKey();
+            return PreferencesKeys.stringSetKey(entry.getKey().getName());
           }
         }
       }
@@ -132,36 +141,40 @@ private synchronized String getStoredUserAgentString(String dateString) {
     return null;
   }
 
-  private synchronized void updateStoredUserAgent(String userAgent, String dateString) {
-    removeStoredDate(dateString);
+  private synchronized void updateStoredUserAgent(
+      MutablePreferences preferences, Preferences.Key<Set<String>> userAgent, String dateString) {
+    removeStoredDate(preferences, dateString);
     Set<String> userAgentDateSet =
-        new HashSet<String>(
-            firebaseSharedPreferences.getStringSet(userAgent, new HashSet<String>()));
+        new HashSet<>(DataStoreKt.getOrDefault(preferences, userAgent, new HashSet<>()));
     userAgentDateSet.add(dateString);
-    firebaseSharedPreferences.edit().putStringSet(userAgent, userAgentDateSet).commit();
+    preferences.set(userAgent, userAgentDateSet);
   }
 
-  private synchronized void removeStoredDate(String dateString) {
+  private synchronized void removeStoredDate(MutablePreferences preferences, String dateString) {
     // Find stored heartbeat and clear it.
-    String userAgentString = getStoredUserAgentString(dateString);
-    if (userAgentString == null) {
+    Preferences.Key<Set<String>> userAgent = getStoredUserAgentString(preferences, dateString);
+    if (userAgent == null) {
       return;
     }
     Set<String> userAgentDateSet =
-        new HashSet<String>(
-            firebaseSharedPreferences.getStringSet(userAgentString, new HashSet<String>()));
+        new HashSet<>(DataStoreKt.getOrDefault(preferences, userAgent, new HashSet<>()));
     userAgentDateSet.remove(dateString);
     if (userAgentDateSet.isEmpty()) {
-      firebaseSharedPreferences.edit().remove(userAgentString).commit();
+      preferences.remove(userAgent);
     } else {
-      firebaseSharedPreferences.edit().putStringSet(userAgentString, userAgentDateSet).commit();
+      preferences.set(userAgent, userAgentDateSet);
     }
   }
 
   synchronized void postHeartBeatCleanUp() {
     String dateString = getFormattedDate(System.currentTimeMillis());
-    firebaseSharedPreferences.edit().putString(LAST_STORED_DATE, dateString).commit();
-    removeStoredDate(dateString);
+
+    firebaseDataStore.editSync(
+        (pref) -> {
+          pref.set(LAST_STORED_DATE, dateString);
+          removeStoredDate(pref, dateString);
+          return null;
+        });
   }
 
   private synchronized String getFormattedDate(long millis) {
@@ -176,71 +189,77 @@ private synchronized String getFormattedDate(long millis) {
 
   synchronized void storeHeartBeat(long millis, String userAgentString) {
     String dateString = getFormattedDate(millis);
-    String lastDateString = firebaseSharedPreferences.getString(LAST_STORED_DATE, "");
-    if (lastDateString.equals(dateString)) {
-      String storedUserAgentString = getStoredUserAgentString(dateString);
-      if (storedUserAgentString == null) {
-        // Heartbeat already sent for today.
-        return;
-      }
-      if (storedUserAgentString.equals(userAgentString)) {
-        // UserAgent not updated.
-        return;
-      } else {
-        updateStoredUserAgent(userAgentString, dateString);
-        return;
-      }
-    }
-    long heartBeatCount = firebaseSharedPreferences.getLong(HEART_BEAT_COUNT_TAG, 0);
-    if (heartBeatCount + 1 == HEART_BEAT_COUNT_LIMIT) {
-      cleanUpStoredHeartBeats();
-      heartBeatCount = firebaseSharedPreferences.getLong(HEART_BEAT_COUNT_TAG, 0);
-    }
-    Set<String> userAgentDateSet =
-        new HashSet<String>(
-            firebaseSharedPreferences.getStringSet(userAgentString, new HashSet<String>()));
-    userAgentDateSet.add(dateString);
-    heartBeatCount += 1;
-    firebaseSharedPreferences
-        .edit()
-        .putStringSet(userAgentString, userAgentDateSet)
-        .putLong(HEART_BEAT_COUNT_TAG, heartBeatCount)
-        .putString(LAST_STORED_DATE, dateString)
-        .commit();
+    Preferences.Key<Set<String>> userAgent = PreferencesKeys.stringSetKey(userAgentString);
+    firebaseDataStore.editSync(
+        (pref) -> {
+          String lastDateString = DataStoreKt.getOrDefault(pref, LAST_STORED_DATE, "");
+          if (lastDateString.equals(dateString)) {
+            Preferences.Key<Set<String>> storedUserAgent =
+                getStoredUserAgentString(pref, dateString);
+            if (storedUserAgent == null) {
+              // Heartbeat already sent for today.
+              return null;
+            } else if (storedUserAgent.getName().equals(userAgentString)) {
+              // UserAgent not updated.
+              return null;
+            } else {
+              updateStoredUserAgent(pref, userAgent, dateString);
+              return null;
+            }
+          }
+          long heartBeatCount = DataStoreKt.getOrDefault(pref, HEART_BEAT_COUNT_TAG, 0L);
+          if (heartBeatCount + 1 == HEART_BEAT_COUNT_LIMIT) {
+            heartBeatCount = cleanUpStoredHeartBeats(pref);
+          }
+          Set<String> userAgentDateSet =
+              new HashSet<>(DataStoreKt.getOrDefault(pref, userAgent, new HashSet<>()));
+          userAgentDateSet.add(dateString);
+          heartBeatCount += 1;
+
+          pref.set(userAgent, userAgentDateSet);
+          pref.set(HEART_BEAT_COUNT_TAG, heartBeatCount);
+          pref.set(LAST_STORED_DATE, dateString);
+
+          return null;
+        });
   }
 
-  private synchronized void cleanUpStoredHeartBeats() {
-    long heartBeatCount = firebaseSharedPreferences.getLong(HEART_BEAT_COUNT_TAG, 0);
+  private synchronized long cleanUpStoredHeartBeats(MutablePreferences preferences) {
+    long heartBeatCount = DataStoreKt.getOrDefault(preferences, HEART_BEAT_COUNT_TAG, 0L);
+
     String lowestDate = null;
     String userAgentString = "";
-    for (Map.Entry<String, ?> entry : firebaseSharedPreferences.getAll().entrySet()) {
+    Set<String> userAgentDateSet = new HashSet<>();
+    for (Map.Entry<Preferences.Key<?>, Object> entry : preferences.asMap().entrySet()) {
       if (entry.getValue() instanceof Set) {
         Set<String> dateSet = (Set<String>) entry.getValue();
         for (String date : dateSet) {
           if (lowestDate == null || lowestDate.compareTo(date) > 0) {
+            userAgentDateSet = dateSet;
             lowestDate = date;
-            userAgentString = entry.getKey();
+            userAgentString = entry.getKey().getName();
           }
         }
       }
     }
-    Set<String> userAgentDateSet =
-        new HashSet<String>(
-            firebaseSharedPreferences.getStringSet(userAgentString, new HashSet<String>()));
+    userAgentDateSet = new HashSet<>(userAgentDateSet);
     userAgentDateSet.remove(lowestDate);
-    firebaseSharedPreferences
-        .edit()
-        .putStringSet(userAgentString, userAgentDateSet)
-        .putLong(HEART_BEAT_COUNT_TAG, heartBeatCount - 1)
-        .commit();
+    preferences.set(PreferencesKeys.stringSetKey(userAgentString), userAgentDateSet);
+    preferences.set(HEART_BEAT_COUNT_TAG, heartBeatCount - 1);
+
+    return heartBeatCount - 1;
   }
 
   synchronized long getLastGlobalHeartBeat() {
-    return firebaseSharedPreferences.getLong(GLOBAL, -1);
+    return firebaseDataStore.getSync(GLOBAL, -1L);
   }
 
   synchronized void updateGlobalHeartBeat(long millis) {
-    firebaseSharedPreferences.edit().putLong(GLOBAL, millis).commit();
+    firebaseDataStore.editSync(
+        (pref) -> {
+          pref.set(GLOBAL, millis);
+          return null;
+        });
   }
 
   synchronized boolean isSameDateUtc(long base, long target) {
@@ -252,15 +271,11 @@ synchronized boolean isSameDateUtc(long base, long target) {
    A sdk heartbeat is sent either when there is no heartbeat sent ever for the sdk or
    when the last heartbeat send for the sdk was later than a day before.
   */
-  synchronized boolean shouldSendSdkHeartBeat(String heartBeatTag, long millis) {
-    if (firebaseSharedPreferences.contains(heartBeatTag)) {
-      if (!this.isSameDateUtc(firebaseSharedPreferences.getLong(heartBeatTag, -1), millis)) {
-        firebaseSharedPreferences.edit().putLong(heartBeatTag, millis).commit();
-        return true;
-      }
+  synchronized boolean shouldSendSdkHeartBeat(Preferences.Key<Long> heartBeatTag, long millis) {
+    if (this.isSameDateUtc(firebaseDataStore.getSync(heartBeatTag, -1L), millis)) {
       return false;
     } else {
-      firebaseSharedPreferences.edit().putLong(heartBeatTag, millis).commit();
+      firebaseDataStore.putSync(heartBeatTag, millis);
       return true;
     }
   }
diff --git a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java
index fe564ca242e..a57c1768f66 100644
--- a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java
+++ b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java
@@ -25,10 +25,10 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import com.google.common.collect.ImmutableSet;
+import com.google.firebase.datastore.DataStorage;
 import com.google.firebase.platforminfo.UserAgentPublisher;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -107,10 +107,8 @@ public void generateHeartBeat_oneHeartBeat() throws InterruptedException, Timeou
   public void firstNewThenOld_synchronizedCorrectly()
       throws InterruptedException, TimeoutException {
     Context context = ApplicationProvider.getApplicationContext();
-    SharedPreferences heartBeatSharedPreferences =
-        context.getSharedPreferences("testHeartBeat", Context.MODE_PRIVATE);
-    HeartBeatInfoStorage heartBeatInfoStorage =
-        new HeartBeatInfoStorage(heartBeatSharedPreferences);
+    DataStorage heartBeatDataStore = new DataStorage(context, "testHeartBeat");
+    HeartBeatInfoStorage heartBeatInfoStorage = new HeartBeatInfoStorage(heartBeatDataStore);
     DefaultHeartBeatController controller =
         new DefaultHeartBeatController(
             () -> heartBeatInfoStorage, logSources, executor, () -> publisher, context);
@@ -130,10 +128,8 @@ public void firstNewThenOld_synchronizedCorrectly()
   public void firstOldThenNew_synchronizedCorrectly()
       throws InterruptedException, TimeoutException {
     Context context = ApplicationProvider.getApplicationContext();
-    SharedPreferences heartBeatSharedPreferences =
-        context.getSharedPreferences("testHeartBeat", Context.MODE_PRIVATE);
-    HeartBeatInfoStorage heartBeatInfoStorage =
-        new HeartBeatInfoStorage(heartBeatSharedPreferences);
+    DataStorage heartBeatDataStore = new DataStorage(context, "testHeartBeat");
+    HeartBeatInfoStorage heartBeatInfoStorage = new HeartBeatInfoStorage(heartBeatDataStore);
     DefaultHeartBeatController controller =
         new DefaultHeartBeatController(
             () -> heartBeatInfoStorage, logSources, executor, () -> publisher, context);
diff --git a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java
index 81b191f117d..b83ba4d3523 100644
--- a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java
+++ b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java
@@ -17,12 +17,15 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
-import android.content.SharedPreferences;
+import androidx.datastore.preferences.core.Preferences;
+import androidx.datastore.preferences.core.PreferencesKeys;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
+import com.google.firebase.datastore.DataStorage;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Set;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -31,23 +34,30 @@
 
 @RunWith(AndroidJUnit4.class)
 public class HeartBeatInfoStorageTest {
-  private final String testSdk = "testSdk";
-  private final String GLOBAL = "fire-global";
+  private final Preferences.Key<Long> testSdk = PreferencesKeys.longKey("testSdk");
+  private final Preferences.Key<Long> GLOBAL = PreferencesKeys.longKey("fire-global");
   private static final int HEART_BEAT_COUNT_LIMIT = 30;
   private static Context applicationContext = ApplicationProvider.getApplicationContext();
-  private static SharedPreferences heartBeatSharedPreferences =
-      applicationContext.getSharedPreferences("testHeartBeat", Context.MODE_PRIVATE);
-  private HeartBeatInfoStorage heartBeatInfoStorage =
-      new HeartBeatInfoStorage(heartBeatSharedPreferences);
+  private static DataStorage heartBeatDataStore =
+      new DataStorage(applicationContext, "testHeartBeat");
+  private HeartBeatInfoStorage heartBeatInfoStorage = new HeartBeatInfoStorage(heartBeatDataStore);
 
   @Before
   public void setUp() {
-    heartBeatSharedPreferences.edit().clear().apply();
+    heartBeatDataStore.editSync(
+        (pref) -> {
+          pref.clear();
+          return null;
+        });
   }
 
   @After
   public void tearDown() {
-    heartBeatSharedPreferences.edit().clear().apply();
+    heartBeatDataStore.editSync(
+        (pref) -> {
+          pref.clear();
+          return null;
+        });
   }
 
   @Config(sdk = 29)
@@ -169,31 +179,32 @@ public void storeExcessHeartBeats_cleanUpProperly() {
   public void shouldSendSdkHeartBeat_answerIsYes() {
     long currentTime = System.currentTimeMillis();
     assertThat(heartBeatInfoStorage.shouldSendSdkHeartBeat(testSdk, 1)).isTrue();
-    assertThat(heartBeatSharedPreferences.getLong(testSdk, -1)).isEqualTo(1);
+    assertThat(heartBeatDataStore.getSync(testSdk, -1L)).isEqualTo(1);
     assertThat(heartBeatInfoStorage.shouldSendSdkHeartBeat(testSdk, currentTime)).isTrue();
-    assertThat(heartBeatSharedPreferences.getLong(testSdk, -1)).isEqualTo(currentTime);
+    assertThat(heartBeatDataStore.getSync(testSdk, -1L)).isEqualTo(currentTime);
   }
 
   @Test
   public void shouldSendGlobalHeartBeat_answerIsNo() {
-    heartBeatSharedPreferences.edit().putLong(GLOBAL, 1).apply();
+    heartBeatDataStore.putSync(GLOBAL, 1L);
     assertThat(heartBeatInfoStorage.shouldSendGlobalHeartBeat(1)).isFalse();
   }
 
   @Test
   public void currentDayHeartbeatNotSent_updatesCorrectly() {
     long millis = System.currentTimeMillis();
+    Preferences.Key<Set<String>> testAgent = PreferencesKeys.stringSetKey("test-agent");
+    Preferences.Key<Set<String>> testAgent1 = PreferencesKeys.stringSetKey("test-agent-1");
     assertThat(heartBeatInfoStorage.getHeartBeatCount()).isEqualTo(0);
     heartBeatInfoStorage.storeHeartBeat(millis, "test-agent");
     assertThat(heartBeatInfoStorage.getHeartBeatCount()).isEqualTo(1);
     assertThat(heartBeatInfoStorage.getAllHeartBeats().size()).isEqualTo(0);
     heartBeatInfoStorage.deleteAllHeartBeats();
     assertThat(heartBeatInfoStorage.getHeartBeatCount()).isEqualTo(1);
-    assertThat(heartBeatSharedPreferences.getStringSet("test-agent", new HashSet<>())).isNotEmpty();
+    assertThat(heartBeatDataStore.getSync(testAgent, new HashSet<>())).isNotEmpty();
     heartBeatInfoStorage.storeHeartBeat(millis, "test-agent-1");
-    assertThat(heartBeatSharedPreferences.getStringSet("test-agent", new HashSet<>())).isEmpty();
-    assertThat(heartBeatSharedPreferences.getStringSet("test-agent-1", new HashSet<>()))
-        .isNotEmpty();
+    assertThat(heartBeatDataStore.getSync(testAgent, new HashSet<>())).isEmpty();
+    assertThat(heartBeatDataStore.getSync(testAgent1, new HashSet<>())).isNotEmpty();
   }
 
   @Test
@@ -222,8 +233,8 @@ public void isSameDate_returnsCorrectly() {
   public void shouldSendGlobalHeartBeat_answerIsYes() {
     long currentTime = System.currentTimeMillis();
     assertThat(heartBeatInfoStorage.shouldSendGlobalHeartBeat(1)).isTrue();
-    assertThat(heartBeatSharedPreferences.getLong(GLOBAL, -1)).isEqualTo(1);
+    assertThat(heartBeatDataStore.getSync(GLOBAL, -1L)).isEqualTo(1);
     assertThat(heartBeatInfoStorage.shouldSendGlobalHeartBeat(currentTime)).isTrue();
-    assertThat(heartBeatSharedPreferences.getLong(GLOBAL, -1)).isEqualTo(currentTime);
+    assertThat(heartBeatDataStore.getSync(GLOBAL, -1L)).isEqualTo(currentTime);
   }
 }

From 77be82fefca8193305cbe358e31c93fc0305c389 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Mon, 24 Mar 2025 14:49:28 -0500
Subject: [PATCH 03/20] Update DataStore.kt

---
 .../com/google/firebase/datastore/DataStore.kt   | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
index 0828e5c0519..97afadcec6b 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.google.firebase.datastore
 
 import android.content.Context

From 638eb670cfdb79ae5e900f4549f062dde71f2860 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Tue, 25 Mar 2025 12:54:30 -0500
Subject: [PATCH 04/20] Update HeartBeatInfoStorage.java

---
 .../google/firebase/heartbeatinfo/HeartBeatInfoStorage.java    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
index c2f04ccf0a0..0d59f0103c4 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
@@ -116,7 +116,8 @@ synchronized List<HeartBeatResult> getAllHeartBeats() {
         Set<String> dates = new HashSet<>((Set<String>) entry.getValue());
         dates.remove(today);
         if (!dates.isEmpty()) {
-          heartBeatResults.add(HeartBeatResult.create(entry.getKey().getName(), new ArrayList<>()));
+          heartBeatResults.add(
+              HeartBeatResult.create(entry.getKey().getName(), new ArrayList<>(dates)));
         }
       }
     }

From 697ed13439cb531aa248126cfe54a4d7a729f88b Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Tue, 25 Mar 2025 13:41:39 -0500
Subject: [PATCH 05/20] Update DataStore.kt

---
 .../google/firebase/datastore/DataStore.kt    | 163 +++++++++++++++++-
 1 file changed, 154 insertions(+), 9 deletions(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
index 97afadcec6b..a4ee0431129 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -27,11 +27,28 @@ import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.runBlocking
 
 /**
- * Don't use this unless you're bridging Java code Use your own [dataStore] directly in Kotlin code,
- * or if you're writing new code.
+ * Wrapper around [DataStore] for easier migration from `SharedPreferences` in Java code.
+ *
+ * Automatically migrates data from any `SharedPreferences` that share the same context and name.
+ *
+ * There should only ever be _one_ instance of this class per context and name variant.
+ *
+ * > Do **NOT** use this _unless_ you're bridging Java code. If you're writing new code, or your >
+ * code is in Kotlin, then you should create your own singleton that uses [DataStore] directly.
+ *
+ * Example:
+ * ```java
+ * DataStorage heartBeatStorage = new DataStorage(applicationContext, "FirebaseHeartBeat");
+ * ```
+ *
+ * @property context The [Context] that this data will be saved under.
+ * @property name What the storage file should be named.
  */
 class DataStorage(val context: Context, val name: String) {
-  private val transforming = ThreadLocal<Boolean>()
+  /**
+   * Used to ensure that there's only ever one call to [editSync] per thread; as to avoid deadlocks.
+   */
+  private val editLock = ThreadLocal<Boolean>()
 
   private val Context.dataStore: DataStore<Preferences> by
     preferencesDataStore(
@@ -41,26 +58,134 @@ class DataStorage(val context: Context, val name: String) {
 
   private val dataStore = context.dataStore
 
+  /**
+   * Get data from the datastore _synchronously_.
+   *
+   * Note that if the key is _not_ in the datastore, while the [defaultValue] will be returned
+   * instead- it will **not** be saved to the datastore; you'll have to manually do that.
+   *
+   * Blocks on the currently running thread.
+   *
+   * Example:
+   * ```java
+   * Preferences.Key<Long> fireCountKey = PreferencesKeys.longKey("fire-count");
+   * assert dataStore.get(fireCountKey, 0L) == 0L;
+   *
+   * dataStore.putSync(fireCountKey, 102L);
+   * assert dataStore.get(fireCountKey, 0L) == 102L;
+   * ```
+   *
+   * @param key The typed key of the entry to get data for.
+   * @param defaultValue A value to default to, if the key isn't found.
+   *
+   * @see Preferences.getOrDefault
+   */
   fun <T> getSync(key: Preferences.Key<T>, defaultValue: T): T = runBlocking {
     dataStore.data.firstOrNull()?.get(key) ?: defaultValue
   }
 
+  /**
+   * Checks if a key is present in the datastore _synchronously_.
+   *
+   * Blocks on the currently running thread.
+   *
+   * Example:
+   * ```java
+   * Preferences.Key<Long> fireCountKey = PreferencesKeys.longKey("fire-count");
+   * assert !dataStore.contains(fireCountKey);
+   *
+   * dataStore.putSync(fireCountKey, 102L);
+   * assert dataStore.contains(fireCountKey);
+   * ```
+   *
+   * @param key The typed key of the entry to find.
+   */
   fun <T> contains(key: Preferences.Key<T>): Boolean = runBlocking {
     dataStore.data.firstOrNull()?.contains(key) ?: false
   }
 
+  /**
+   * Sets and saves data in the datastore _synchronously_.
+   *
+   * Existing values will be overwritten.
+   *
+   * Blocks on the currently running thread.
+   *
+   * Example:
+   * ```java
+   * dataStore.putSync(PreferencesKeys.longKey("fire-count"), 102L);
+   * ```
+   *
+   * @param key The typed key of the entry to save the data under.
+   * @param value The data to save.
+   *
+   * @return The [Preferences] object that the data was saved under.
+   */
   fun <T> putSync(key: Preferences.Key<T>, value: T): Preferences = runBlocking {
     dataStore.edit { it[key] = value }
   }
 
-  /** Do not modify the returned map (should be obvious, since it's immutable though) */
+  /**
+   * Gets all data in the datastore _synchronously_.
+   *
+   * Blocks on the currently running thread.
+   *
+   * Example:
+   * ```java
+   * ArrayList<String> allDates = new ArrayList<>();
+   *
+   * for (Map.Entry<Preferences.Key<?>, Object> entry : dataStore.getAllSync().entrySet()) {
+   *   if (entry.getValue() instanceof Set) {
+   *     Set<String> dates = new HashSet<>((Set<String>) entry.getValue());
+   *     if (!dates.isEmpty()) {
+   *       allDates.add(new ArrayList<>(dates));
+   *     }
+   *   }
+   * }
+   * ```
+   *
+   * @return An _immutable_ map of data currently present in the datastore.
+   */
   fun getAllSync(): Map<Preferences.Key<*>, Any> = runBlocking {
     dataStore.data.firstOrNull()?.asMap() ?: emptyMap()
   }
 
-  /** Edit calls should not be called within edit calls. Is there a way to prevent this? */
+  /**
+   * Transactionally edit data in the datastore _synchronously_.
+   *
+   * Edits made within the [transform] callback will be saved (committed) all at once once the
+   * [transform] block exits.
+   *
+   * Because of the blocking nature of this function, you should _never_ call [editSync] within an
+   * already running [transform] block. Since this can cause a deadlock, [editSync] will instead
+   * throw an exception if it's caught.
+   *
+   * Blocks on the currently running thread.
+   *
+   * Example:
+   * ```java
+   * dataStore.editSync((pref) -> {
+   *   Long heartBeatCount = pref.get(HEART_BEAT_COUNT_TAG);
+   *   if (heartBeatCount == null || heartBeatCount > 30) {
+   *     heartBeatCount = 0L;
+   *   }
+   *   pref.set(HEART_BEAT_COUNT_TAG, heartBeatCount);
+   *   pref.set(LAST_STORED_DATE, "1970-0-1");
+   *
+   *   return null;
+   * });
+   * ```
+   *
+   * @param transform A callback to invoke with the [MutablePreferences] object.
+   *
+   * @return The [Preferences] object that the data was saved under.
+   * @throws IllegalStateException If you attempt to call [editSync] within another [transform]
+   * block.
+   *
+   * @see Preferences.getOrDefault
+   */
   fun editSync(transform: (MutablePreferences) -> Unit): Preferences = runBlocking {
-    if (transforming.get() == true) {
+    if (editLock.get() == true) {
       throw IllegalStateException(
         """
         Don't call DataStorage.edit() from within an existing edit() callback.
@@ -70,15 +195,35 @@ class DataStorage(val context: Context, val name: String) {
           .trimIndent()
       )
     }
-    transforming.set(true)
+    editLock.set(true)
     try {
       dataStore.edit { transform(it) }
     } finally {
-      transforming.set(false)
+      editLock.set(false)
     }
   }
 }
 
-/** Helper for Java code */
+/**
+ * Helper method for getting the value out of a [Preferences] object if it exists, else falling back
+ * to the default value.
+ *
+ * This is primarily useful when working with an instance of [MutablePreferences]
+ * - like when working within an [DataStorage.editSync] callback.
+ *
+ * Example:
+ * ```java
+ * dataStore.editSync((pref) -> {
+ *  long heartBeatCount = DataStoreKt.getOrDefault(pref, HEART_BEAT_COUNT_TAG, 0L);
+ *  heartBeatCount+=1;
+ *  pref.set(HEART_BEAT_COUNT_TAG, heartBeatCount);
+ *
+ *  return null;
+ * });
+ * ```
+ *
+ * @param key The typed key of the entry to get data for.
+ * @param defaultValue A value to default to, if the key isn't found.
+ */
 fun <T> Preferences.getOrDefault(key: Preferences.Key<T>, defaultValue: T) =
   get(key) ?: defaultValue

From a00351d7a12b367a71f9fe7bfe6c12f38a2a0ef0 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Tue, 25 Mar 2025 15:11:47 -0500
Subject: [PATCH 06/20] Bump components to see if it fixes the crash

---
 firebase-common/firebase-common.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts
index c0c379a6ace..e337e084027 100644
--- a/firebase-common/firebase-common.gradle.kts
+++ b/firebase-common/firebase-common.gradle.kts
@@ -55,7 +55,7 @@ android {
 dependencies {
   api(libs.kotlin.coroutines.tasks)
 
-  api("com.google.firebase:firebase-components:18.0.0")
+  api("com.google.firebase:firebase-components:18.0.1")
   api("com.google.firebase:firebase-annotations:16.2.0")
   implementation("androidx.datastore:datastore-preferences:1.1.3")
   implementation(libs.androidx.annotation)

From 1c756ee587ea4f40c62c444367c5a7bf4ad5b5b4 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Tue, 25 Mar 2025 15:59:21 -0500
Subject: [PATCH 07/20] Add logging to try to catch error.

---
 .../main/java/com/google/firebase/datastore/DataStore.kt  | 8 ++++++++
 .../firebase/heartbeatinfo/HeartBeatInfoStorage.java      | 7 +++++++
 2 files changed, 15 insertions(+)

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
index a4ee0431129..2cdf4c0b208 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -17,6 +17,7 @@
 package com.google.firebase.datastore
 
 import android.content.Context
+import android.util.Log
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.SharedPreferencesMigration
 import androidx.datastore.preferences.core.MutablePreferences
@@ -56,6 +57,13 @@ class DataStorage(val context: Context, val name: String) {
       produceMigrations = { listOf(SharedPreferencesMigration(it, name)) }
     )
 
+  init {
+    Log.i(
+      "DataStorage",
+      "Datastore instance created for context $context (${context.packageName}) with name $name"
+    )
+  }
+
   private val dataStore = context.dataStore
 
   /**
diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
index 0d59f0103c4..7387a617386 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
@@ -16,6 +16,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.util.Log;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.datastore.preferences.core.*;
@@ -61,6 +62,11 @@ class HeartBeatInfoStorage {
   private final DataStorage firebaseDataStore;
 
   public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
+    Log.i(
+        "DataStorage",
+        String.format(
+            "Creating a DataStorage instance for context %s (%s) with name %s",
+            applicationContext, applicationContext.getPackageName(), persistenceKey));
     this.firebaseDataStore =
         new DataStorage(applicationContext, HEARTBEAT_PREFERENCES_NAME + persistenceKey);
   }
@@ -68,6 +74,7 @@ public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
   @VisibleForTesting
   @RestrictTo(RestrictTo.Scope.TESTS)
   HeartBeatInfoStorage(DataStorage dataStorage) {
+    Log.i("DataStorage", "New test storage created");
     this.firebaseDataStore = dataStorage;
   }
 

From 1b53a942e1a532d9f085452783d5c3cab33f336d Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Wed, 26 Mar 2025 14:17:45 -0500
Subject: [PATCH 08/20] Add additional logging

---
 .../DefaultHeartBeatController.java           | 23 +++++++++++++------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
index ff9ba5123fc..b11fb09b65f 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
@@ -17,6 +17,7 @@
 import android.content.Context;
 import android.util.Base64;
 import android.util.Base64OutputStream;
+import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.os.UserManagerCompat;
@@ -130,6 +131,11 @@ private DefaultHeartBeatController(
       Executor executor,
       Provider<UserAgentPublisher> userAgentProvider,
       Context context) {
+    Log.i(
+        "DefHeartBeatController",
+        String.format(
+            "[DAYMON] Constructor called with context %s and testStorage %s",
+            context.toString(), testStorage.toString()));
     storageProvider = testStorage;
     this.consumers = consumers;
     this.backgroundExecutor = executor;
@@ -139,6 +145,7 @@ private DefaultHeartBeatController(
 
   public static @NonNull Component<DefaultHeartBeatController> component() {
     Qualified<Executor> backgroundExecutor = Qualified.qualified(Background.class, Executor.class);
+    Log.i("DefHeartBeatController", "[DAYMON] component() called");
     return Component.builder(
             DefaultHeartBeatController.class, HeartBeatController.class, HeartBeatInfo.class)
         .add(Dependency.required(Context.class))
@@ -147,13 +154,15 @@ private DefaultHeartBeatController(
         .add(Dependency.requiredProvider(UserAgentPublisher.class))
         .add(Dependency.required(backgroundExecutor))
         .factory(
-            c ->
-                new DefaultHeartBeatController(
-                    c.get(Context.class),
-                    c.get(FirebaseApp.class).getPersistenceKey(),
-                    c.setOf(HeartBeatConsumer.class),
-                    c.getProvider(UserAgentPublisher.class),
-                    c.get(backgroundExecutor)))
+            c -> {
+              Log.i("DefHeartBeatController", "[DAYMON] Factory called");
+              return new DefaultHeartBeatController(
+                  c.get(Context.class),
+                  c.get(FirebaseApp.class).getPersistenceKey(),
+                  c.setOf(HeartBeatConsumer.class),
+                  c.getProvider(UserAgentPublisher.class),
+                  c.get(backgroundExecutor));
+            })
         .build();
   }
 

From 8459026b58f7f2f47312480619994c72c5929c6d Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Wed, 26 Mar 2025 14:17:54 -0500
Subject: [PATCH 09/20] Make provider lazy

---
 .../firebase/heartbeatinfo/DefaultHeartBeatController.java     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
index b11fb09b65f..ce5cae3f975 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
@@ -27,6 +27,7 @@
 import com.google.firebase.annotations.concurrent.Background;
 import com.google.firebase.components.Component;
 import com.google.firebase.components.Dependency;
+import com.google.firebase.components.Lazy;
 import com.google.firebase.components.Qualified;
 import com.google.firebase.inject.Provider;
 import com.google.firebase.platforminfo.UserAgentPublisher;
@@ -117,7 +118,7 @@ private DefaultHeartBeatController(
       Provider<UserAgentPublisher> userAgentProvider,
       Executor backgroundExecutor) {
     this(
-        () -> new HeartBeatInfoStorage(context, persistenceKey),
+        new Lazy<>(() -> new HeartBeatInfoStorage(context, persistenceKey)),
         consumers,
         backgroundExecutor,
         userAgentProvider,

From 09ac9c30d1fbb58ff9bec18ba53ac09d68b32b9a Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 27 Mar 2025 16:26:13 -0500
Subject: [PATCH 10/20] Update compile/target sdk versions in health metrics

---
 health-metrics/benchmark/template/app/build.gradle.mustache   | 4 ++--
 health-metrics/benchmark/template/macrobenchmark/build.gradle | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/health-metrics/benchmark/template/app/build.gradle.mustache b/health-metrics/benchmark/template/app/build.gradle.mustache
index 77279ae25d9..a8bf11948eb 100644
--- a/health-metrics/benchmark/template/app/build.gradle.mustache
+++ b/health-metrics/benchmark/template/app/build.gradle.mustache
@@ -21,14 +21,14 @@ plugins {
 }
 
 android {
-    compileSdkVersion 32
+    compileSdkVersion 34
 
     namespace "com.google.firebase.benchmark"
 
     defaultConfig {
         applicationId 'com.google.firebase.benchmark'
         minSdkVersion 29
-        targetSdkVersion 32
+        targetSdkVersion 34
         versionCode 1
         versionName '1.0'
 
diff --git a/health-metrics/benchmark/template/macrobenchmark/build.gradle b/health-metrics/benchmark/template/macrobenchmark/build.gradle
index 8b4556eee76..086de25e4a2 100644
--- a/health-metrics/benchmark/template/macrobenchmark/build.gradle
+++ b/health-metrics/benchmark/template/macrobenchmark/build.gradle
@@ -33,7 +33,7 @@ android {
 
     defaultConfig {
         minSdk 29
-        targetSdk 32
+        targetSdk 34
 
         testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
     }

From 08aab02a87e7a88b869af299f5cb620ed3124ace Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 27 Mar 2025 16:26:30 -0500
Subject: [PATCH 11/20] Use version toml

---
 firebase-common/firebase-common.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts
index e337e084027..fc9ebdf00c3 100644
--- a/firebase-common/firebase-common.gradle.kts
+++ b/firebase-common/firebase-common.gradle.kts
@@ -57,7 +57,7 @@ dependencies {
 
   api("com.google.firebase:firebase-components:18.0.1")
   api("com.google.firebase:firebase-annotations:16.2.0")
-  implementation("androidx.datastore:datastore-preferences:1.1.3")
+  implementation(libs.androidx.datastore.preferences)
   implementation(libs.androidx.annotation)
   implementation(libs.androidx.futures)
   implementation(libs.kotlin.stdlib)

From 4dda28bb60d4497493cb277c5d3c5f9db5b31927 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 27 Mar 2025 16:26:36 -0500
Subject: [PATCH 12/20] Update DataStore.kt

---
 .../src/main/java/com/google/firebase/datastore/DataStore.kt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
index 2cdf4c0b208..df230774203 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -34,7 +34,7 @@ import kotlinx.coroutines.runBlocking
  *
  * There should only ever be _one_ instance of this class per context and name variant.
  *
- * > Do **NOT** use this _unless_ you're bridging Java code. If you're writing new code, or your >
+ * > Do **NOT** use this _unless_ you're bridging Java code. If you're writing new code, or your
  * code is in Kotlin, then you should create your own singleton that uses [DataStore] directly.
  *
  * Example:

From 5c004c5953c3975845873976416cb3c25cb52ed5 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 27 Mar 2025 16:31:44 -0500
Subject: [PATCH 13/20] Remove logging

---
 .../google/firebase/datastore/DataStore.kt    |  8 -------
 .../DefaultHeartBeatController.java           | 23 ++++++-------------
 .../heartbeatinfo/HeartBeatInfoStorage.java   |  7 ------
 3 files changed, 7 insertions(+), 31 deletions(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
index df230774203..831700f75a4 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -17,7 +17,6 @@
 package com.google.firebase.datastore
 
 import android.content.Context
-import android.util.Log
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.SharedPreferencesMigration
 import androidx.datastore.preferences.core.MutablePreferences
@@ -57,13 +56,6 @@ class DataStorage(val context: Context, val name: String) {
       produceMigrations = { listOf(SharedPreferencesMigration(it, name)) }
     )
 
-  init {
-    Log.i(
-      "DataStorage",
-      "Datastore instance created for context $context (${context.packageName}) with name $name"
-    )
-  }
-
   private val dataStore = context.dataStore
 
   /**
diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
index ce5cae3f975..7aa8cfb613d 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
@@ -17,7 +17,6 @@
 import android.content.Context;
 import android.util.Base64;
 import android.util.Base64OutputStream;
-import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.os.UserManagerCompat;
@@ -132,11 +131,6 @@ private DefaultHeartBeatController(
       Executor executor,
       Provider<UserAgentPublisher> userAgentProvider,
       Context context) {
-    Log.i(
-        "DefHeartBeatController",
-        String.format(
-            "[DAYMON] Constructor called with context %s and testStorage %s",
-            context.toString(), testStorage.toString()));
     storageProvider = testStorage;
     this.consumers = consumers;
     this.backgroundExecutor = executor;
@@ -146,7 +140,6 @@ private DefaultHeartBeatController(
 
   public static @NonNull Component<DefaultHeartBeatController> component() {
     Qualified<Executor> backgroundExecutor = Qualified.qualified(Background.class, Executor.class);
-    Log.i("DefHeartBeatController", "[DAYMON] component() called");
     return Component.builder(
             DefaultHeartBeatController.class, HeartBeatController.class, HeartBeatInfo.class)
         .add(Dependency.required(Context.class))
@@ -155,15 +148,13 @@ private DefaultHeartBeatController(
         .add(Dependency.requiredProvider(UserAgentPublisher.class))
         .add(Dependency.required(backgroundExecutor))
         .factory(
-            c -> {
-              Log.i("DefHeartBeatController", "[DAYMON] Factory called");
-              return new DefaultHeartBeatController(
-                  c.get(Context.class),
-                  c.get(FirebaseApp.class).getPersistenceKey(),
-                  c.setOf(HeartBeatConsumer.class),
-                  c.getProvider(UserAgentPublisher.class),
-                  c.get(backgroundExecutor));
-            })
+            c ->
+                new DefaultHeartBeatController(
+                    c.get(Context.class),
+                    c.get(FirebaseApp.class).getPersistenceKey(),
+                    c.setOf(HeartBeatConsumer.class),
+                    c.getProvider(UserAgentPublisher.class),
+                    c.get(backgroundExecutor)))
         .build();
   }
 
diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
index 7387a617386..0d59f0103c4 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
@@ -16,7 +16,6 @@
 
 import android.content.Context;
 import android.os.Build;
-import android.util.Log;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.datastore.preferences.core.*;
@@ -62,11 +61,6 @@ class HeartBeatInfoStorage {
   private final DataStorage firebaseDataStore;
 
   public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
-    Log.i(
-        "DataStorage",
-        String.format(
-            "Creating a DataStorage instance for context %s (%s) with name %s",
-            applicationContext, applicationContext.getPackageName(), persistenceKey));
     this.firebaseDataStore =
         new DataStorage(applicationContext, HEARTBEAT_PREFERENCES_NAME + persistenceKey);
   }
@@ -74,7 +68,6 @@ public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
   @VisibleForTesting
   @RestrictTo(RestrictTo.Scope.TESTS)
   HeartBeatInfoStorage(DataStorage dataStorage) {
-    Log.i("DataStorage", "New test storage created");
     this.firebaseDataStore = dataStorage;
   }
 

From c7e34a9ef3da1f94473e1d3ebfbed18c253df2cd Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 10 Apr 2025 14:30:02 -0500
Subject: [PATCH 14/20] Update firebase-common.gradle.kts

---
 firebase-common/firebase-common.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts
index fc9ebdf00c3..52114f82558 100644
--- a/firebase-common/firebase-common.gradle.kts
+++ b/firebase-common/firebase-common.gradle.kts
@@ -55,7 +55,7 @@ android {
 dependencies {
   api(libs.kotlin.coroutines.tasks)
 
-  api("com.google.firebase:firebase-components:18.0.1")
+  api("com.google.firebase:firebase-components:18.0.0")
   api("com.google.firebase:firebase-annotations:16.2.0")
   implementation(libs.androidx.datastore.preferences)
   implementation(libs.androidx.annotation)

From 607c4171f59a54407bef77fa1c96c47d34bfa621 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 10 Apr 2025 15:59:08 -0500
Subject: [PATCH 15/20] Exclude datastore proto deps

---
 firebase-common/firebase-common.gradle.kts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts
index 52114f82558..64c64e3b1a6 100644
--- a/firebase-common/firebase-common.gradle.kts
+++ b/firebase-common/firebase-common.gradle.kts
@@ -57,7 +57,10 @@ dependencies {
 
   api("com.google.firebase:firebase-components:18.0.0")
   api("com.google.firebase:firebase-annotations:16.2.0")
-  implementation(libs.androidx.datastore.preferences)
+  implementation(libs.androidx.datastore.preferences)  {
+    exclude("androidx.datastore", "datastore-preferences-proto")
+    exclude("androidx.datastore", "datastore-preferences-external-protobuf")
+  }
   implementation(libs.androidx.annotation)
   implementation(libs.androidx.futures)
   implementation(libs.kotlin.stdlib)

From 42824aa95718b37fac4b9ce01c3d6d37129826e4 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Thu, 10 Apr 2025 16:01:13 -0500
Subject: [PATCH 16/20] Revert "Exclude datastore proto deps"

This reverts commit 607c4171f59a54407bef77fa1c96c47d34bfa621.
---
 firebase-common/firebase-common.gradle.kts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/firebase-common/firebase-common.gradle.kts b/firebase-common/firebase-common.gradle.kts
index 64c64e3b1a6..52114f82558 100644
--- a/firebase-common/firebase-common.gradle.kts
+++ b/firebase-common/firebase-common.gradle.kts
@@ -57,10 +57,7 @@ dependencies {
 
   api("com.google.firebase:firebase-components:18.0.0")
   api("com.google.firebase:firebase-annotations:16.2.0")
-  implementation(libs.androidx.datastore.preferences)  {
-    exclude("androidx.datastore", "datastore-preferences-proto")
-    exclude("androidx.datastore", "datastore-preferences-external-protobuf")
-  }
+  implementation(libs.androidx.datastore.preferences)
   implementation(libs.androidx.annotation)
   implementation(libs.androidx.futures)
   implementation(libs.kotlin.stdlib)

From 9e143b5d969c586300f089455c8f03bb36329651 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Mon, 9 Jun 2025 10:33:09 -0500
Subject: [PATCH 17/20] Hide datastore from public api/add note on thread

---
 .../com/google/firebase/datastore/DataStore.kt   |  7 +++++++
 .../google/firebase/datastore/package-info.java  | 16 ++++++++++++++++
 2 files changed, 23 insertions(+)
 create mode 100644 firebase-common/src/main/java/com/google/firebase/datastore/package-info.java

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
index 831700f75a4..0191a53772d 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
@@ -23,6 +23,7 @@ import androidx.datastore.preferences.core.MutablePreferences
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.preferencesDataStore
+import com.google.firebase.annotations.concurrent.Background
 import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.runBlocking
 
@@ -33,6 +34,10 @@ import kotlinx.coroutines.runBlocking
  *
  * There should only ever be _one_ instance of this class per context and name variant.
  *
+ * Note that most of the methods in this class **block** on the _current_ thread, as to help keep
+ * parity with existing Java code. Typically, you'd want to dispatch this work to another thread
+ * like [@Background][Background].
+ *
  * > Do **NOT** use this _unless_ you're bridging Java code. If you're writing new code, or your
  * code is in Kotlin, then you should create your own singleton that uses [DataStore] directly.
  *
@@ -43,6 +48,8 @@ import kotlinx.coroutines.runBlocking
  *
  * @property context The [Context] that this data will be saved under.
  * @property name What the storage file should be named.
+ *
+ * @hide
  */
 class DataStorage(val context: Context, val name: String) {
   /**
diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/package-info.java b/firebase-common/src/main/java/com/google/firebase/datastore/package-info.java
new file mode 100644
index 00000000000..d6ddf8dd525
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/datastore/package-info.java
@@ -0,0 +1,16 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/** @hide */
+package com.google.firebase.datastore;

From 9ce5766097df3997f2649695de409fe95ed449bb Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Mon, 9 Jun 2025 10:46:18 -0500
Subject: [PATCH 18/20] Update CHANGELOG.md

---
 firebase-common/CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/firebase-common/CHANGELOG.md b/firebase-common/CHANGELOG.md
index 8015035242a..944fd445e17 100644
--- a/firebase-common/CHANGELOG.md
+++ b/firebase-common/CHANGELOG.md
@@ -1,6 +1,7 @@
 # Unreleased
 * [fixed] Correctly declare dependency on firebase-components, issue #5732
 * [changed] Added extension method `Random.nextAlphanumericString()` (PR #5818)
+* [changed] Migrated internal `SharedPreferences` usages to `DataStore`. ([GitHub PR #6801](https://github.com/firebase/firebase-android-sdk/pull/6801){ .external})
 
 # 20.4.0
 * [changed] Added Kotlin extensions (KTX) APIs from `com.google.firebase:firebase-common-ktx`

From ad2932c832a7d9f7e2c1c55d9e961b0601816574 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Tue, 17 Jun 2025 12:09:43 -0500
Subject: [PATCH 19/20] Rename DataStore to JavaDataStore

---
 .../JavaDataStorage.kt}                       | 10 ++++----
 .../package-info.java                         |  2 +-
 .../heartbeatinfo/HeartBeatInfoStorage.java   | 24 +++++++++----------
 .../DefaultHeartBeatControllerTest.java       |  6 ++---
 .../HeartBeatInfoStorageTest.java             |  6 ++---
 5 files changed, 24 insertions(+), 24 deletions(-)
 rename firebase-common/src/main/java/com/google/firebase/{datastore/DataStore.kt => datastorage/JavaDataStorage.kt} (95%)
 rename firebase-common/src/main/java/com/google/firebase/{datastore => datastorage}/package-info.java (93%)

diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt b/firebase-common/src/main/java/com/google/firebase/datastorage/JavaDataStorage.kt
similarity index 95%
rename from firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
rename to firebase-common/src/main/java/com/google/firebase/datastorage/JavaDataStorage.kt
index 0191a53772d..9b16720210e 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/DataStore.kt
+++ b/firebase-common/src/main/java/com/google/firebase/datastorage/JavaDataStorage.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.firebase.datastore
+package com.google.firebase.datastorage
 
 import android.content.Context
 import androidx.datastore.core.DataStore
@@ -43,7 +43,7 @@ import kotlinx.coroutines.runBlocking
  *
  * Example:
  * ```java
- * DataStorage heartBeatStorage = new DataStorage(applicationContext, "FirebaseHeartBeat");
+ * JavaDataStorage heartBeatStorage = new JavaDataStorage(applicationContext, "FirebaseHeartBeat");
  * ```
  *
  * @property context The [Context] that this data will be saved under.
@@ -51,7 +51,7 @@ import kotlinx.coroutines.runBlocking
  *
  * @hide
  */
-class DataStorage(val context: Context, val name: String) {
+class JavaDataStorage(val context: Context, val name: String) {
   /**
    * Used to ensure that there's only ever one call to [editSync] per thread; as to avoid deadlocks.
    */
@@ -195,7 +195,7 @@ class DataStorage(val context: Context, val name: String) {
     if (editLock.get() == true) {
       throw IllegalStateException(
         """
-        Don't call DataStorage.edit() from within an existing edit() callback.
+        Don't call JavaDataStorage.edit() from within an existing edit() callback.
         This causes deadlocks, and is generally indicative of a code smell.
         Instead, either pass around the initial `MutablePreferences` instance, or don't do everything in a single callback. 
       """
@@ -216,7 +216,7 @@ class DataStorage(val context: Context, val name: String) {
  * to the default value.
  *
  * This is primarily useful when working with an instance of [MutablePreferences]
- * - like when working within an [DataStorage.editSync] callback.
+ * - like when working within an [JavaDataStorage.editSync] callback.
  *
  * Example:
  * ```java
diff --git a/firebase-common/src/main/java/com/google/firebase/datastore/package-info.java b/firebase-common/src/main/java/com/google/firebase/datastorage/package-info.java
similarity index 93%
rename from firebase-common/src/main/java/com/google/firebase/datastore/package-info.java
rename to firebase-common/src/main/java/com/google/firebase/datastorage/package-info.java
index d6ddf8dd525..44489b704ff 100644
--- a/firebase-common/src/main/java/com/google/firebase/datastore/package-info.java
+++ b/firebase-common/src/main/java/com/google/firebase/datastorage/package-info.java
@@ -13,4 +13,4 @@
 // limitations under the License.
 
 /** @hide */
-package com.google.firebase.datastore;
+package com.google.firebase.datastorage;
diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
index 0d59f0103c4..8f64ce5dec0 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
@@ -20,8 +20,8 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.datastore.preferences.core.*;
 import androidx.datastore.preferences.core.Preferences;
-import com.google.firebase.datastore.DataStorage;
-import com.google.firebase.datastore.DataStoreKt;
+import com.google.firebase.datastorage.JavaDataStorage;
+import com.google.firebase.datastorage.JavaDataStorageKt;
 import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -58,17 +58,17 @@ class HeartBeatInfoStorage {
   // As soon as you hit the limit of heartbeats. The number of stored heartbeats is halved.
   private static final int HEART_BEAT_COUNT_LIMIT = 30;
 
-  private final DataStorage firebaseDataStore;
+  private final JavaDataStorage firebaseDataStore;
 
   public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
     this.firebaseDataStore =
-        new DataStorage(applicationContext, HEARTBEAT_PREFERENCES_NAME + persistenceKey);
+        new JavaDataStorage(applicationContext, HEARTBEAT_PREFERENCES_NAME + persistenceKey);
   }
 
   @VisibleForTesting
   @RestrictTo(RestrictTo.Scope.TESTS)
-  HeartBeatInfoStorage(DataStorage dataStorage) {
-    this.firebaseDataStore = dataStorage;
+  HeartBeatInfoStorage(JavaDataStorage javaDataStorage) {
+    this.firebaseDataStore = javaDataStorage;
   }
 
   @VisibleForTesting
@@ -146,7 +146,7 @@ private synchronized void updateStoredUserAgent(
       MutablePreferences preferences, Preferences.Key<Set<String>> userAgent, String dateString) {
     removeStoredDate(preferences, dateString);
     Set<String> userAgentDateSet =
-        new HashSet<>(DataStoreKt.getOrDefault(preferences, userAgent, new HashSet<>()));
+        new HashSet<>(JavaDataStorageKt.getOrDefault(preferences, userAgent, new HashSet<>()));
     userAgentDateSet.add(dateString);
     preferences.set(userAgent, userAgentDateSet);
   }
@@ -158,7 +158,7 @@ private synchronized void removeStoredDate(MutablePreferences preferences, Strin
       return;
     }
     Set<String> userAgentDateSet =
-        new HashSet<>(DataStoreKt.getOrDefault(preferences, userAgent, new HashSet<>()));
+        new HashSet<>(JavaDataStorageKt.getOrDefault(preferences, userAgent, new HashSet<>()));
     userAgentDateSet.remove(dateString);
     if (userAgentDateSet.isEmpty()) {
       preferences.remove(userAgent);
@@ -193,7 +193,7 @@ synchronized void storeHeartBeat(long millis, String userAgentString) {
     Preferences.Key<Set<String>> userAgent = PreferencesKeys.stringSetKey(userAgentString);
     firebaseDataStore.editSync(
         (pref) -> {
-          String lastDateString = DataStoreKt.getOrDefault(pref, LAST_STORED_DATE, "");
+          String lastDateString = JavaDataStorageKt.getOrDefault(pref, LAST_STORED_DATE, "");
           if (lastDateString.equals(dateString)) {
             Preferences.Key<Set<String>> storedUserAgent =
                 getStoredUserAgentString(pref, dateString);
@@ -208,12 +208,12 @@ synchronized void storeHeartBeat(long millis, String userAgentString) {
               return null;
             }
           }
-          long heartBeatCount = DataStoreKt.getOrDefault(pref, HEART_BEAT_COUNT_TAG, 0L);
+          long heartBeatCount = JavaDataStorageKt.getOrDefault(pref, HEART_BEAT_COUNT_TAG, 0L);
           if (heartBeatCount + 1 == HEART_BEAT_COUNT_LIMIT) {
             heartBeatCount = cleanUpStoredHeartBeats(pref);
           }
           Set<String> userAgentDateSet =
-              new HashSet<>(DataStoreKt.getOrDefault(pref, userAgent, new HashSet<>()));
+              new HashSet<>(JavaDataStorageKt.getOrDefault(pref, userAgent, new HashSet<>()));
           userAgentDateSet.add(dateString);
           heartBeatCount += 1;
 
@@ -226,7 +226,7 @@ synchronized void storeHeartBeat(long millis, String userAgentString) {
   }
 
   private synchronized long cleanUpStoredHeartBeats(MutablePreferences preferences) {
-    long heartBeatCount = DataStoreKt.getOrDefault(preferences, HEART_BEAT_COUNT_TAG, 0L);
+    long heartBeatCount = JavaDataStorageKt.getOrDefault(preferences, HEART_BEAT_COUNT_TAG, 0L);
 
     String lowestDate = null;
     String userAgentString = "";
diff --git a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java
index a57c1768f66..3ffc0795b26 100644
--- a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java
+++ b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatControllerTest.java
@@ -28,7 +28,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import com.google.common.collect.ImmutableSet;
-import com.google.firebase.datastore.DataStorage;
+import com.google.firebase.datastorage.JavaDataStorage;
 import com.google.firebase.platforminfo.UserAgentPublisher;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -107,7 +107,7 @@ public void generateHeartBeat_oneHeartBeat() throws InterruptedException, Timeou
   public void firstNewThenOld_synchronizedCorrectly()
       throws InterruptedException, TimeoutException {
     Context context = ApplicationProvider.getApplicationContext();
-    DataStorage heartBeatDataStore = new DataStorage(context, "testHeartBeat");
+    JavaDataStorage heartBeatDataStore = new JavaDataStorage(context, "testHeartBeat");
     HeartBeatInfoStorage heartBeatInfoStorage = new HeartBeatInfoStorage(heartBeatDataStore);
     DefaultHeartBeatController controller =
         new DefaultHeartBeatController(
@@ -128,7 +128,7 @@ public void firstNewThenOld_synchronizedCorrectly()
   public void firstOldThenNew_synchronizedCorrectly()
       throws InterruptedException, TimeoutException {
     Context context = ApplicationProvider.getApplicationContext();
-    DataStorage heartBeatDataStore = new DataStorage(context, "testHeartBeat");
+    JavaDataStorage heartBeatDataStore = new JavaDataStorage(context, "testHeartBeat");
     HeartBeatInfoStorage heartBeatInfoStorage = new HeartBeatInfoStorage(heartBeatDataStore);
     DefaultHeartBeatController controller =
         new DefaultHeartBeatController(
diff --git a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java
index b83ba4d3523..a9605c103df 100644
--- a/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java
+++ b/firebase-common/src/test/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorageTest.java
@@ -21,7 +21,7 @@
 import androidx.datastore.preferences.core.PreferencesKeys;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
-import com.google.firebase.datastore.DataStorage;
+import com.google.firebase.datastorage.JavaDataStorage;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -38,8 +38,8 @@ public class HeartBeatInfoStorageTest {
   private final Preferences.Key<Long> GLOBAL = PreferencesKeys.longKey("fire-global");
   private static final int HEART_BEAT_COUNT_LIMIT = 30;
   private static Context applicationContext = ApplicationProvider.getApplicationContext();
-  private static DataStorage heartBeatDataStore =
-      new DataStorage(applicationContext, "testHeartBeat");
+  private static JavaDataStorage heartBeatDataStore =
+      new JavaDataStorage(applicationContext, "testHeartBeat");
   private HeartBeatInfoStorage heartBeatInfoStorage = new HeartBeatInfoStorage(heartBeatDataStore);
 
   @Before

From 4a7cb4a4ab158381fd63cca418b11979a64ce301 Mon Sep 17 00:00:00 2001
From: Daymon <daymxn@google.com>
Date: Tue, 17 Jun 2025 12:12:50 -0500
Subject: [PATCH 20/20] Remove star import

---
 .../google/firebase/heartbeatinfo/HeartBeatInfoStorage.java    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
index 8f64ce5dec0..d998729c69d 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/HeartBeatInfoStorage.java
@@ -18,8 +18,9 @@
 import android.os.Build;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
-import androidx.datastore.preferences.core.*;
+import androidx.datastore.preferences.core.MutablePreferences;
 import androidx.datastore.preferences.core.Preferences;
+import androidx.datastore.preferences.core.PreferencesKeys;
 import com.google.firebase.datastorage.JavaDataStorage;
 import com.google.firebase.datastorage.JavaDataStorageKt;
 import java.text.SimpleDateFormat;