Skip to content

Conversation

@OrangeAndGreen
Copy link
Contributor

Back-merging the change from the 2.59.2 hotfix

OrangeAndGreen and others added 5 commits September 9, 2025 14:00
Instead, sending empty hash/token to PersonalID and it can choose whether we fail.
Reporting heartbeat-triggered integrity reports as "Heartbeat".
Reporting failed integrity checks during start of PersonalID configuration as "PersonalID Configuration"
Integrity token workaround for Connect
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="106"
android:versionName="2.60">
android:versionName="2.59.2">
Copy link
Contributor

Choose a reason for hiding this comment

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

should be 2.60 in master

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting, is this something that's missing from the hotfix scripts? I didn't see any error indicating that this step was attempted and failed. Either way, fixed here: 0835f55

Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm not mistaken, this happens when you merge master into the release branch before backmerging

Copy link
Contributor

Choose a reason for hiding this comment

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

Think I do that manually everytime before backmerging for both major and hotfix releases, likely that something in scripts is not working as expected here.

@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

📝 Walkthrough

Walkthrough

  • AndroidManifest: versionName changed from "2.60" to "2.59.2" (versionCode 106 unchanged).
  • Firebase analytics: reportPersonalIdIntegritySubmission now includes a trigger parameter; added configuration/heartbeat wrapper methods; added TRIGGER constant.
  • IntegrityReporterWorker: on token fetch failure, derives error code via IntegrityTokenApiRequestHelper.getCodeForException and reports heartbeat analytics; also updates send-failure analytics.
  • IntegrityTokenApiRequestHelper: adds getCodeForException(Throwable) to normalize error codes.
  • ReportIntegrityResponseParser: switches to heartbeat analytics method.
  • PersonalIdPhoneFragment: allows nullable integrity token, retries configuration without token on token failure, and reports configuration-triggered analytics.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant F as PersonalIdPhoneFragment
  participant H as IntegrityTokenApiRequestHelper
  participant A as FirebaseAnalyticsUtil
  participant S as Server (Start Configuration)

  U->>F: Start configuration
  F->>H: fetchIntegrityToken()
  alt Token success
    H-->>F: StandardIntegrityToken
    F->>S: makeStartConfigurationCall(token, requestHash)
    S-->>F: Response
  else Token failure
    H-->>F: Exception
    F->>A: reportPersonalIdConfigurationIntegritySubmission(errorCode)
    note right of F: Retry without token
    F->>S: makeStartConfigurationCall("", requestHash)
    S-->>F: Response
  end
Loading
sequenceDiagram
  autonumber
  participant W as IntegrityReporterWorker
  participant H as IntegrityTokenApiRequestHelper
  participant A as FirebaseAnalyticsUtil
  participant S as Server (Report Integrity)
  participant P as ReportIntegrityResponseParser

  W->>H: fetchIntegrityToken()
  alt Token success
    H-->>W: Token
    W->>S: reportIntegrity(token)
    alt Network/API success
      S-->>W: JSON
      W->>P: parse(JSON, requestId)
      P->>A: reportPersonalIdHeartbeatIntegritySubmission(requestId, result_code)
      P-->>W: true
      W-->>W: Result.success()
    else Network/API failure
      S-->>W: Error
      W->>A: reportPersonalIdHeartbeatIntegritySubmission(requestId, "SendError")
      W-->>W: Result.retry()/failure
    end
  else Token failure
    H-->>W: Exception
    W->>H: getCodeForException(exception)
    W->>A: reportPersonalIdHeartbeatIntegritySubmission(requestId, errorCode)
    W->>S: reportIntegrity(device_error=errorCode)
    S-->>W: (ignored)
    W-->>W: Result.failure()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

skip-integration-tests

Suggested reviewers

  • shubham1g5
  • avazirna
  • Jignesh-dimagi

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The current description only states “Back-merging the change from the 2.59.2 hotfix” and does not follow the repository’s required template sections for Product Description, Technical Summary, Safety Assurance, or Labels and Review, leaving out key context, rationale, testing details, and risk assessments. Please update the description to match the repository template by adding a Product Description (even if no visible UI changes), a Technical Summary with ticket links and design rationale, Feature Flag information if applicable, and a Safety Assurance section covering your testing approach, automated coverage, and QA plan, as well as the Labels and Review checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Commcare 2.59.2 back-merge” succinctly describes that this pull request back-merges the 2.59.2 hotfix into the master branch and directly highlights the core action and version involved, making it clear and specific for future readers.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch commcare_2.59

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
app/src/org/commcare/connect/network/connectId/parser/ReportIntegrityResponseParser.java (1)

23-24: Heartbeat wrapper usage — LGTM; add null-safe requestId

Casting anyInputObject to String can yield null; guard to avoid null in analytics.

-        FirebaseAnalyticsUtil.reportPersonalIdHeartbeatIntegritySubmission(((String)anyInputObject),
-                json.optString("result_code", "NoCodeFromServer"));
+        final String requestId = (anyInputObject instanceof String) ? (String) anyInputObject : "UnknownRequestId";
+        FirebaseAnalyticsUtil.reportPersonalIdHeartbeatIntegritySubmission(
+                requestId, json.optString("result_code", "NoCodeFromServer"));
app/src/org/commcare/android/integrity/IntegrityReporterWorker.kt (1)

