Skip to content

ADFA-4942: Gate GlitchTip and Firebase analytics behind an onboarding opt-out consent - #1617

Merged
Daniel-ADFA merged 6 commits into
stagefrom
ADFA-4942
Aug 11, 2026
Merged

ADFA-4942: Gate GlitchTip and Firebase analytics behind an onboarding opt-out consent#1617
Daniel-ADFA merged 6 commits into
stagefrom
ADFA-4942

Conversation

@Daniel-ADFA

Copy link
Copy Markdown
Contributor

Adds an opt-out for GlitchTip and Firebase analytics (ADFA-4942): one-time consent at onboarding, no Settings toggle, accept as the prominent default (per ticket design). Users exposed to network-level surveillance (SNI/DPI) can keep CoGo fully offline.

How it works

  • Manifest disables both SDKs' auto-init - nothing transmits before the choice.
  • Tri-state consent (UNSET/GRANTED/DECLINED) in device-protected SharedPreferences (StatPreferences).
  • DeviceProtectedApplicationLoader inits telemetry only on GRANTED; Accept triggers the same idempotent path on first run.
  • Dialog: "Share anonymous data" / "Keep offline" (sticky, clear-app-data resets) / "Learn more" (records nothing).
  • AnalyticsManager's collection flag is consent-driven, so ungated track calls can't re-enable it for declined users.
  • Upgraders who accepted the legacy disclosure migrate to GRANTED once.

Demo

Screen_recording_20260803_162156.webm

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough
  • Add one-time onboarding consent for GlitchTip and Firebase Analytics.
  • Store consent as UNSET, GRANTED, or DECLINED in device-protected storage.
  • Initialize telemetry only after the user selects “Share anonymous data.”
  • Let users select “Keep offline” to disable analytics and crash reporting.
  • Disable automatic SDK initialization in AndroidManifest.xml.
  • Prevent declined users from re-enabling analytics through ungated tracking calls.
  • Migrate accepted legacy privacy disclosures to GRANTED once.
  • Reset consent when the user clears app data.
  • Add tests for consent persistence, corrupt values, analytics behavior, and legacy-consent migration.
  • Risk: The codebase contains direct Sentry capture calls outside the consent flow. Audit these calls to confirm that declined users cannot send telemetry.
  • Risk: The one-time migration can grant consent based on legacy disclosure data that may not meet current consent requirements.
  • Best practice: Verify privacy strings, consent storage, and SDK initialization behavior on fresh installs, upgrades, and app-data resets.

Walkthrough

The PR replaces the legacy privacy flag with persisted telemetry consent states. Startup and Firebase Analytics now require granted consent. Onboarding provides accept, decline, and learn-more actions. Tests cover migration, persistence, analytics, and dialog controls.

Changes

Telemetry consent

Layer / File(s) Summary
Consent storage and validation
preferences/src/main/.../StatPreferences.kt, app/src/test/.../StatPreferencesTest.kt
StatPreferences stores TelemetryConsent in device-protected preferences. Missing or invalid values resolve to UNSET.
Consent-gated telemetry startup
app/src/main/.../DeviceProtectedApplicationLoader.kt, app/src/main/.../AnalyticsManager.kt, app/src/main/AndroidManifest.xml, app/src/test/.../TelemetryConsentMigrationTest.kt
Startup migrates legacy consent and initializes telemetry once when consent is granted. Firebase Analytics and Sentry automatic collection are disabled by default.
Onboarding consent dialog
app/src/main/.../PermissionsFragment.kt, resources/src/main/res/values/strings.xml
The onboarding dialog presents data-sharing choices. Acceptance grants consent and initializes telemetry. Declining records refusal and closes Sentry.
Consent flow validation
app/src/test/.../AnalyticsManagerConsentTest.kt, app/src/androidTest/.../HandlePrivacyDisclosureHelper.kt
Tests and UI helpers verify analytics behavior, consent persistence, and dialog controls.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PermissionsFragment
  participant StatPreferences
  participant DeviceProtectedApplicationLoader
  participant AnalyticsManager
  participant Firebase
  User->>PermissionsFragment: choose telemetry consent
  PermissionsFragment->>StatPreferences: persist GRANTED or DECLINED
  PermissionsFragment->>DeviceProtectedApplicationLoader: initialize after GRANTED
  DeviceProtectedApplicationLoader->>AnalyticsManager: initialize telemetry
  AnalyticsManager->>Firebase: enable analytics collection
