Skip to content

Crashlytics exception handler #6266

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

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion firebase-crashlytics-ndk/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version=19.0.4
version=19.1.0
latestReleasedVersion=19.0.3
10 changes: 8 additions & 2 deletions firebase-crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Unreleased
* [feature] Added the `isCrashlyticsCollectionEnabled` API to check if Crashlytics collection is enabled.
([Github #5919](//github.com/firebase/firebase-android-sdk/issues/5919))
* [feature] Added the `isCrashlyticsCollectionEnabled` API to check if Crashlytics collection is
enabled.
(GitHub [#5919](https://github.com/firebase/firebase-android-sdk/issues/5919){: .external})
* [fixed] Ensure that on-demand fatal events are never processed on the main thread.
(GitHub [#4345](https://github.com/firebase/firebase-android-sdk/issues/4345){: .external})
* [fixed] Improved data consistency for rapid user actions.
* [changed] Internal changes to improve startup time.
* [changed] Internal changes to the way session IDs are generated.
* [changed] Internal changes to the way background tasks are scheduled.
* [changed] Migrated SDK to use standard Firebase executors.


# 19.0.3
Expand Down
2 changes: 1 addition & 1 deletion firebase-crashlytics/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version=19.0.4
version=19.1.0
latestReleasedVersion=19.0.3
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,9 @@ public void testUploadWithNoReports() throws Exception {

final CrashlyticsController controller = createController();

Task<Void> task = controller.submitAllReports(testSettingsProvider.getSettingsAsync());
controller.submitAllReports(testSettingsProvider.getSettingsAsync());

await(task);
crashlyticsWorkers.common.await();

verify(mockSessionReportingCoordinator).hasReportsToSend();
verifyNoMoreInteractions(mockSessionReportingCoordinator);
Expand All @@ -426,9 +426,9 @@ public void testUploadWithDataCollectionAlwaysEnabled() throws Exception {

final CrashlyticsController controller = createController();

final Task<Void> task = controller.submitAllReports(testSettingsProvider.getSettingsAsync());
controller.submitAllReports(testSettingsProvider.getSettingsAsync());

await(task);
crashlyticsWorkers.common.await();

verify(mockSessionReportingCoordinator).hasReportsToSend();
verify(mockDataCollectionArbiter).isAutomaticDataCollectionEnabled();
Expand All @@ -452,15 +452,13 @@ public void testUploadDisabledThenOptIn() throws Exception {
final ControllerBuilder builder = builder();
builder.setDataCollectionArbiter(arbiter);
final CrashlyticsController controller = builder.build();

final Task<Void> task = controller.submitAllReports(testSettingsProvider.getSettingsAsync());

controller.submitAllReports(testSettingsProvider.getSettingsAsync());
verify(arbiter).isAutomaticDataCollectionEnabled();
verify(mockSessionReportingCoordinator).hasReportsToSend();
verify(mockSessionReportingCoordinator, never()).sendReports(any(Executor.class));

await(controller.sendUnsentReports());
await(task);
crashlyticsWorkers.common.await();

verify(mockSessionReportingCoordinator).sendReports(any(Executor.class));
verifyNoMoreInteractions(mockSessionReportingCoordinator);
Expand All @@ -481,14 +479,14 @@ public void testUploadDisabledThenOptOut() throws Exception {
builder.setDataCollectionArbiter(arbiter);
final CrashlyticsController controller = builder.build();

final Task<Void> task = controller.submitAllReports(testSettingsProvider.getSettingsAsync());
controller.submitAllReports(testSettingsProvider.getSettingsAsync());

verify(arbiter).isAutomaticDataCollectionEnabled();
verify(mockSessionReportingCoordinator).hasReportsToSend();
verify(mockSessionReportingCoordinator, never()).removeAllReports();

await(controller.deleteUnsentReports());
await(task);
crashlyticsWorkers.common.await();

crashlyticsWorkers.diskWrite.await();

Expand Down Expand Up @@ -525,7 +523,7 @@ public void testUploadDisabledThenEnabled() throws Exception {
builder.setDataCollectionArbiter(arbiter);
final CrashlyticsController controller = builder.build();

final Task<Void> task = controller.submitAllReports(testSettingsProvider.getSettingsAsync());
controller.submitAllReports(testSettingsProvider.getSettingsAsync());

verify(mockSessionReportingCoordinator).hasReportsToSend();
verify(mockSessionReportingCoordinator, never()).sendReports(any(Executor.class));
Expand All @@ -547,7 +545,7 @@ public void testUploadDisabledThenEnabled() throws Exception {
when(app.isDataCollectionDefaultEnabled()).thenReturn(false);
assertFalse(arbiter.isAutomaticDataCollectionEnabled());

await(task);
crashlyticsWorkers.common.await();

verify(mockSessionReportingCoordinator).sendReports(any(Executor.class));
verifyNoMoreInteractions(mockSessionReportingCoordinator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.tasks.SuccessContinuation;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
Expand Down Expand Up @@ -379,14 +377,7 @@ private Task<CrashlyticsCore> startCoreAsync(CrashlyticsCore crashlyticsCore) {

return crashlyticsCore
.doBackgroundInitializationAsync(mockSettingsController)
.onSuccessTask(
new SuccessContinuation<Void, CrashlyticsCore>() {
@NonNull
@Override
public Task<CrashlyticsCore> then(@Nullable Void aVoid) throws Exception {
return Tasks.forResult(crashlyticsCore);
}
});
.onSuccessTask(unused -> Tasks.forResult(crashlyticsCore));
}

/** Helper class for building CrashlyticsCore instances. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@
import android.os.BatteryManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.concurrent.TestOnlyExecutors;
import com.google.firebase.crashlytics.internal.DevelopmentPlatformProvider;
import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers;
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.Execution;
import com.google.firebase.crashlytics.internal.settings.Settings;
Expand Down Expand Up @@ -66,6 +69,8 @@ public class CrashlyticsReportDataCaptureTest {
private int eventThreadImportance;
private int maxChainedExceptions;
private SettingsProvider testSettingsProvider;
private final CrashlyticsWorkers crashlyticsWorkers =
new CrashlyticsWorkers(TestOnlyExecutors.background(), TestOnlyExecutors.blocking());

@Mock private DevelopmentPlatformProvider developmentPlatformProvider;

Expand Down Expand Up @@ -118,7 +123,9 @@ public void testCaptureReport_containsUnityVersionInDeveloperPlatformFieldsWhenA
.thenReturn(expectedUnityVersion);
initDataCapture();

final CrashlyticsReport report = dataCapture.captureReportData("sessionId", 0);
Task<CrashlyticsReport> task =
crashlyticsWorkers.common.submit(() -> dataCapture.captureReportData("sessionId", 0));
CrashlyticsReport report = Tasks.await(task);

assertNotNull(report.getSession());
assertEquals(UNITY_PLATFORM, report.getSession().getApp().getDevelopmentPlatform());
Expand All @@ -132,7 +139,9 @@ public void testCaptureReport_containsNoDeveloperPlatformFieldsWhenUnityIsMissin
when(developmentPlatformProvider.getDevelopmentPlatform()).thenReturn(null);
initDataCapture();

final CrashlyticsReport report = dataCapture.captureReportData("sessionId", 0);
Task<CrashlyticsReport> task =
crashlyticsWorkers.common.submit(() -> dataCapture.captureReportData("sessionId", 0));
CrashlyticsReport report = Tasks.await(task);

assertNotNull(report.getSession());
assertNull(report.getSession().getApp().getDevelopmentPlatform());
Expand Down Expand Up @@ -307,10 +316,13 @@ public void testCaptureAnrEvent_noBuildIdInAppData_buildIdsNull() throws Excepti
}

@Test
public void testCaptureReportSessionFields() {
public void testCaptureReportSessionFields() throws Exception {
final String sessionId = "sessionId";
final long timestamp = System.currentTimeMillis();
final CrashlyticsReport report = dataCapture.captureReportData(sessionId, timestamp);

Task<CrashlyticsReport> task =
crashlyticsWorkers.common.submit(() -> dataCapture.captureReportData(sessionId, timestamp));
CrashlyticsReport report = Tasks.await(task);

assertEquals(sessionId, report.getSession().getIdentifier());
assertEquals(timestamp, report.getSession().getStartedAt());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@

import android.content.SharedPreferences;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.concurrent.TestOnlyExecutors;
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
import com.google.firebase.crashlytics.internal.common.InstallIdProvider.InstallIds;
import com.google.firebase.crashlytics.internal.concurrency.CrashlyticsWorkers;
import com.google.firebase.installations.FirebaseInstallationsApi;
import java.util.concurrent.TimeoutException;

public class IdManagerTest extends CrashlyticsTestCase {

SharedPreferences prefs;
SharedPreferences legacyPrefs;
private SharedPreferences prefs;
private SharedPreferences legacyPrefs;

private final CrashlyticsWorkers crashlyticsWorkers =
new CrashlyticsWorkers(TestOnlyExecutors.background(), TestOnlyExecutors.blocking());

@Override
public void setUp() throws Exception {
Expand Down Expand Up @@ -66,21 +72,21 @@ private IdManager createIdManager(String instanceId, DataCollectionArbiter arbit
return new IdManager(getContext(), getContext().getPackageName(), iid, arbiter);
}

public void testCreateUUID() {
public void testCreateUUID() throws Exception {
final String fid = "test_fid";
final IdManager idManager = createIdManager(fid, MOCK_ARBITER_ENABLED);
final String installId = idManager.getInstallIds().getCrashlyticsInstallId();
final String installId = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotNull(installId);

assertEquals(installId, prefs.getString(IdManager.PREFKEY_INSTALLATION_UUID, null));
assertNull(prefs.getString(IdManager.PREFKEY_ADVERTISING_ID, null));
assertEquals(fid, prefs.getString(IdManager.PREFKEY_FIREBASE_IID, null));

// subsequent calls should return the same id
assertEquals(installId, idManager.getInstallIds().getCrashlyticsInstallId());
assertEquals(installId, getInstallIds(idManager).getCrashlyticsInstallId());
}

public void testGetIdExceptionalCase_doesNotRotateInstallId() {
public void testGetIdExceptionalCase_doesNotRotateInstallId() throws Exception {
FirebaseInstallationsApi fis = mock(FirebaseInstallationsApi.class);
final String expectedInstallId = "expectedInstallId";
when(fis.getId())
Expand All @@ -93,12 +99,12 @@ public void testGetIdExceptionalCase_doesNotRotateInstallId() {

final IdManager idManager =
new IdManager(getContext(), getContext().getPackageName(), fis, MOCK_ARBITER_ENABLED);
final String actualInstallId = idManager.getInstallIds().getCrashlyticsInstallId();
final String actualInstallId = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotNull(actualInstallId);
assertEquals(expectedInstallId, actualInstallId);
}

public void testInstanceIdChanges_dataCollectionEnabled() {
public void testInstanceIdChanges_dataCollectionEnabled() throws Exception {
// Set up the initial state with a valid iid and uuid.
final String oldUuid = "old_uuid";
final String newFid = "new_test_fid";
Expand All @@ -111,18 +117,18 @@ public void testInstanceIdChanges_dataCollectionEnabled() {
// Initialize the manager with a different FID.
IdManager idManager = createIdManager(newFid, MOCK_ARBITER_ENABLED);

String installId = idManager.getInstallIds().getCrashlyticsInstallId();
String installId = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotNull(installId);
assertFalse(installId.equals(oldUuid));

assertEquals(installId, prefs.getString(IdManager.PREFKEY_INSTALLATION_UUID, null));
assertEquals(newFid, prefs.getString(IdManager.PREFKEY_FIREBASE_IID, null));

// subsequent calls should return the same id
assertEquals(installId, idManager.getInstallIds().getCrashlyticsInstallId());
assertEquals(installId, getInstallIds(idManager).getCrashlyticsInstallId());
}

void validateInstanceIdDoesntChange(boolean dataCollectionEnabled) {
void validateInstanceIdDoesntChange(boolean dataCollectionEnabled) throws Exception {
final String oldUuid = "test_uuid";
final String fid = dataCollectionEnabled ? "test_fid" : IdManager.createSyntheticFid();
// Set up the initial state with a valid iid and uuid.
Expand All @@ -136,7 +142,7 @@ void validateInstanceIdDoesntChange(boolean dataCollectionEnabled) {
IdManager idManager =
createIdManager(fid, dataCollectionEnabled ? MOCK_ARBITER_ENABLED : MOCK_ARBITER_DISABLED);

String installId = idManager.getInstallIds().getCrashlyticsInstallId();
String installId = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotNull(installId);

// Test that the UUID didn't change.
Expand All @@ -146,18 +152,18 @@ void validateInstanceIdDoesntChange(boolean dataCollectionEnabled) {
assertEquals(fid, prefs.getString(IdManager.PREFKEY_FIREBASE_IID, null));

// subsequent calls should return the same id
assertEquals(oldUuid, idManager.getInstallIds().getCrashlyticsInstallId());
assertEquals(oldUuid, getInstallIds(idManager).getCrashlyticsInstallId());
}

public void testInstanceIdDoesntChange_dataCollectionEnabled() {
public void testInstanceIdDoesntChange_dataCollectionEnabled() throws Exception {
validateInstanceIdDoesntChange(/* dataCollectionEnabled= */ true);
}

public void testInstanceIdDoesntChange_dataCollectionDisabled() {
public void testInstanceIdDoesntChange_dataCollectionDisabled() throws Exception {
validateInstanceIdDoesntChange(/* dataCollectionEnabled= */ false);
}

public void testInstanceIdRotatesWithDataCollectionFlag() {
public void testInstanceIdRotatesWithDataCollectionFlag() throws Exception {
final String originalUuid = "test_uuid";
final String originalFid = "test_fid";
// Set up the initial state with a valid iid and uuid.
Expand All @@ -169,44 +175,47 @@ public void testInstanceIdRotatesWithDataCollectionFlag() {

// Initialize the manager with the same FID.
IdManager idManager = createIdManager(originalFid, MOCK_ARBITER_ENABLED);
String firstUuid = idManager.getInstallIds().getCrashlyticsInstallId();
String firstUuid = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotNull(firstUuid);
assertEquals(originalUuid, firstUuid);

// subsequent calls should return the same id
assertEquals(firstUuid, idManager.getInstallIds().getCrashlyticsInstallId());
assertEquals(firstUuid, getInstallIds(idManager).getCrashlyticsInstallId());
assertEquals(
firstUuid,
createIdManager(originalFid, MOCK_ARBITER_ENABLED)
.getInstallIds()
getInstallIds(createIdManager(originalFid, MOCK_ARBITER_ENABLED))
.getCrashlyticsInstallId());

// Disable data collection manager and confirm we get a different id
idManager = createIdManager(originalFid, MOCK_ARBITER_DISABLED);
String secondUuid = idManager.getInstallIds().getCrashlyticsInstallId();
String secondUuid = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotSame(secondUuid, firstUuid);
assertEquals(secondUuid, idManager.getInstallIds().getCrashlyticsInstallId());
assertEquals(secondUuid, getInstallIds(idManager).getCrashlyticsInstallId());
assertEquals(
secondUuid,
createIdManager(null, MOCK_ARBITER_DISABLED).getInstallIds().getCrashlyticsInstallId());
getInstallIds(createIdManager(null, MOCK_ARBITER_DISABLED)).getCrashlyticsInstallId());
// Check that we cached an synthetic FID
final SharedPreferences prefs = CommonUtils.getSharedPrefs(getContext());
String cachedFid = prefs.getString(IdManager.PREFKEY_FIREBASE_IID, null);
assertTrue(IdManager.isSyntheticFid(cachedFid));

// re-enable data collection
idManager = createIdManager(originalFid, MOCK_ARBITER_ENABLED);
String thirdUuid = idManager.getInstallIds().getCrashlyticsInstallId();
String thirdUuid = getInstallIds(idManager).getCrashlyticsInstallId();
assertNotSame(thirdUuid, firstUuid);
assertNotSame(thirdUuid, secondUuid);
assertEquals(thirdUuid, idManager.getInstallIds().getCrashlyticsInstallId());
assertEquals(thirdUuid, getInstallIds(idManager).getCrashlyticsInstallId());
assertEquals(
thirdUuid,
createIdManager(originalFid, MOCK_ARBITER_ENABLED)
.getInstallIds()
getInstallIds(createIdManager(originalFid, MOCK_ARBITER_ENABLED))
.getCrashlyticsInstallId());
// The cached ID should be back to the original
cachedFid = prefs.getString(IdManager.PREFKEY_FIREBASE_IID, null);
assertEquals(cachedFid, originalFid);
}

/** Get the install ids on the common worker. */
private InstallIds getInstallIds(IdManager idManager) throws Exception {
return Tasks.await(crashlyticsWorkers.common.submit(idManager::getInstallIds));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public void testWriteUserData_emptyString() throws Exception {
SESSION_ID_1, metadataWithUserId(SESSION_ID_1, "").getUserId());
});
crashlyticsWorkers.diskWrite.await();
Thread.sleep(3);
UserMetadata userData =
UserMetadata.loadFromExistingSession(SESSION_ID_1, fileStore, crashlyticsWorkers);
assertEquals("", userData.getUserId());
Expand Down
Loading
Loading