Skip to content

Commit 9db5174

Browse files
authored
User id fix (#5432)
Fix Unity and Flutter on-demand user meta data behaviour. Cherry-picked from previous closed pr: #5275
1 parent fef7bad commit 9db5174

File tree

5 files changed

+122
-6
lines changed

5 files changed

+122
-6
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
now deprecated. As early as April 2024, we'll no longer release KTX modules. For details, see the
1111
[FAQ about this initiative](https://firebase.google.com/docs/android/kotlin-migration)
1212

13+
* [fixed] Fixed Flutter and Unity on-demand fatal `setUserIdentifier` behaviour. Github
14+
[#10759](https://github.com/firebase/flutterfire/issues/10759)
1315

1416
# 18.4.3
1517
* [fixed] Disabled `GradleMetadataPublishing` to fix breakage of the Kotlin extensions library. [#5337]

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.crashlytics.internal.common;
1616

17+
import static org.mockito.AdditionalMatchers.not;
1718
import static org.mockito.Mockito.any;
1819
import static org.mockito.Mockito.anyLong;
1920
import static org.mockito.Mockito.anyString;
@@ -38,6 +39,7 @@
3839
import com.google.firebase.crashlytics.internal.NativeSessionFileProvider;
3940
import com.google.firebase.crashlytics.internal.analytics.AnalyticsEventLogger;
4041
import com.google.firebase.crashlytics.internal.metadata.LogFileManager;
42+
import com.google.firebase.crashlytics.internal.metadata.UserMetadata;
4143
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
4244
import com.google.firebase.crashlytics.internal.persistence.FileStore;
4345
import com.google.firebase.crashlytics.internal.settings.Settings;
@@ -52,6 +54,7 @@
5254
import java.util.TreeSet;
5355
import java.util.concurrent.Executor;
5456
import java.util.concurrent.TimeUnit;
57+
import org.junit.Test;
5558
import org.mockito.ArgumentCaptor;
5659

5760
public class CrashlyticsControllerTest extends CrashlyticsTestCase {
@@ -101,22 +104,33 @@ private class ControllerBuilder {
101104
private CrashlyticsNativeComponent nativeComponent = null;
102105
private AnalyticsEventLogger analyticsEventLogger;
103106
private SessionReportingCoordinator sessionReportingCoordinator;
107+
108+
private CrashlyticsBackgroundWorker backgroundWorker;
104109
private LogFileManager logFileManager = null;
105110

111+
private UserMetadata userMetadata = null;
112+
106113
ControllerBuilder() {
107114
dataCollectionArbiter = mockDataCollectionArbiter;
108115
nativeComponent = mockNativeComponent;
109116

110117
analyticsEventLogger = mock(AnalyticsEventLogger.class);
111118

112119
sessionReportingCoordinator = mockSessionReportingCoordinator;
120+
121+
backgroundWorker = new CrashlyticsBackgroundWorker(new SameThreadExecutorService());
113122
}
114123

115124
ControllerBuilder setDataCollectionArbiter(DataCollectionArbiter arbiter) {
116125
dataCollectionArbiter = arbiter;
117126
return this;
118127
}
119128

129+
ControllerBuilder setUserMetadata(UserMetadata userMetadata) {
130+
this.userMetadata = userMetadata;
131+
return this;
132+
}
133+
120134
public ControllerBuilder setNativeComponent(CrashlyticsNativeComponent nativeComponent) {
121135
this.nativeComponent = nativeComponent;
122136
return this;
@@ -153,13 +167,13 @@ public CrashlyticsController build() {
153167
final CrashlyticsController controller =
154168
new CrashlyticsController(
155169
testContext.getApplicationContext(),
156-
new CrashlyticsBackgroundWorker(new SameThreadExecutorService()),
170+
backgroundWorker,
157171
idManager,
158172
dataCollectionArbiter,
159173
testFileStore,
160174
crashMarker,
161175
appData,
162-
null,
176+
userMetadata,
163177
logFileManager,
164178
sessionReportingCoordinator,
165179
nativeComponent,
@@ -211,6 +225,26 @@ public void testFatalException_callsSessionReportingCoordinatorPersistFatal() th
211225
.persistFatalEvent(eq(fatal), eq(thread), eq(sessionId), anyLong());
212226
}
213227

228+
@Test
229+
public void testOnDemandFatal_callLogFatalException() {
230+
Thread thread = Thread.currentThread();
231+
Exception fatal = new RuntimeException("Fatal");
232+
Thread.UncaughtExceptionHandler exceptionHandler = mock(Thread.UncaughtExceptionHandler.class);
233+
UserMetadata mockUserMetadata = mock(UserMetadata.class);
234+
when(mockSessionReportingCoordinator.listSortedOpenSessionIds())
235+
.thenReturn(new TreeSet<>(Collections.singleton(SESSION_ID)).descendingSet());
236+
237+
final CrashlyticsController controller =
238+
builder()
239+
.setLogFileManager(new LogFileManager(testFileStore))
240+
.setUserMetadata(mockUserMetadata)
241+
.build();
242+
controller.enableExceptionHandling(SESSION_ID, exceptionHandler, testSettingsProvider);
243+
controller.logFatalException(thread, fatal);
244+
245+
verify(mockUserMetadata).setNewSession(not(eq(SESSION_ID)));
246+
}
247+
214248
public void testNativeCrashDataCausesNativeReport() throws Exception {
215249
final String sessionId = "sessionId_1_new";
216250
final String previousSessionId = "sessionId_0_previous";

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/metadata/MetaDataStoreTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.firebase.crashlytics.internal.metadata;
1616

17+
import static com.google.common.truth.Truth.assertThat;
18+
1719
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
1820
import com.google.firebase.crashlytics.internal.common.CrashlyticsBackgroundWorker;
1921
import com.google.firebase.crashlytics.internal.persistence.FileStore;
@@ -23,6 +25,7 @@
2325
import java.util.Collections;
2426
import java.util.HashMap;
2527
import java.util.Map;
28+
import org.junit.Test;
2629

2730
@SuppressWarnings("ResultOfMethodCallIgnored") // Convenient use of files.
2831
public class MetaDataStoreTest extends CrashlyticsTestCase {
@@ -139,6 +142,55 @@ public void testReadUserData_noStoredData() {
139142
assertNull(userData.getUserId());
140143
}
141144

145+
@Test
146+
public void testUpdateSessionId_notPersistUserIdToNewSessionIfNoUserIdSet() {
147+
UserMetadata userMetadata = new UserMetadata(SESSION_ID_1, fileStore, worker);
148+
userMetadata.setNewSession(SESSION_ID_2);
149+
assertThat(fileStore.getSessionFile(SESSION_ID_2, UserMetadata.USERDATA_FILENAME).exists())
150+
.isFalse();
151+
}
152+
153+
@Test
154+
public void testUpdateSessionId_notPersistCustomKeysToNewSessionIfNoCustomKeysSet() {
155+
UserMetadata userMetadata = new UserMetadata(SESSION_ID_1, fileStore, worker);
156+
userMetadata.setNewSession(SESSION_ID_2);
157+
assertThat(fileStore.getSessionFile(SESSION_ID_2, UserMetadata.KEYDATA_FILENAME).exists())
158+
.isFalse();
159+
}
160+
161+
@Test
162+
public void testUpdateSessionId_persistCustomKeysToNewSessionIfCustomKeysSet() {
163+
UserMetadata userMetadata = new UserMetadata(SESSION_ID_1, fileStore, worker);
164+
final Map<String, String> keys =
165+
new HashMap<String, String>() {
166+
{
167+
put(KEY_1, VALUE_1);
168+
put(KEY_2, VALUE_2);
169+
put(KEY_3, VALUE_3);
170+
}
171+
};
172+
userMetadata.setCustomKeys(keys);
173+
userMetadata.setNewSession(SESSION_ID_2);
174+
assertThat(fileStore.getSessionFile(SESSION_ID_2, UserMetadata.KEYDATA_FILENAME).exists())
175+
.isTrue();
176+
177+
MetaDataStore metaDataStore = new MetaDataStore(fileStore);
178+
assertThat(metaDataStore.readKeyData(SESSION_ID_2)).isEqualTo(keys);
179+
}
180+
181+
@Test
182+
public void testUpdateSessionId_persistUserIdToNewSessionIfUserIdSet() {
183+
String userId = "ThemisWang";
184+
UserMetadata userMetadata = new UserMetadata(SESSION_ID_1, fileStore, worker);
185+
userMetadata.setUserId(userId);
186+
userMetadata.setNewSession(SESSION_ID_2);
187+
assertThat(fileStore.getSessionFile(SESSION_ID_2, UserMetadata.USERDATA_FILENAME).exists())
188+
.isTrue();
189+
190+
MetaDataStore metaDataStore = new MetaDataStore(fileStore);
191+
assertThat(metaDataStore.readUserId(SESSION_ID_2)).isEqualTo(userId);
192+
}
193+
142194
// Keys
143195

144196
public void testWriteKeys() {

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public Task<Void> call() throws Exception {
216216

217217
doWriteAppExceptionMarker(timestampMillis);
218218
doCloseSessions(settingsProvider);
219-
doOpenSession(new CLSUUID(idManager).toString());
219+
doOpenSession(new CLSUUID(idManager).toString(), isOnDemand);
220220

221221
// If automatic data collection is disabled, we'll need to wait until the next run
222222
// of the app.
@@ -498,7 +498,7 @@ void openSession(String sessionIdentifier) {
498498
new Callable<Void>() {
499499
@Override
500500
public Void call() throws Exception {
501-
doOpenSession(sessionIdentifier);
501+
doOpenSession(sessionIdentifier, /*isOnDemand=*/ false);
502502
return null;
503503
}
504504
});
@@ -550,7 +550,7 @@ boolean finalizeSessions(SettingsProvider settingsProvider) {
550550
* Not synchronized/locked. Must be executed from the single thread executor service used by this
551551
* class.
552552
*/
553-
private void doOpenSession(String sessionIdentifier) {
553+
private void doOpenSession(String sessionIdentifier, Boolean isOnDemand) {
554554
final long startedAtSeconds = getCurrentTimestampSeconds();
555555

556556
Logger.getLogger().d("Opening a new session with ID " + sessionIdentifier);
@@ -568,6 +568,14 @@ private void doOpenSession(String sessionIdentifier) {
568568
startedAtSeconds,
569569
StaticSessionData.create(appData, osData, deviceData));
570570

571+
// If is on-demand fatal, we need to update the session id for userMetadata
572+
// as well(since we don't really change the object to a new one for a new session).
573+
// all the information in the previous session is still in memory, but we do need to
574+
// manually writing them into persistence for the new session.
575+
if (isOnDemand && sessionIdentifier != null) {
576+
userMetadata.setNewSession(sessionIdentifier);
577+
}
578+
571579
logFileManager.setCurrentSession(sessionIdentifier);
572580
sessionsSubscriber.setSessionId(sessionIdentifier);
573581
reportingCoordinator.onBeginSession(sessionIdentifier, startedAtSeconds);

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/metadata/UserMetadata.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class UserMetadata {
4141

4242
private final MetaDataStore metaDataStore;
4343
private final CrashlyticsBackgroundWorker backgroundWorker;
44-
private final String sessionIdentifier;
44+
private String sessionIdentifier;
4545

4646
// The following references contain a marker bit, which is true if the data maintained in the
4747
// associated reference has been serialized since the last time it was updated.
@@ -77,6 +77,26 @@ public UserMetadata(
7777
this.backgroundWorker = backgroundWorker;
7878
}
7979

80+
/**
81+
* Refresh the userMetadata to reflect the status of the new session. This API is mainly for
82+
* on-demand fatal feature since we need to close and update to a new session. UserMetadata also
83+
* need to make this update instead of updating session id, we also need to manually writing the
84+
* into persistence for the new session.
85+
*/
86+
public void setNewSession(String sessionId) {
87+
synchronized (sessionIdentifier) {
88+
sessionIdentifier = sessionId;
89+
Map<String, String> keyData = customKeys.getKeys();
90+
if (getUserId() != null) {
91+
metaDataStore.writeUserData(sessionId, getUserId());
92+
}
93+
if (!keyData.isEmpty()) {
94+
metaDataStore.writeKeyData(sessionId, keyData);
95+
}
96+
// TODO(themis): adding feature rollouts later
97+
}
98+
}
99+
80100
@Nullable
81101
public String getUserId() {
82102
return userId.getReference();

0 commit comments

Comments
 (0)