Loading

Possibly related PRs

Suggested reviewers: jatezzz

Poem

A rabbit taps “Share anonymous data,”
Consent rests in its little nest.
If granted, telemetry starts.
If declined, trackers stay away.
Offline hops are honored.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes gating GlitchTip and Firebase analytics behind onboarding consent.
Description check ✅ Passed The description directly explains the consent flow, telemetry gating, migration behavior, and offline option implemented by the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-4942

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.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (4)
preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt (2)

35-49: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Cache fields are not synchronized across threads.

cachedPrefs and cachedPrefsApp are plain var fields read and written from multiple threads: PermissionsFragment writes telemetryConsent on the main thread, while DeviceProtectedApplicationLoader.initTelemetryIfConsented reads it from a Dispatchers.Default coroutine. Without @Volatile or synchronization, one thread may not see another thread's write to these fields.

In practice, the impact is limited because Android's ContextImpl already caches SharedPreferences instances per file path internally, so a stale read here just means an extra call to getSharedPreferences(), not incorrect data. Consider marking these fields @Volatile for correctness under the Java Memory Model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt`
around lines 35 - 49, Mark both cachedPrefs and cachedPrefsApp in the
StatPreferences cache with `@Volatile` so updates are visible across threads under
the Java Memory Model, while preserving the existing cache lookup and
initialization behavior.

30-59: 📐 Maintainability & Code Quality | 🔵 Trivial

Add KDoc for the public consent API.

StatPreferences.telemetryConsent has a non-obvious contract: it falls back to TelemetryConsent.UNSET for missing or corrupt stored values, and writes are asynchronous through apply(). Document this contract, including the threading expectations, since callers in DeviceProtectedApplicationLoader and PermissionsFragment read and write this property from different dispatchers.

As per coding guidelines, "Public classes, functions, and non-obvious logic must have KDoc or Javadoc documenting contracts, rationale, threading, nullability, side effects, or units."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt`
around lines 30 - 59, Add KDoc to the public StatPreferences.telemetryConsent
property documenting that missing or invalid persisted values return
TelemetryConsent.UNSET, writes use asynchronous SharedPreferences.apply(), and
callers must follow the existing threading expectations when accessing it from
different dispatchers.

Source: Coding guidelines

app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt (1)

108-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add KDoc to the public telemetry-gating functions.

initTelemetryIfConsented and onTelemetryConsentGranted are public functions with non-obvious contracts: consent gating, a one-time atomic guard, and thread dispatch (Dispatchers.Default then Dispatchers.Main). Document these contracts for future maintainers.

As per coding guidelines, "Public classes, functions, and non-obvious logic must have KDoc or Javadoc documenting contracts, rationale, threading, nullability, side effects, or units."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt`
around lines 108 - 171, Add KDoc to the public functions
initTelemetryIfConsented and onTelemetryConsentGranted documenting consent
gating, the one-time atomic initialization guard, telemetry initialization side
effects, and their threading behavior: the caller launch uses
Dispatchers.Default and analytics initialization switches to Dispatchers.Main.

Source: Coding guidelines

app/src/androidTest/kotlin/com/itsaky/androidide/helper/HandlePrivacyDisclosureHelper.kt (1)

29-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the decline ("Keep offline") path in this helper.

