Skip to content

Synchronize old and new heartbeats #3177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.google.firebase.components.Component;
import com.google.firebase.components.ComponentRegistrar;
import com.google.firebase.heartbeatinfo.DefaultHeartBeatController;
import com.google.firebase.heartbeatinfo.DefaultHeartBeatInfo;
import com.google.firebase.platforminfo.DefaultUserAgentPublisher;
import com.google.firebase.platforminfo.KotlinDetector;
import com.google.firebase.platforminfo.LibraryVersionComponent;
Expand All @@ -44,7 +43,6 @@ public class FirebaseCommonRegistrar implements ComponentRegistrar {
public List<Component<?>> getComponents() {
List<Component<?>> result = new ArrayList<>();
result.add(DefaultUserAgentPublisher.component());
result.add(DefaultHeartBeatInfo.component());
result.add(DefaultHeartBeatController.component());
result.add(
LibraryVersionComponent.create(FIREBASE_ANDROID, String.valueOf(Build.VERSION.SDK_INT)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
import org.json.JSONObject;

/** Provides a function to store heartbeats and another function to retrieve stored heartbeats. */
public class DefaultHeartBeatController implements HeartBeatController {
public class DefaultHeartBeatController implements HeartBeatController, HeartBeatInfo {

private final Provider<HeartBeatInfoStorage> storageProvider;

private Context applicationContext;
private final Context applicationContext;

private final Provider<UserAgentPublisher> userAgentProvider;

Expand Down Expand Up @@ -114,24 +114,27 @@ private DefaultHeartBeatController(
consumers,
new ThreadPoolExecutor(
0, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), THREAD_FACTORY),
userAgentProvider);
this.applicationContext = context;
userAgentProvider,
context);
}

@VisibleForTesting
DefaultHeartBeatController(
Provider<HeartBeatInfoStorage> testStorage,
Set<HeartBeatConsumer> consumers,
Executor executor,
Provider<UserAgentPublisher> userAgentProvider) {
Provider<UserAgentPublisher> userAgentProvider,
Context context) {
storageProvider = testStorage;
this.consumers = consumers;
this.backgroundExecutor = executor;
this.userAgentProvider = userAgentProvider;
this.applicationContext = context;
}

public static @NonNull Component<DefaultHeartBeatController> component() {
return Component.builder(DefaultHeartBeatController.class, HeartBeatController.class)
return Component.builder(
DefaultHeartBeatController.class, HeartBeatController.class, HeartBeatInfo.class)
.add(Dependency.required(Context.class))
.add(Dependency.required(FirebaseApp.class))
.add(Dependency.setOf(HeartBeatConsumer.class))
Expand All @@ -145,4 +148,18 @@ private DefaultHeartBeatController(
c.getProvider(UserAgentPublisher.class)))
.build();
}

@Override
@NonNull
public synchronized HeartBeat getHeartBeatCode(@NonNull String heartBeatTag) {
long presentTime = System.currentTimeMillis();
HeartBeatInfoStorage storage = storageProvider.get();
boolean shouldSendGlobalHB = storage.shouldSendGlobalHeartBeat(presentTime);
if (shouldSendGlobalHB) {
storage.postHeartBeatCleanUp();
return HeartBeat.GLOBAL;
} else {
return HeartBeat.NONE;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,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 static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy z");

private SharedPreferences sharedPreferences;
private SharedPreferences firebaseSharedPreferences;
private final SharedPreferences firebaseSharedPreferences;

public HeartBeatInfoStorage(Context applicationContext, String persistenceKey) {
this.sharedPreferences =
applicationContext.getSharedPreferences(
PREFERENCES_NAME + persistenceKey, Context.MODE_PRIVATE);
this.firebaseSharedPreferences =
applicationContext.getSharedPreferences(
HEARTBEAT_PREFERENCES_NAME + persistenceKey, Context.MODE_PRIVATE);
}

@VisibleForTesting
@RestrictTo(RestrictTo.Scope.TESTS)
HeartBeatInfoStorage(SharedPreferences preferences, SharedPreferences firebaseSharedPreferences) {
this.sharedPreferences = preferences;
HeartBeatInfoStorage(SharedPreferences firebaseSharedPreferences) {
this.firebaseSharedPreferences = firebaseSharedPreferences;
}

Expand All @@ -81,7 +74,14 @@ int getHeartBeatCount() {
}

synchronized void deleteAllHeartBeats() {
firebaseSharedPreferences.edit().clear().apply();
SharedPreferences.Editor editor = firebaseSharedPreferences.edit();
for (Map.Entry<String, ?> entry : this.firebaseSharedPreferences.getAll().entrySet()) {
if (entry.getValue() instanceof Set) {
editor.remove(entry.getKey());
}
}
editor.remove(HEART_BEAT_COUNT_TAG);
editor.commit();
}

synchronized List<HeartBeatResult> getAllHeartBeats() {
Expand All @@ -93,10 +93,48 @@ synchronized List<HeartBeatResult> getAllHeartBeats() {
entry.getKey(), new ArrayList<String>((Set<String>) entry.getValue())));
}
}
updateGlobalHeartBeat(System.currentTimeMillis());
return heartBeatResults;
}

synchronized String getFormattedDate(long millis) {
private synchronized String getStoredUserAgentString(String dateString) {
for (Map.Entry<String, ?> entry : firebaseSharedPreferences.getAll().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 null;
}

private synchronized void removeStoredDate(String dateString) {
// Find stored heartbeat and clear it.
String userAgentString = getStoredUserAgentString(dateString);
if (userAgentString == null) {
return;
}
Set<String> userAgentDateSet =
new HashSet<String>(
firebaseSharedPreferences.getStringSet(userAgentString, new HashSet<String>()));
userAgentDateSet.remove(dateString);
if (userAgentDateSet.isEmpty()) {
firebaseSharedPreferences.edit().remove(userAgentString).commit();
} else {
firebaseSharedPreferences.edit().putStringSet(userAgentString, userAgentDateSet).commit();
}
}

synchronized void postHeartBeatCleanUp() {
String dateString = getFormattedDate(System.currentTimeMillis());
firebaseSharedPreferences.edit().putString(LAST_STORED_DATE, dateString).commit();
removeStoredDate(dateString);
}

private synchronized String getFormattedDate(long millis) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Instant instant = new Date(millis).toInstant();
LocalDateTime ldt = instant.atOffset(ZoneOffset.UTC).toLocalDateTime();
Expand Down Expand Up @@ -127,7 +165,7 @@ synchronized void storeHeartBeat(long millis, String userAgentString) {
.putStringSet(userAgentString, userAgentDateSet)
.putLong(HEART_BEAT_COUNT_TAG, heartBeatCount)
.putString(LAST_STORED_DATE, dateString)
.apply();
.commit();
}

private synchronized void cleanUpStoredHeartBeats() {
Expand All @@ -153,21 +191,19 @@ private synchronized void cleanUpStoredHeartBeats() {
.edit()
.putStringSet(userAgentString, userAgentDateSet)
.putLong(HEART_BEAT_COUNT_TAG, heartBeatCount - 1)
.apply();
.commit();
}

synchronized long getLastGlobalHeartBeat() {
return sharedPreferences.getLong(GLOBAL, -1);
return firebaseSharedPreferences.getLong(GLOBAL, -1);
}

synchronized void updateGlobalHeartBeat(long millis) {
sharedPreferences.edit().putLong(GLOBAL, millis).apply();
firebaseSharedPreferences.edit().putLong(GLOBAL, millis).commit();
}

static boolean isSameDateUtc(long base, long target) {
Date baseDate = new Date(base);
Date targetDate = new Date(target);
return !(FORMATTER.format(baseDate).equals(FORMATTER.format(targetDate)));
synchronized boolean isSameDateUtc(long base, long target) {
return getFormattedDate(base).equals(getFormattedDate(target));
}

/*
Expand All @@ -176,14 +212,14 @@ static boolean isSameDateUtc(long base, long target) {
when the last heartbeat send for the sdk was later than a day before.
*/
synchronized boolean shouldSendSdkHeartBeat(String heartBeatTag, long millis) {
if (sharedPreferences.contains(heartBeatTag)) {
if (isSameDateUtc(sharedPreferences.getLong(heartBeatTag, -1), millis)) {
sharedPreferences.edit().putLong(heartBeatTag, millis).apply();
if (firebaseSharedPreferences.contains(heartBeatTag)) {
if (!this.isSameDateUtc(firebaseSharedPreferences.getLong(heartBeatTag, -1), millis)) {
firebaseSharedPreferences.edit().putLong(heartBeatTag, millis).commit();
return true;
}
return false;
} else {
sharedPreferences.edit().putLong(heartBeatTag, millis).apply();
firebaseSharedPreferences.edit().putLong(heartBeatTag, millis).commit();
return true;
}
}
Expand Down
Loading