diff --git a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java index ec2f7496f51..fc336d0e19d 100644 --- a/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java +++ b/firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java @@ -90,6 +90,17 @@ public void testListSortedOpenSessionIds_noOpenSessions() { assertTrue(openSessionIds.isEmpty()); } + @Test + public void testPersistReports_getStartTimestampMillis() { + final String sessionId = "testSession"; + final CrashlyticsReport testReport = makeTestReport(sessionId); + + reportPersistence.persistReport(testReport); + assertEquals( + testReport.getSession().getStartedAt() * 1000, + reportPersistence.getStartTimestampMillis(sessionId)); + } + @Test public void testHasFinalizedReports() { final String sessionId = "testSession"; diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java index da5857213ba..be6eb9c58e0 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java @@ -37,7 +37,6 @@ import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; @@ -168,14 +167,14 @@ synchronized void handleUncaughtException( // Capture the time that the crash occurs and close over it so that the time doesn't // reflect when we get around to executing the task later. - final Date time = new Date(); + final long timestampMillis = System.currentTimeMillis(); final Task handleUncaughtExceptionTask = backgroundWorker.submitTask( new Callable>() { @Override public Task call() throws Exception { - final long timestampSeconds = getTimestampSeconds(time); + final long timestampSeconds = getTimestampSeconds(timestampMillis); final String currentSessionId = getCurrentSessionId(); if (currentSessionId == null) { @@ -190,7 +189,7 @@ public Task call() throws Exception { reportingCoordinator.persistFatalEvent( ex, thread, currentSessionId, timestampSeconds); - doWriteAppExceptionMarker(time.getTime()); + doWriteAppExceptionMarker(timestampMillis); doCloseSessions(); doOpenSession(); @@ -397,14 +396,14 @@ public Void call() throws Exception { void writeNonFatalException(@NonNull final Thread thread, @NonNull final Throwable ex) { // Capture and close over the current time, so that we get the exact call time, // rather than the time at which the task executes. - final Date time = new Date(); + final long timestampMillis = System.currentTimeMillis(); backgroundWorker.submit( new Runnable() { @Override public void run() { if (!isHandlingException()) { - long timestampSeconds = getTimestampSeconds(time); + long timestampSeconds = getTimestampSeconds(timestampMillis); final String currentSessionId = getCurrentSessionId(); if (currentSessionId == null) { Logger.getLogger() @@ -661,11 +660,11 @@ private void finalizePreviousNativeSession(String previousSessionId) { } private static long getCurrentTimestampSeconds() { - return getTimestampSeconds(new Date()); + return getTimestampSeconds(System.currentTimeMillis()); } - private static long getTimestampSeconds(Date date) { - return date.getTime() / 1000; + private static long getTimestampSeconds(long timestampMillis) { + return timestampMillis / 1000; } // region Serialization to protobuf diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsReportDataCapture.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsReportDataCapture.java index f736697ae7b..92c37da541a 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsReportDataCapture.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsReportDataCapture.java @@ -79,8 +79,8 @@ public CrashlyticsReportDataCapture( this.stackTraceTrimmingStrategy = stackTraceTrimmingStrategy; } - public CrashlyticsReport captureReportData(String identifier, long timestamp) { - return buildReportData().setSession(populateSessionData(identifier, timestamp)).build(); + public CrashlyticsReport captureReportData(String identifier, long timestampSeconds) { + return buildReportData().setSession(populateSessionData(identifier, timestampSeconds)).build(); } public Event captureEventData( @@ -120,9 +120,9 @@ private CrashlyticsReport.Builder buildReportData() { .setPlatform(REPORT_ANDROID_PLATFORM); } - private CrashlyticsReport.Session populateSessionData(String identifier, long timestamp) { + private CrashlyticsReport.Session populateSessionData(String identifier, long timestampSeconds) { return CrashlyticsReport.Session.builder() - .setStartedAt(timestamp) + .setStartedAt(timestampSeconds) .setIdentifier(identifier) .setGenerator(GENERATOR) .setApp(populateSessionApplicationData()) diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java index 4d43f75a535..8725dd51c86 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java @@ -88,8 +88,9 @@ public static SessionReportingCoordinator create( } @Override - public void onBeginSession(@NonNull String sessionId, long timestamp) { - final CrashlyticsReport capturedReport = dataCapture.captureReportData(sessionId, timestamp); + public void onBeginSession(@NonNull String sessionId, long timestampSeconds) { + final CrashlyticsReport capturedReport = + dataCapture.captureReportData(sessionId, timestampSeconds); reportPersistence.persistReport(capturedReport); } diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistence.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistence.java index 7dc67afa3bf..867f9241395 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistence.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistence.java @@ -59,6 +59,9 @@ public class CrashlyticsReportPersistence { private static final String REPORT_FILE_NAME = "report"; private static final String USER_FILE_NAME = "user"; + // We use the lastModified timestamp of this file to quickly store and access the startTime in ms + // of a session. + private static final String SESSION_START_TIMESTAMP_FILE_NAME = "start-time"; private static final String EVENT_FILE_NAME_PREFIX = "event"; private static final int EVENT_COUNTER_WIDTH = 10; // String width of maximum positive int value private static final String EVENT_COUNTER_FORMAT = "%0" + EVENT_COUNTER_WIDTH + "d"; @@ -110,6 +113,10 @@ public void persistReport(@NonNull CrashlyticsReport report) { final File sessionDirectory = prepareDirectory(getSessionDirectoryById(sessionId)); final String json = TRANSFORM.reportToJson(report); writeTextFile(new File(sessionDirectory, REPORT_FILE_NAME), json); + writeTextFile( + new File(sessionDirectory, SESSION_START_TIMESTAMP_FILE_NAME), + "", + session.getStartedAt()); } catch (IOException e) { Logger.getLogger().d("Could not persist report for session " + sessionId, e); } @@ -177,6 +184,19 @@ public List listSortedOpenSessionIds() { return openSessionIds; } + /** + * Gets the startTimestampMs of the given sessionId. + * + * @param sessionId + * @return startTimestampMs + */ + public long getStartTimestampMillis(String sessionId) { + final File sessionDirectory = getSessionDirectoryById(sessionId); + final File sessionStartTimestampFile = + new File(sessionDirectory, SESSION_START_TIMESTAMP_FILE_NAME); + return sessionStartTimestampFile.lastModified(); + } + public boolean hasFinalizedReports() { return !getAllFinalizedReportFiles().isEmpty(); } @@ -489,6 +509,14 @@ private static void writeTextFile(File file, String text) throws IOException { } } + private static void writeTextFile(File file, String text, long lastModifiedTimestampSeconds) + throws IOException { + try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8)) { + writer.write(text); + file.setLastModified(convertTimestampFromSecondsToMs(lastModifiedTimestampSeconds)); + } + } + @NonNull private static String readTextFile(@NonNull File file) throws IOException { final byte[] readBuffer = new byte[8192]; @@ -532,4 +560,8 @@ private static void recursiveDelete(@Nullable File file) { } file.delete(); } + + private static long convertTimestampFromSecondsToMs(long timestampSeconds) { + return timestampSeconds * 1000; + } }