64-66: Consider retrying on transient Play Integrity errors

For TOO_MANY_REQUESTS/GOOGLE_SERVER_UNAVAILABLE/NETWORK_ERROR/CLIENT_TRANSIENT_ERROR, a retry is likely preferable to immediate failure.

             val errorCode = IntegrityTokenApiRequestHelper.getCodeForException(it)
+            if (it is com.google.android.play.core.integrity.StandardIntegrityException &&
+                it.errorCode in setOf(
+                    com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.TOO_MANY_REQUESTS,
+                    com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE,
+                    com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.NETWORK_ERROR,
+                    com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.CLIENT_TRANSIENT_ERROR
+                )
+            ) {
+                return Result.retry()
+            }
             FirebaseAnalyticsUtil.reportPersonalIdHeartbeatIntegritySubmission(requestId, errorCode)
app/src/org/commcare/fragments/personalId/PersonalIdPhoneFragment.java (1)

389-394: Minor: avoid mutating method params

Use a local for resolvedRequestHash to keep params immutable/readable.

-        String token = integrityTokenResponse != null ? integrityTokenResponse.token() : "";
-        if(requestHash == null) {
-            requestHash = "";
-        }
+        final String token = integrityTokenResponse != null ? integrityTokenResponse.token() : "";
+        final String resolvedRequestHash = (requestHash != null) ? requestHash : "";
...
-        }.makeStartConfigurationCall(requireActivity(), body, token, requestHash);
+        }.makeStartConfigurationCall(requireActivity(), body, token, resolvedRequestHash);
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)

412-419: Trigger-aware analytics — good; refine requestId and enforce param length

  • Don’t set requestId to a label for configuration events; prefer empty string.
  • Use the existing reportEvent(String, String[], String[]) path to auto-trim params to 100 chars.
-    public static void reportPersonalIdConfigurationIntegritySubmission(String responseCode) {
-        String label = "PersonalID Configuration";
-        reportPersonalIdIntegritySubmission(label, label, responseCode);
-    }
+    public static void reportPersonalIdConfigurationIntegritySubmission(String responseCode) {
+        reportPersonalIdIntegritySubmission("PersonalID Configuration", "", responseCode);
+    }
@@
-    public static void reportPersonalIdIntegritySubmission(String trigger, String requestId, String responseCode) {
-        Bundle b = new Bundle();
-        b.putString(CCAnalyticsParam.REQUEST_ID, requestId);
-        b.putString(CCAnalyticsParam.TRIGGER, trigger);
-        b.putString(CCAnalyticsParam.RESULT_CODE, responseCode);
-        reportEvent(CCAnalyticsEvent.PERSONAL_ID_INTEGRITY_REPORTED, b);
-    }
+    public static void reportPersonalIdIntegritySubmission(String trigger, String requestId, String responseCode) {
+        reportEvent(CCAnalyticsEvent.PERSONAL_ID_INTEGRITY_REPORTED,
+                new String[]{CCAnalyticsParam.REQUEST_ID, CCAnalyticsParam.TRIGGER, CCAnalyticsParam.RESULT_CODE},
+                new String[]{String.valueOf(requestId), String.valueOf(trigger), String.valueOf(responseCode)});
+    }

Also applies to: 421-427

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5295a7f and 1372a65.

📒 Files selected for processing (7)
  • app/AndroidManifest.xml (1 hunks)
  • app/src/org/commcare/android/integrity/IntegrityReporterWorker.kt (2 hunks)
  • app/src/org/commcare/android/integrity/IntegrityTokenApiRequestHelper.kt (1 hunks)
  • app/src/org/commcare/connect/network/connectId/parser/ReportIntegrityResponseParser.java (1 hunks)
  • app/src/org/commcare/fragments/personalId/PersonalIdPhoneFragment.java (3 hunks)
  • app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1 hunks)
  • app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-02-04T21:22:56.537Z
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2949
File: app/src/org/commcare/fragments/connectId/ConnectIdPinFragment.java:244-275
Timestamp: 2025-02-04T21:22:56.537Z
Learning: Direct JSONObject parsing is acceptable for handling user data responses in ConnectIdPinFragment, as decided by the team. No need to enforce ConnectUserResponseParser usage in this context.

Applied to files:

  • app/src/org/commcare/connect/network/connectId/parser/ReportIntegrityResponseParser.java
🧬 Code graph analysis (3)
app/src/org/commcare/connect/network/connectId/parser/ReportIntegrityResponseParser.java (1)
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)
  • FirebaseAnalyticsUtil (41-578)
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)
app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1)
  • CCAnalyticsParam (7-43)
app/src/org/commcare/fragments/personalId/PersonalIdPhoneFragment.java (2)
app/src/org/commcare/android/integrity/IntegrityTokenApiRequestHelper.kt (1)
  • getCodeForException (156-162)
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)
  • FirebaseAnalyticsUtil (41-578)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Lint Code Base