This helper verifies the dialog's appearance and the accept path, but it does not click "Keep offline" and confirm TelemetryConsent.DECLINED persists end-to-end. Since the opt-out flow is a primary objective of this PR, exercising the decline branch here would close the last gap in UI-level coverage for consent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/androidTest/kotlin/com/itsaky/androidide/helper/HandlePrivacyDisclosureHelper.kt`
around lines 29 - 74, Extend handlePrivacyDisclosure to exercise the decline
path using the existing declineText control: click “Keep offline,” wait for the
UI to become idle, and verify with the same persistence retry pattern that
StatPreferences.telemetryConsent becomes TelemetryConsent.DECLINED. Preserve the
existing accept-path assertions and dialog verification.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@app/src/androidTest/kotlin/com/itsaky/androidide/helper/HandlePrivacyDisclosureHelper.kt`:
- Around line 29-74: Extend handlePrivacyDisclosure to exercise the decline path
using the existing declineText control: click “Keep offline,” wait for the UI to
become idle, and verify with the same persistence retry pattern that
StatPreferences.telemetryConsent becomes TelemetryConsent.DECLINED. Preserve the
existing accept-path assertions and dialog verification.

In
`@app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt`:
- Around line 108-171: Add KDoc to the public functions initTelemetryIfConsented
and onTelemetryConsentGranted documenting consent gating, the one-time atomic
initialization guard, telemetry initialization side effects, and their threading
behavior: the caller launch uses Dispatchers.Default and analytics
initialization switches to Dispatchers.Main.

In
`@preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt`:
- Around line 35-49: Mark both cachedPrefs and cachedPrefsApp in the
StatPreferences cache with `@Volatile` so updates are visible across threads under
the Java Memory Model, while preserving the existing cache lookup and
initialization behavior.
- Around line 30-59: Add KDoc to the public StatPreferences.telemetryConsent
property documenting that missing or invalid persisted values return
TelemetryConsent.UNSET, writes use asynchronous SharedPreferences.apply(), and
callers must follow the existing threading expectations when accessing it from
different dispatchers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 96611758-b0d8-47f1-ad0f-dd6800ce0a02

📥 Commits

Reviewing files that changed from the base of the PR and between 506e297 and b16b39c.

📒 Files selected for processing (12)
  • app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt
  • app/src/androidTest/kotlin/com/itsaky/androidide/helper/HandlePrivacyDisclosureHelper.kt
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/itsaky/androidide/analytics/AnalyticsManager.kt
  • app/src/main/java/com/itsaky/androidide/app/DeviceProtectedApplicationLoader.kt
  • app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt
  • app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt
  • app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt
  • app/src/test/java/com/itsaky/androidide/app/TelemetryConsentMigrationTest.kt
  • app/src/test/java/com/itsaky/androidide/preferences/StatPreferencesTest.kt
  • preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt
  • resources/src/main/res/values/strings.xml

@Daniel-ADFA
Daniel-ADFA requested a review from a team August 3, 2026 22:25
Comment thread app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt (1)

65-72: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the telemetry-consent property contract.

Add KDoc for the device-protected storage behavior and the UNSET fallback for missing or invalid values.

As per coding guidelines, “Public classes, functions, and non-obvious logic must have KDoc.”

Proposed KDoc
+	/**
+	 * Stores the user's telemetry consent in device-protected preferences.
+	 *
+	 * Missing or invalid values return [TelemetryConsent.UNSET].
+	 */
 	var telemetryConsent: TelemetryConsent
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt`
around lines 65 - 72, Add KDoc to the public telemetryConsent property in
StatPreferences, documenting that it uses device-protected preferences and
returns TelemetryConsent.UNSET when the stored value is missing or invalid. Keep
the existing getter and setter behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt`:
- Around line 47-58: Update the StatPreferences cache-miss path around
TELEMETRY_CONSENT to avoid synchronously loading SharedPreferences on the main
thread and remove the allowThreadDiskReads exemption. Probe or load the consent
state from a background context, keeping telemetry disabled until that
asynchronous state is available; preserve the existing in-memory preference
behavior once loaded.

---

Outside diff comments:
In
`@preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt`:
- Around line 65-72: Add KDoc to the public telemetryConsent property in
StatPreferences, documenting that it uses device-protected preferences and
returns TelemetryConsent.UNSET when the stored value is missing or invalid. Keep
the existing getter and setter behavior unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 97abb0b0-0a58-4282-b041-18020aef1bd0

📥 Commits

Reviewing files that changed from the base of the PR and between 4938ece and ab72cae.

📒 Files selected for processing (1)
  • preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt (2)

37-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add KDoc to the added public test functions.

Kotlin member functions are public by default. These test functions lack KDoc for their consent state and expected collection behavior. Add concise KDoc to each function as required by the repository guidelines.

Also applies to: 45-45, 53-53, 61-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt`
at line 37, Add concise KDoc to each newly added public test function in
AnalyticsManagerConsentTest, including the tests around “track call before
initialize keeps collection disabled” and the functions at the referenced
locations. Document the consent state being exercised and the expected analytics
collection behavior, while leaving the test logic unchanged.

