From 6a6116f8f68e685c1dc6b088fcc6df931f492895 Mon Sep 17 00:00:00 2001 From: Mohit Rajput Date: Fri, 27 Jun 2025 02:59:21 -0700 Subject: [PATCH] NTP-75213 | bbps transsion db crash fix experiment (#16755) --- .../com/navi/bbps/common/NaviBbpsAnalytics.kt | 7 + .../com/navi/bbps/db/TranssionDbExperiment.kt | 202 ++++++++++++++++++ .../com/navi/bbps/db/di/NaviBbpsDbModule.kt | 11 +- .../FirebaseRemoteConfigHelper.kt | 5 +- 4 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 android/navi-bbps/src/main/kotlin/com/navi/bbps/db/TranssionDbExperiment.kt diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt index ad29cbb615..5ad8b43a93 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/NaviBbpsAnalytics.kt @@ -56,6 +56,13 @@ class NaviBbpsAnalytics private constructor() { eventValues = mapOf("errorConfig" to errorConfig.toString()), ) } + + fun onTranssionDeviceData(eventValues: Map) { + NaviTrackEvent.trackEventOnClickStream( + eventName = "Dev_NaviBBPS_TranssionCrashFixExperiment", + eventValues = eventValues, + ) + } } inner class BillCategories { diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/TranssionDbExperiment.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/TranssionDbExperiment.kt new file mode 100644 index 0000000000..721fd55b09 --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/TranssionDbExperiment.kt @@ -0,0 +1,202 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.db + +import android.os.Build +import androidx.room.RoomDatabase +import androidx.sqlite.db.SupportSQLiteDatabase +import com.navi.analytics.firebase.FcmAnalyticsUtil +import com.navi.bbps.common.NaviBbpsAnalytics +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.NAVI_BBPS_TRANSSION_DB_CRASH_FIX_ENABLED +import java.util.concurrent.Executors + +/** + * A/B Test configuration for Transsion phone database compatibility fixes + * + * This experiment tests enhanced SQLite settings specifically for Transsion devices to resolve + * SQLiteCantOpenDatabaseException crashes. + * + * Test Groups: + * - CONTROL: Current default Room database settings + * - EXPERIMENT: Enhanced compatibility with PRAGMA settings and TRUNCATE journal mode + */ +class TranssionDbExperiment { + + private val analytics = NaviBbpsAnalytics.INSTANCE.GenericDev() + + /** + * Applies database configuration based on A/B test group + * + * @param builder Room database builder to configure + */ + fun applyDbFixes(builder: RoomDatabase.Builder<*>) { + if (!isTranssionDevice()) { + // Non-Transsion devices: no changes + return + } + + val isExperimentEnabled = + FirebaseRemoteConfigHelper.getBoolean(NAVI_BBPS_TRANSSION_DB_CRASH_FIX_ENABLED) + + FcmAnalyticsUtil.analytics.trackEvent( + eventName = "TranssionDbExperiment init", + eventValues = + mapOf("isTranssionDbFixExperimentEnabled" to isExperimentEnabled.toString()), + ) + if (isExperimentEnabled) { + applyExperimentalFixes(builder) + } else { + applyControlGroupLogging(builder) + } + } + + private fun applyExperimentalFixes(builder: RoomDatabase.Builder<*>) { + logExperimentMetrics( + group = "EXPERIMENT", + event = "configuration_started", + success = true, + errorMessage = "Applying Transsion DB fixes", + ) + + builder.apply { + setJournalMode(RoomDatabase.JournalMode.TRUNCATE) + + addCallback( + object : RoomDatabase.Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + logExperimentMetrics( + group = "EXPERIMENT", + event = "database_created", + success = true, + errorMessage = "Database onCreate for ${Build.MODEL}", + ) + + try { + db.execSQL("PRAGMA journal_mode=DELETE") + db.execSQL("PRAGMA synchronous=NORMAL") + logExperimentMetrics( + group = "EXPERIMENT", + event = "pragma_applied", + success = true, + errorMessage = "PRAGMA settings applied successfully", + ) + } catch (e: Exception) { + logExperimentMetrics( + group = "EXPERIMENT", + event = "pragma_failed", + success = false, + errorMessage = e.message, + ) + } + } + + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + logExperimentMetrics( + group = "EXPERIMENT", + event = "database_opened", + success = true, + errorMessage = "Database onOpen for ${Build.MODEL}", + ) + + try { + db.execSQL("PRAGMA integrity_check") + logExperimentMetrics( + group = "EXPERIMENT", + event = "integrity_check_passed", + success = true, + errorMessage = "Database integrity check passed", + ) + } catch (e: Exception) { + logExperimentMetrics( + group = "EXPERIMENT", + event = "integrity_check_failed", + success = false, + errorMessage = e.message, + ) + } + } + } + ) + } + } + + private fun applyControlGroupLogging(builder: RoomDatabase.Builder<*>) { + logExperimentMetrics( + group = "CONTROL", + event = "configuration_started", + success = true, + errorMessage = "Using default DB settings", + ) + + builder.apply { + setQueryCallback( + { sqlQuery, bindArgs -> + logExperimentMetrics( + group = "CONTROL", + event = "query_executed", + success = true, + errorMessage = "Query: $sqlQuery, args: ${bindArgs.joinToString()}", + ) + }, + Executors.newSingleThreadExecutor(), + ) + + addCallback( + object : RoomDatabase.Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + logExperimentMetrics( + group = "CONTROL", + event = "database_created", + success = true, + errorMessage = "Database onCreate for ${Build.MODEL}", + ) + } + + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + logExperimentMetrics( + group = "CONTROL", + event = "database_opened", + success = true, + errorMessage = "Database onOpen for ${Build.MODEL}", + ) + } + } + ) + } + } + + fun logExperimentMetrics( + group: String, + event: String, + success: Boolean, + errorMessage: String? = null, + ) { + val deviceDetails = + "Model: ${Build.MODEL}, Manufacturer: ${Build.MANUFACTURER}, Brand: ${Build.BRAND}, SDK: ${Build.VERSION.SDK_INT}" + + analytics.onTranssionDeviceData( + eventValues = + mapOf( + "device_details" to deviceDetails, + "group" to group, + "event" to event, + "success" to success.toString(), + "error_message" to errorMessage.orEmpty(), + ) + ) + } + + private fun isTranssionDevice(): Boolean = + Build.MANUFACTURER.equals("TRANSSION", ignoreCase = true) || + Build.BRAND.equals("TRANSSION", ignoreCase = true) +} diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt index 58ffcdc47b..6122757dd5 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/db/di/NaviBbpsDbModule.kt @@ -22,6 +22,7 @@ import com.navi.bbps.db.NAVI_BBPS_DATABASE_MIGRATION_7_8 import com.navi.bbps.db.NAVI_BBPS_DATABASE_MIGRATION_8_9 import com.navi.bbps.db.NAVI_BBPS_DATABASE_MIGRATION_9_10 import com.navi.bbps.db.NaviBbpsAppDatabase +import com.navi.bbps.db.TranssionDbExperiment import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -34,15 +35,21 @@ import javax.inject.Singleton object NaviBbpsDbModule { @Singleton @Provides fun providesSharedPreferences() = BbpsSharedPreferences() + @Singleton @Provides fun providesTranssionDbExperiment() = TranssionDbExperiment() + @Singleton @Provides - fun providesNaviBbpsAppDatabase(@ApplicationContext context: Context) = + fun providesNaviBbpsAppDatabase( + @ApplicationContext context: Context, + transsionDbExperiment: TranssionDbExperiment, + ) = Room.databaseBuilder( context, NaviBbpsAppDatabase::class.java, NaviBbpsAppDatabase.NAVI_BBPS_DATABASE_NAME, ) .enableMultiInstanceInvalidation() + .apply { transsionDbExperiment.applyDbFixes(this) } .addMigrations( NAVI_BBPS_DATABASE_MIGRATION_1_2, NAVI_BBPS_DATABASE_MIGRATION_2_3, @@ -56,7 +63,7 @@ object NaviBbpsDbModule { NAVI_BBPS_DATABASE_MIGRATION_10_11, NAVI_BBPS_DATABASE_MIGRATION_11_12, ) - .fallbackToDestructiveMigration() + .fallbackToDestructiveMigration(dropAllTables = false) .build() @Singleton diff --git a/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt b/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt index 5a8fa563e8..c80ad71037 100644 --- a/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt +++ b/android/navi-common/src/main/java/com/navi/common/firebaseremoteconfig/FirebaseRemoteConfigHelper.kt @@ -197,7 +197,6 @@ object FirebaseRemoteConfigHelper { // BBPS const val NAVI_BBPS_REWARDS_NUDGE_CACHE_TTL_IN_MILLIS = "NAVI_BBPS_REWARDS_NUDGE_CACHE_TTL_IN_MILLIS" - const val NAVI_BBPS_CATEGORIES_CACHE_MILLIS = "NAVI_BBPS_CATEGORIES_CACHE_MILLIS" const val NAVI_BBPS_PREPAID_OPERATOR_CIRCLE_CACHE_KEY = "NAVI_BBPS_PREPAID_OPERATOR_CIRCLE_CACHE_KEY" const val NAVI_BBPS_PREPAID_PLANS_CACHE_MILLIS = "NAVI_BBPS_PREPAID_PLANS_CACHE_MILLIS" @@ -205,7 +204,6 @@ object FirebaseRemoteConfigHelper { "NAVI_BBPS_CUSTOM_PREPAID_PLANS_CACHE_TTL_IN_MILLIS" const val NAVI_BBPS_CUSTOM_PREPAID_PLANS_SHIMMER_DURATION_IN_MILLIS = "NAVI_BBPS_CUSTOM_PREPAID_PLANS_SHIMMER_DURATION_IN_MILLIS" - const val NAVI_BBPS_BILLER_LIST_CACHE_MILLIS = "NAVI_BBPS_BILLER_LIST_CACHE_MILLIS" const val NAVI_BBPS_PHONE_SERIES_MAPPING_CACHE_MILLIS = "NAVI_BBPS_PHONE_SERIES_MAPPING_CACHE_MILLIS" @@ -220,14 +218,13 @@ object FirebaseRemoteConfigHelper { const val NAVI_CHECK_BALANCE_CROSS_SELL_AD_FALLBACK_TIMEOUT = "NAVI_CHECK_BALANCE_CROSS_SELL_AD_FALLBACK_TIMEOUT" const val NAVI_BBPS_PPS_CROSS_SELL_AD_RE_ID = "NAVI_BBPS_PPS_CROSS_SELL_AD_RE_ID" - const val NAVI_BBPS_DISMISS_BILL_DURATION = "NAVI_BBPS_DISMISS_BILL_DURATION" const val NAVI_BBPS_PPS_SHARE_RECEIPT_CALLOUT_TEXT = "NAVI_BBPS_PPS_SHARE_RECEIPT_CALLOUT_TEXT" const val NAVI_BBPS_TDS_SHARE_RECEIPT_CALLOUT_TEXT = "NAVI_BBPS_TDS_SHARE_RECEIPT_CALLOUT_TEXT" const val NAVI_BBPS_EXTERNAL_APP_SHARE_CONFIGURABLE_TEXT = "NAVI_BBPS_EXTERNAL_APP_SHARE_CONFIGURABLE_TEXT" const val NAVI_BBPS_BILL_REFRESH_DURATION = "NAVI_BBPS_BILL_REFRESH_DURATION" - const val NAVI_BBPS_OFFER_SHIMMER_TIMEOUT = "NAVI_BBPS_OFFER_SHIMMER_TIMEOUT" + const val NAVI_BBPS_TRANSSION_DB_CRASH_FIX_ENABLED = "NAVI_BBPS_TRANSSION_DB_CRASH_FIX_ENABLED" const val NAVI_IAN_CROSS_SELL_AD_RE_ID = "NAVI_IAN_CROSS_SELL_AD_RE_ID"