From 6d48d1a21a4ff8ddd5151c8572c5c589da400002 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 28 Aug 2026 13:38:09 -0600 Subject: [PATCH 1/2] ADFA-5337 chore(build): build without google-services.json (conditional Firebase + analytics off) A fresh clone can't build: the com.google.gms.google-services plugin fails when google-services.json is missing, and that file holds the real Firebase keys so it is gitignored (not in the repo). Apply the plugin only when the file is present, and flag AnalyticsClient off when it isn't, so a contributor gets a working APK with analytics compiled out while release builds drop in the real file to enable Firebase. - controller/app/build.gradle: move google-services out of the plugins {} block and apply it conditionally at the bottom (file("google-services.json").exists()); expose BuildConfig.ANALYTICS_ENABLED from the same check. - AnalyticsClient: gate() and applyConsent() short-circuit when ANALYTICS_ENABLED is false, so no path touches the uninitialized Firebase SDK (Firebase is referenced only in this class). A harmless FirebaseInitProvider warning in logcat is expected when building without the file; the app does not crash. No behavior change for release builds (file present -> plugin applied, analytics on). --- controller/app/build.gradle | 24 +++++++++++++++++-- .../controller/analytics/AnalyticsClient.java | 11 ++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/controller/app/build.gradle b/controller/app/build.gradle index ba1c7736d..ab3e9daba 100644 --- a/controller/app/build.gradle +++ b/controller/app/build.gradle @@ -1,7 +1,8 @@ plugins { id 'com.android.application' - // ADFA-4466 Phase 1: Firebase Analytics. - id 'com.google.gms.google-services' + // ADFA-4466 Phase 1: Firebase Analytics. The google-services plugin fails the build when + // google-services.json is absent, so it is applied CONDITIONALLY at the bottom of this file + // (present -> Firebase on; absent -> AnalyticsClient compiles out and the build still works). } // 1. AUTO-VERSIONING MAP (Must be defined outside the 'android' block) @@ -24,6 +25,12 @@ def gitShortSha = { } ext.gitSha = gitShortSha() +// Firebase is OPTIONAL for local/contributor builds. google-services.json holds the real project keys +// and is gitignored, so a fresh clone doesn't have it. When it's absent we skip the google-services +// plugin (which would otherwise fail the build) and flag AnalyticsClient off via BuildConfig so it never +// touches the uninitialized Firebase SDK. Set the file to enable analytics for a release build. +def hasGoogleServices = file("google-services.json").exists() + android { // 2. Identity namespace 'org.iiab.controller' @@ -45,6 +52,8 @@ android { : (project.ext.gitSha ? "-${project.ext.gitSha}" : "") versionName "v0.7.0-beta${ciSuffix}" buildConfigField "String", "GIT_SHA", "\"${project.ext.gitSha}\"" + // True only when google-services.json is present, so AnalyticsClient can compile out cleanly. + buildConfigField "boolean", "ANALYTICS_ENABLED", "${hasGoogleServices}" setProperty("archivesBaseName", "$applicationId-$versionName") @@ -753,3 +762,14 @@ task refreshKolibriCatalog { } } } + +// ADFA-4466 / build resilience: apply the Firebase google-services plugin only when its config file is +// present. Without it the plugin fails the build; skipping it lets contributors build a working APK with +// analytics compiled out (BuildConfig.ANALYTICS_ENABLED = false). Applied at the bottom, as the plugin +// expects, so it processes the already-configured android block. +if (hasGoogleServices) { + apply plugin: 'com.google.gms.google-services' + logger.lifecycle("K2Go: google-services.json found — Firebase Analytics enabled.") +} else { + logger.lifecycle("K2Go: google-services.json not found — building WITHOUT Firebase (analytics disabled).") +} diff --git a/controller/app/src/main/java/org/iiab/controller/analytics/AnalyticsClient.java b/controller/app/src/main/java/org/iiab/controller/analytics/AnalyticsClient.java index b9068bfea..c348ce753 100644 --- a/controller/app/src/main/java/org/iiab/controller/analytics/AnalyticsClient.java +++ b/controller/app/src/main/java/org/iiab/controller/analytics/AnalyticsClient.java @@ -6,6 +6,7 @@ import com.google.firebase.analytics.FirebaseAnalytics; +import org.iiab.controller.BuildConfig; import org.iiab.controller.analytics.domain.AnalyticsBuckets; import org.iiab.controller.delivery.data.AnalyticsConsent; import org.iiab.controller.delivery.data.InstallId; @@ -48,6 +49,9 @@ public static AnalyticsClient with(Context ctx) { * anywhere (e.g. Application start, or right after the consent toggle changes). */ public void applyConsent() { + if (!BuildConfig.ANALYTICS_ENABLED) { + return; // built without google-services.json → Firebase not configured; nothing to sync + } FirebaseAnalytics.getInstance(app).setAnalyticsCollectionEnabled(AnalyticsConsent.isEnabled(app)); } @@ -179,8 +183,13 @@ public void logModuleInstall(String module, boolean success) { // ------------------------------------------------------------------- internals - /** True only when the operator opted in; also keeps the SDK flag in sync. */ + /** True only when the operator opted in AND Firebase is configured in this build; also keeps the SDK + * flag in sync. When built without google-services.json, this short-circuits so no code path ever + * touches the uninitialized Firebase SDK (every public logging method gates through here). */ private boolean gate() { + if (!BuildConfig.ANALYTICS_ENABLED) { + return false; // no google-services.json → analytics compiled out + } boolean consent = AnalyticsConsent.isEnabled(app); FirebaseAnalytics.getInstance(app).setAnalyticsCollectionEnabled(consent); return consent; From b88112cfecb6e5bf934a1bf4bdfebffbb7a698ef Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 28 Aug 2026 13:55:56 -0600 Subject: [PATCH 2/2] ADFA-5337 chore(ui): hide the analytics opt-in UI when analytics is compiled out Follow-up to the conditional Firebase build: when built without google-services.json (BuildConfig.ANALYTICS_ENABLED = false) there is nothing to share, so the opt-in UI should not appear. - SettingsSubFragment: the "Share usage statistics" toggle is not added. - AboutFragment (+ fragment_about.xml): the "Share anonymous data" row + description are GONE (ids added to hide them). - WizardActivity / SetupSectionFragment: the first-run analytics enrollment prompt is skipped and the flow proceeds. No change when analytics is enabled (file present): the UI shows exactly as before. --- .../iiab/controller/SetupSectionFragment.java | 5 ++++ .../redesign/SettingsSubFragment.java | 12 ++++++---- .../controller/redesign/WizardActivity.java | 2 ++ .../controller/settings/AboutFragment.java | 23 +++++++++++++------ .../src/main/res/layout/fragment_about.xml | 6 +++-- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java b/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java index 9e13571cf..6f108dcb8 100644 --- a/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java @@ -206,6 +206,11 @@ private void persistSelectedLanguage() { * the consent and proceeds. Shown at most once (guarded by a flag). */ private void maybeShowAnalyticsEnrollment() { + // ADFA-5337: no analytics compiled in (no google-services.json) → nothing to consent to; skip. + if (!BuildConfig.ANALYTICS_ENABLED) { + completeSetup(); + return; + } SharedPreferences delivery = requireContext().getSharedPreferences( DELIVERY_PREFS, Context.MODE_PRIVATE); if (delivery.getBoolean(KEY_ENROLLMENT_SHOWN, false)) { diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/SettingsSubFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/SettingsSubFragment.java index 32cd21dd2..8e8099723 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/SettingsSubFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/SettingsSubFragment.java @@ -190,10 +190,14 @@ private void buildAbout(Context ctx, LinearLayout list) { } }); SettingsUi.row(ctx, list, getString(R.string.k2go_settings_permissions), null, null, v -> openAppSettings(ctx)); - SettingsUi.toggle(ctx, list, getString(R.string.k2go_settings_usage_stats), AnalyticsConsent.isEnabled(ctx), checked -> { - AnalyticsConsent.setEnabled(ctx, checked); - org.iiab.controller.analytics.AnalyticsClient.with(ctx).applyConsent(); - }); + // ADFA-5337: hide the usage-statistics toggle when analytics is compiled out (no + // google-services.json), since there's nothing to share and the switch would do nothing. + if (org.iiab.controller.BuildConfig.ANALYTICS_ENABLED) { + SettingsUi.toggle(ctx, list, getString(R.string.k2go_settings_usage_stats), AnalyticsConsent.isEnabled(ctx), checked -> { + AnalyticsConsent.setEnabled(ctx, checked); + org.iiab.controller.analytics.AnalyticsClient.with(ctx).applyConsent(); + }); + } SettingsUi.preview(ctx, list, getString(R.string.k2go_settings_licenses), null); SettingsUi.preview(ctx, list, getString(R.string.k2go_settings_privacy), null); } diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java index be6c24c95..dcd133300 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java @@ -198,6 +198,8 @@ private void onPrimary() { /** One-time usage-stats consent prompt; runs {@code onDone} after the choice (or immediately * if already asked here or in the legacy flow). Reuses the analytics_enroll_* strings. */ private void maybeAskAnalytics(Runnable onDone) { + // ADFA-5337: no analytics compiled in (no google-services.json) → nothing to consent to; skip. + if (!org.iiab.controller.BuildConfig.ANALYTICS_ENABLED) { onDone.run(); return; } if (AnalyticsConsent.wasAsked(this)) { onDone.run(); return; } new MaterialAlertDialogBuilder(this) .setTitle(R.string.analytics_enroll_title) diff --git a/controller/app/src/main/java/org/iiab/controller/settings/AboutFragment.java b/controller/app/src/main/java/org/iiab/controller/settings/AboutFragment.java index c7295cad9..b0b79e452 100644 --- a/controller/app/src/main/java/org/iiab/controller/settings/AboutFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/settings/AboutFragment.java @@ -33,13 +33,22 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat TextView versionView = view.findViewById(R.id.about_version); versionView.setText(getString(R.string.about_version, appVersionName())); - SwitchCompat analytics = view.findViewById(R.id.switch_analytics_consent); - analytics.setChecked(AnalyticsConsent.isEnabled(requireContext())); - analytics.setOnCheckedChangeListener( - (btn, checked) -> { - AnalyticsConsent.setEnabled(requireContext(), checked); - org.iiab.controller.analytics.AnalyticsClient.with(requireContext()).applyConsent(); - }); + // ADFA-5337: no analytics compiled in (no google-services.json) → hide the whole opt-in block, + // since there's nothing to share and the switch would do nothing. + if (!org.iiab.controller.BuildConfig.ANALYTICS_ENABLED) { + View row = view.findViewById(R.id.analytics_consent_row); + View desc = view.findViewById(R.id.analytics_consent_desc); + if (row != null) row.setVisibility(View.GONE); + if (desc != null) desc.setVisibility(View.GONE); + } else { + SwitchCompat analytics = view.findViewById(R.id.switch_analytics_consent); + analytics.setChecked(AnalyticsConsent.isEnabled(requireContext())); + analytics.setOnCheckedChangeListener( + (btn, checked) -> { + AnalyticsConsent.setEnabled(requireContext(), checked); + org.iiab.controller.analytics.AnalyticsClient.with(requireContext()).applyConsent(); + }); + } } private String appVersionName() { diff --git a/controller/app/src/main/res/layout/fragment_about.xml b/controller/app/src/main/res/layout/fragment_about.xml index 9ddef7497..3a47432d6 100644 --- a/controller/app/src/main/res/layout/fragment_about.xml +++ b/controller/app/src/main/res/layout/fragment_about.xml @@ -21,7 +21,8 @@ - -