Source: Coding guidelines


37-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Confirm the test framework policy for this class.

The added tests inherit @RunWith(RobolectricTestRunner::class), which indicates a JUnit 4 runner. The repository guideline requires JUnit Jupiter for src/test tests. Migrate this class to the approved Jupiter/Robolectric setup, or confirm an explicit legacy exception.

Also applies to: 45-50, 53-58, 61-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt`
around lines 37 - 42, Update AnalyticsManagerConsentTest to use the approved
JUnit Jupiter + Robolectric setup instead of the JUnit 4 runner inherited from
`@RunWith`(RobolectricTestRunner::class). Adjust the class-level test annotations
and execution style accordingly while keeping the existing test methods and
mocks intact, or otherwise document an explicit legacy exception if this class
is intentionally excluded from the JUnit Jupiter policy.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt`:
- Around line 45-50: Align the test with its stated target by either renaming
`metric call before initialize keeps collection disabled` to describe
`trackProjectOpened`, or change the invocation to `trackMetric` with a
representative metric; ensure the final test name and method under test match.

---

Nitpick comments:
In
`@app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt`:
- Line 37: Add concise KDoc to each newly added public test function in
AnalyticsManagerConsentTest, including the tests around “track call before
initialize keeps collection disabled” and the functions at the referenced
locations. Document the consent state being exercised and the expected analytics
collection behavior, while leaving the test logic unchanged.
- Around line 37-42: Update AnalyticsManagerConsentTest to use the approved
JUnit Jupiter + Robolectric setup instead of the JUnit 4 runner inherited from
`@RunWith`(RobolectricTestRunner::class). Adjust the class-level test annotations
and execution style accordingly while keeping the existing test methods and
mocks intact, or otherwise document an explicit legacy exception if this class
is intentionally excluded from the JUnit Jupiter policy.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe13a9de-9e28-43ae-b69d-fd09f7eae734

📥 Commits

Reviewing files that changed from the base of the PR and between ab72cae and f6746db.

📒 Files selected for processing (4)
  • app/src/main/java/com/itsaky/androidide/analytics/AnalyticsManager.kt
  • app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt
  • app/src/test/java/com/itsaky/androidide/analytics/AnalyticsManagerConsentTest.kt
  • preferences/src/main/java/com/itsaky/androidide/preferences/internal/StatPreferences.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt

@Daniel-ADFA
Daniel-ADFA requested a review from itsaky-adfa August 5, 2026 10:42

@itsaky-adfa itsaky-adfa left a comment

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.

@Daniel-ADFA We still want to verify that there are no new StrictMode issues during QA.

@Daniel-ADFA
Daniel-ADFA merged commit ad23205 into stage Aug 11, 2026
4 checks passed
@Daniel-ADFA
Daniel-ADFA deleted the ADFA-4942 branch August 11, 2026 10:26
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