Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions controller/app/build.gradle
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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'
Expand All @@ -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")

Expand Down Expand Up @@ -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).")
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
6 changes: 4 additions & 2 deletions controller/app/src/main/res/layout/fragment_about.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
<View android:layout_width="match_parent" android:layout_height="1dp"
android:layout_marginTop="24dp" android:background="@color/chrome_ripple"/>

<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
<LinearLayout android:id="@+id/analytics_consent_row"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:gravity="center_vertical"
android:layout_marginTop="20dp">
<TextView android:layout_width="0dp" android:layout_height="wrap_content"
Expand All @@ -32,7 +33,8 @@
android:id="@+id/switch_analytics_consent"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content"
<TextView android:id="@+id/analytics_consent_desc"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/settings_analytics_desc"
android:textSize="13sp"/>
Expand Down
Loading