🔇 Additional comments (4)
app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1)

31-31: Add trigger analytics key — LGTM

The new TRIGGER key cleanly supports the expanded event payload.

app/AndroidManifest.xml (1)

5-5: Confirm versionName downgrade on master

Back-merges shouldn’t generally lower master’s versionName. Please confirm this isn’t accidental and won’t impact release pipelines or Play pre-release tracks.

If unintended, revert this hunk:

-          android:versionName="2.59.2">
+          android:versionName="2.60">
app/src/org/commcare/android/integrity/IntegrityReporterWorker.kt (2)

64-66: Good switch to centralized error-code + heartbeat analytics

Centralizing code derivation and logging through the heartbeat wrapper looks correct.


96-96: Send-failure analytics path — LGTM

Heartbeat wrapper on network failure is consistent with the new schema.

Comment on lines +156 to +162
fun getCodeForException(exception: Throwable): String {
return if (exception is StandardIntegrityException) {
exception.errorCode.toString()
} else {
exception.message ?: "UnknownError"
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid sending raw exception messages to analytics

Using exception.message risks leaking details; prefer a sanitized, stable code.

-        fun getCodeForException(exception: Throwable): String {
-            return if (exception is StandardIntegrityException) {
-                exception.errorCode.toString()
-            } else {
-                exception.message ?: "UnknownError"
-            }
-        }
+        fun getCodeForException(exception: Throwable): String {
+            return if (exception is StandardIntegrityException) {
+                exception.errorCode.toString()
+            } else {
+                exception::class.simpleName ?: "UnknownError"
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun getCodeForException(exception: Throwable): String {
return if (exception is StandardIntegrityException) {
exception.errorCode.toString()
} else {
exception.message ?: "UnknownError"
}
}
fun getCodeForException(exception: Throwable): String {
return if (exception is StandardIntegrityException) {
exception.errorCode.toString()
} else {
exception::class.simpleName ?: "UnknownError"
}
}
🤖 Prompt for AI Agents
In app/src/org/commcare/android/integrity/IntegrityTokenApiRequestHelper.kt
around lines 156 to 162, the method currently returns exception.message for
non-StandardIntegrityException cases which may leak sensitive info; change it to
return a sanitized, stable identifier instead (for example
exception::class.java.simpleName or a fixed token like
"UnknownError"/"UnhandledException") so raw messages are never sent to
analytics, keeping the StandardIntegrityException branch unchanged.

Comment on lines +270 to 274
String errorCode = IntegrityTokenApiRequestHelper.Companion.getCodeForException(exception);
FirebaseAnalyticsUtil.reportPersonalIdConfigurationIntegritySubmission(errorCode);

makeStartConfigurationCall(null, body, null);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Possible NPE path: handleIntegritySubError can receive null tokenResponse

After onTokenFailure you call makeStartConfigurationCall(null, body, null). If the API then returns INTEGRITY_ERROR, the subsequent handleIntegritySubError(integrityTokenResponse, …) dereferences a null tokenResponse in showIntegrityCheckDialog(...). Add a null guard and avoid fall-through.

-                    case INTEGRITY_ERROR:
-                        handleIntegritySubError(integrityTokenResponse,
-                                personalIdSessionDataViewModel.getPersonalIdSessionData().getSessionFailureSubcode());
-                    default:
-                        navigateFailure(failureCode, t);
-                        break;
+                    case INTEGRITY_ERROR:
+                        if (integrityTokenResponse != null) {
+                            handleIntegritySubError(
+                                    integrityTokenResponse,
+                                    personalIdSessionDataViewModel.getPersonalIdSessionData().getSessionFailureSubcode());
+                            break;
+                        } else {
+                            onConfigurationFailure(
+                                    AnalyticsParamValue.START_CONFIGURATION_INTEGRITY_CHECK_FAILURE,
+                                    getString(R.string.personalid_configuration_process_failed_subtitle));
+                            break;
+                        }
+                    default:
+                        navigateFailure(failureCode, t);
+                        break;

Also applies to: 389-394

🤖 Prompt for AI Agents
In app/src/org/commcare/fragments/personalId/PersonalIdPhoneFragment.java around
lines 270-274 (and similarly around lines 389-394), handleIntegritySubError may
be called with a null integrityTokenResponse because you call
makeStartConfigurationCall(null, body, null) after onTokenFailure; add a null
guard to avoid dereferencing a null tokenResponse: before calling
makeStartConfigurationCall or before delegating to handleIntegritySubError,
check if the integrity token response is null and either log/report the
condition and return early or call the error path that does not call
showIntegrityCheckDialog; apply the same null-check logic to the other
occurrence at lines 389-394 to prevent the NPE.

@shubham1g5 shubham1g5 merged commit 53b0db4 into master Sep 11, 2025
5 of 9 checks passed
@shubham1g5 shubham1g5 deleted the commcare_2.59 branch September 11, 2025 04:07
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.

4 participants