Skip to content

Add didANRKillOnPreviousExecution() to FirebaseCrashlytics#8112

Open
jrodiz wants to merge 3 commits into
mainfrom
feature/jrc--Issue4201.API.to.check.previous.ANR
Open

Add didANRKillOnPreviousExecution() to FirebaseCrashlytics#8112
jrodiz wants to merge 3 commits into
mainfrom
feature/jrc--Issue4201.API.to.check.previous.ANR

Conversation

@jrodiz
Copy link
Copy Markdown
Collaborator

@jrodiz jrodiz commented May 7, 2026

Add didANRKillOnPreviousExecution() to FirebaseCrashlytics

Implements #4201.

Background

Crashlytics already detects ANRs and files reports for them, and developers have long been able to call didCrashOnPreviousExecution() to gate logic that should only run after a fatal crash. However, there was no equivalent API for ANRs: developers who want to adapt their app's behavior after an ANR (e.g., disable a heavy feature on the next cold start, log a custom key, or show a recovery dialog) had no supported way to do so.

didCrashOnPreviousExecution() works by reading a marker file written at crash time. ANRs cannot use that mechanism because the main thread is frozen and the crash handler never runs. Instead, the new method uses ApplicationExitInfo (API 30+), which records process-exit reasons at the OS level — the same source already queried internally by SessionReportingCoordinator when filing ANR reports. The new API delegates to that existing infrastructure rather than introducing a separate detection path.

Risk

  • API surface: one new public method on FirebaseCrashlytics (didANRKillOnPreviousExecution()). No existing method signatures changed.
  • Behavior on API < 30: always returns false; no ApplicationExitInfo access attempted.
  • Behavior on API 30+: reads getHistoricalProcessExitReasons once at startup on the background thread with a bounded timeout. This is the sameActivityManager call already made by the ANR-reporting path; no new system service is introduced.
  • No dependency bumps, no public API removals, no migration required.

jrodiz added 3 commits May 7, 2026 15:08
Implements firebase-android-sdk#4201 by exposing a new public API method
that detects whether the app was killed by an ANR in the previous run,
mirroring the existing didCrashOnPreviousExecution() pattern.

On Android API 30+ the method queries ApplicationExitInfo (already used
internally for ANR session reporting) against the previous session's start
timestamp. On older API levels it always returns false.
… check failures

- Add null guard on ActivityManager from getSystemService() before
  calling getHistoricalProcessExitReasons() to avoid potential NPE.
- Log exceptions in checkForPreviousAnr() at verbose level instead of
  silently ignoring them, to aid diagnosability.
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

📝 PRs merging into main branch

Our main branch should always be in a releasable state. If you are working on a larger change, or if you don't want this change to see the light of the day just yet, consider using a feature branch first, and only merge into the main branch when the code complete and ready to be released.

@jrodiz jrodiz requested a review from mrober May 7, 2026 21:40

Boolean result;
try {
result = future.get(DEFAULT_MAIN_HANDLER_TIMEOUT_SEC, TimeUnit.SECONDS);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this block on main thread?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes —future.get() blocks the main thread for up to DEFAULT_MAIN_HANDLER_TIMEOUT_SEC (3 s) while the background executor runs getHistoricalProcessExitReasons. This is intentional and mirrors the existing checkForPreviousCrash() directly above it, which submits to the same executor and blocks with the same timeout. The bound is the same 3 s, and on the timeout path the method returns false safely.

@jrodiz
Copy link
Copy Markdown
Collaborator Author

jrodiz commented May 11, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the didANRKillOnPreviousExecution() method to FirebaseCrashlytics, allowing applications to detect if they were terminated by an ANR in the previous run on Android API 30+. The implementation includes changes to CrashlyticsCore to check for ANRs during initialization and updates to CrashlyticsController and SessionReportingCoordinator to process historical exit reasons. Feedback focuses on optimizing initialization performance by combining blocking checks, improving naming consistency for the public API, simplifying code with method references, and enhancing error logging.

Comment on lines 199 to +200
checkForPreviousCrash();
checkForPreviousAnr();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling checkForPreviousCrash() and checkForPreviousAnr() sequentially results in a cumulative blocking time of up to 6 seconds on the initialization thread (which is often the main thread). Consider combining these checks into a single background task that shares a single DEFAULT_MAIN_HANDLER_TIMEOUT_SEC timeout to ensure app startup is not excessively delayed.

*
* @return true if an ANR was recorded during the previous run of the app.
*/
public boolean didANRKillOnPreviousExecution() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

For consistency with the existing didCrashOnPreviousExecution() method, consider renaming this to didANROnPreviousExecution(). The internal methods in CrashlyticsCore and CrashlyticsController already use this more concise name.

Suggested change
public boolean didANRKillOnPreviousExecution() {
public boolean didANROnPreviousExecution() {

crashlyticsWorkers
.common
.getExecutor()
.submit(() -> controller.didANROnPreviousExecution());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

This lambda can be simplified to a method reference.

Suggested change
.submit(() -> controller.didANROnPreviousExecution());
.submit(controller::didANROnPreviousExecution);

try {
result = future.get(DEFAULT_MAIN_HANDLER_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (Exception ex) {
Logger.getLogger().v("Error checking for previous ANR: " + ex.getMessage());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Logging only ex.getMessage() can be unhelpful if the message is null (e.g., for a TimeoutException). It is better to pass the exception object directly to the logger to capture the full stack trace and exception type.

Suggested change
Logger.getLogger().v("Error checking for previous ANR: " + ex.getMessage());
Logger.getLogger().v("Error checking for previous ANR.", ex);

public class FirebaseCrashlytics {
method public com.google.android.gms.tasks.Task<java.lang.Boolean!> checkForUnsentReports();
method public void deleteUnsentReports();
method public boolean didANRKillOnPreviousExecution();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

If the public method is renamed for consistency, this API signature should be updated accordingly.

    method public boolean didANROnPreviousExecution();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants