diff --git a/android/app/src/main/java/com/naviapp/app/initializers/AppLifecycleManagerInitializer.kt b/android/app/src/main/java/com/naviapp/app/initializers/AppLifecycleManagerInitializer.kt index 7549fc53e6..6443ed6e47 100644 --- a/android/app/src/main/java/com/naviapp/app/initializers/AppLifecycleManagerInitializer.kt +++ b/android/app/src/main/java/com/naviapp/app/initializers/AppLifecycleManagerInitializer.kt @@ -8,7 +8,6 @@ package com.naviapp.app.initializers import android.app.Activity -import android.app.Application import android.os.Build import android.os.Bundle import android.view.WindowManager @@ -18,6 +17,7 @@ import com.navi.base.sharedpref.PreferenceManager import com.navi.base.utils.AppLaunchUtils import com.navi.base.utils.isNull import com.navi.chat.base.ChatBaseActivity +import com.navi.common.NaviActivityLifecycleCallbacks import com.navi.common.checkmate.core.CheckMateManager import com.navi.common.checkmate.model.SessionDetails import com.navi.common.checkmate.utils.getCurrentSessionMetrics @@ -55,7 +55,7 @@ constructor(private val alfredInitializer: AlfredFacade) : ComponentInitializer } private val activityLifecycleCallback = - object : Application.ActivityLifecycleCallbacks { + object : NaviActivityLifecycleCallbacks() { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { if (activity is HomePageActivity) { @@ -175,9 +175,5 @@ constructor(private val alfredInitializer: AlfredFacade) : ComponentInitializer ) } } - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} - - override fun onActivityDestroyed(activity: Activity) {} } } diff --git a/android/app/src/main/java/com/naviapp/app/initializers/SignalManagerInitializer.kt b/android/app/src/main/java/com/naviapp/app/initializers/SignalManagerInitializer.kt new file mode 100644 index 0000000000..036ed57fcc --- /dev/null +++ b/android/app/src/main/java/com/naviapp/app/initializers/SignalManagerInitializer.kt @@ -0,0 +1,54 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.naviapp.app.initializers + +import android.app.Activity +import com.navi.common.NaviActivityLifecycleCallbacks +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper +import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper.SIGNAL_INFO_TRACKING_DISABLED +import com.navi.common.network.SignalStrengthManager +import com.naviapp.app.NaviApplication +import dagger.Lazy +import javax.inject.Inject + +class SignalManagerInitializer @Inject constructor() : ComponentInitializer { + + @Inject lateinit var signalStrengthManager: Lazy + private var isInitialized = false + private var foregroundActivitiesCount = 0 + private val isSignalInfoTrackingDisabled: Boolean by lazy { + FirebaseRemoteConfigHelper.getBoolean(SIGNAL_INFO_TRACKING_DISABLED) + } + + override fun initialize(application: NaviApplication) { + application.registerActivityLifecycleCallbacks(activityLifecycleCallback) + } + + private val activityLifecycleCallback = + object : NaviActivityLifecycleCallbacks() { + override fun onActivityStarted(activity: Activity) { + foregroundActivitiesCount++ + + // attach listener + if (!isInitialized && !isSignalInfoTrackingDisabled) { + isInitialized = true + signalStrengthManager.get().startTracking() + } + } + + override fun onActivityStopped(activity: Activity) { + foregroundActivitiesCount-- + + // remove listener + if (foregroundActivitiesCount == 0 && !isSignalInfoTrackingDisabled) { + isInitialized = false + signalStrengthManager.get().stopTracking() + } + } + } +} diff --git a/android/app/src/main/java/com/naviapp/common/di/AppBindingModule.kt b/android/app/src/main/java/com/naviapp/common/di/AppBindingModule.kt index 1e7c26b8fc..bfec210c04 100644 --- a/android/app/src/main/java/com/naviapp/common/di/AppBindingModule.kt +++ b/android/app/src/main/java/com/naviapp/common/di/AppBindingModule.kt @@ -1,6 +1,6 @@ /* * - * * Copyright © 2024 by Navi Technologies Limited + * * Copyright © 2024-2025 by Navi Technologies Limited * * All rights reserved. Strictly confidential * */ @@ -8,6 +8,8 @@ package com.naviapp.common.di import com.navi.common.dbcleaner.ModuleDatabaseCleanerExecutor +import com.navi.common.network.SignalStrengthManager +import com.navi.common.network.SignalStrengthManagerImpl import com.naviapp.app.db.NaviModuleDatabaseCleanerExecutor import com.naviapp.app.image.CoilImageLibraryHandler import com.naviapp.app.image.ImageLibraryHandler @@ -32,4 +34,9 @@ interface AppBindingModule { fun bindModuleDatabaseCleanerDelegate( naviModuleDatabaseCleanerDelegate: NaviModuleDatabaseCleanerExecutor ): ModuleDatabaseCleanerExecutor + + @Binds + fun bindSignalStrengthHelper( + signalStrengthHelperImpl: SignalStrengthManagerImpl + ): SignalStrengthManager } diff --git a/android/app/src/main/java/com/naviapp/common/di/AppModule.kt b/android/app/src/main/java/com/naviapp/common/di/AppModule.kt index 9be34bae49..b7a4849700 100644 --- a/android/app/src/main/java/com/naviapp/common/di/AppModule.kt +++ b/android/app/src/main/java/com/naviapp/common/di/AppModule.kt @@ -16,6 +16,7 @@ import com.naviapp.app.initializers.ComponentInitializer import com.naviapp.app.initializers.CrashHandlerInitializer import com.naviapp.app.initializers.NetworkConfigurationInitializer import com.naviapp.app.initializers.SdkInitializer +import com.naviapp.app.initializers.SignalManagerInitializer import com.naviapp.network.di.CoroutineScopeIO import dagger.Module import dagger.Provides @@ -50,6 +51,7 @@ object AppModule { crashHandlerInitializer: CrashHandlerInitializer, appLifecycleManagerInitializer: AppLifecycleManagerInitializer, networkConfigurationInitializer: NetworkConfigurationInitializer, + signalManagerInitializer: SignalManagerInitializer, ): List { return listOf( sdkInitializer, @@ -57,6 +59,7 @@ object AppModule { crashHandlerInitializer, appLifecycleManagerInitializer, networkConfigurationInitializer, + signalManagerInitializer, ) } } diff --git a/android/navi-analytics/src/main/java/com/navi/analytics/model/SignalInfo.kt b/android/navi-analytics/src/main/java/com/navi/analytics/model/SignalInfo.kt new file mode 100644 index 0000000000..26aab1aebe --- /dev/null +++ b/android/navi-analytics/src/main/java/com/navi/analytics/model/SignalInfo.kt @@ -0,0 +1,16 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.analytics.model + +data class SignalInfo(val level: Int, val type: SignalType) + +enum class SignalType { + WIFI, + CELLULAR, + UNKNOWN, +} diff --git a/android/navi-analytics/src/main/java/com/navi/analytics/utils/NaviTrackEvent.kt b/android/navi-analytics/src/main/java/com/navi/analytics/utils/NaviTrackEvent.kt index 93c07ea94d..93d0a740bb 100644 --- a/android/navi-analytics/src/main/java/com/navi/analytics/utils/NaviTrackEvent.kt +++ b/android/navi-analytics/src/main/java/com/navi/analytics/utils/NaviTrackEvent.kt @@ -17,6 +17,8 @@ import com.navi.analytics.BuildConfig import com.navi.analytics.appsflyer.AppsFlyerUtil import com.navi.analytics.firebase.FcmAnalyticsUtil import com.navi.analytics.model.AnalyticsConfiguration +import com.navi.analytics.model.SignalInfo +import com.navi.analytics.model.SignalType import com.navi.analytics.utils.NaviAnalyticsHelper.addBasePropertiesToScreenLandEvent import com.navi.analytics.utils.NaviAnalyticsHelper.isEventWhiteListedForAppsflyer import com.navi.base.model.AnalyticsEvent @@ -35,6 +37,7 @@ object NaviTrackEvent { var currentScreenName: String? = null var foregroundScreen: String? = null var foregroundVertical: String? = null + var signalInfo: SignalInfo = SignalInfo(level = -1, type = SignalType.UNKNOWN) fun appInit(appContext: Application, analyticsConfiguration: AnalyticsConfiguration) { applicationContext = appContext diff --git a/android/navi-common/src/main/java/com/navi/common/NaviActivityLifecycleCallbacks.kt b/android/navi-common/src/main/java/com/navi/common/NaviActivityLifecycleCallbacks.kt new file mode 100644 index 0000000000..184ed4a146 --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/NaviActivityLifecycleCallbacks.kt @@ -0,0 +1,28 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.common + +import android.app.Activity +import android.app.Application +import android.os.Bundle + +abstract class NaviActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} + + override fun onActivityStarted(activity: Activity) {} + + override fun onActivityResumed(activity: Activity) {} + + override fun onActivityPaused(activity: Activity) {} + + override fun onActivityStopped(activity: Activity) {} + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + + override fun onActivityDestroyed(activity: Activity) {} +} diff --git a/android/navi-common/src/main/java/com/navi/common/checkmate/core/CheckMateManager.kt b/android/navi-common/src/main/java/com/navi/common/checkmate/core/CheckMateManager.kt index fabd1a60b7..38012c90c6 100644 --- a/android/navi-common/src/main/java/com/navi/common/checkmate/core/CheckMateManager.kt +++ b/android/navi-common/src/main/java/com/navi/common/checkmate/core/CheckMateManager.kt @@ -81,6 +81,8 @@ object CheckMateManager { "errorCode" to errorCode, "exception" to exception, "alfredSessionId" to AlfredManager.getAlfredSessionId(), + "signalLevel" to NaviTrackEvent.signalInfo.level.toString(), + "signalType" to NaviTrackEvent.signalInfo.type.name, "isNae" to getIsNae(isNae = isNae, statusCode = statusCode, exception = exception) .toString(), 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 00129e37aa..932e3cee54 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 @@ -83,6 +83,7 @@ object FirebaseRemoteConfigHelper { const val PAN_VERIFICATION_HPC_PAN_SCREEN_NAME = "PAN_VERIFICATION_HPC_PAN_SCREEN_NAME" const val ONE_TIME_DATA_UPLOAD_DISABLED = "ONE_TIME_DATA_UPLOAD_DISABLED" const val ENABLE_UI_TRON_MODIFIER_BUILDER = "ENABLE_UI_TRON_MODIFIER_BUILDER" + const val SIGNAL_INFO_TRACKING_DISABLED = "SIGNAL_INFO_TRACKING_DISABLED" // NAVI_PAY const val NAVI_PAY_INTENT_VPA = "NAVI_PAY_INTENT_VPA" diff --git a/android/navi-common/src/main/java/com/navi/common/managers/NaviLocationManager.kt b/android/navi-common/src/main/java/com/navi/common/managers/NaviLocationManager.kt index fb45275e9f..791e150af3 100644 --- a/android/navi-common/src/main/java/com/navi/common/managers/NaviLocationManager.kt +++ b/android/navi-common/src/main/java/com/navi/common/managers/NaviLocationManager.kt @@ -10,13 +10,11 @@ package com.navi.common.managers import android.Manifest import android.annotation.SuppressLint import android.app.Activity -import android.app.Application import android.content.Context import android.location.Geocoder import android.location.Location.distanceBetween import android.location.LocationManager import android.os.Build -import android.os.Bundle import android.os.Looper import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -47,6 +45,7 @@ import com.navi.base.utils.orElse import com.navi.base.utils.orFalse import com.navi.base.utils.orZero import com.navi.base.utils.toDoubleWithSafe +import com.navi.common.NaviActivityLifecycleCallbacks import com.navi.common.R import com.navi.common.checkmate.model.MetricInfo import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper @@ -68,7 +67,7 @@ import timber.log.Timber class NaviLocationManager( val applicationContext: Context? = AppServiceManager.application, val forceUpdateLocation: Boolean = false, -) : Application.ActivityLifecycleCallbacks { +) : NaviActivityLifecycleCallbacks() { private val fusedLocationClient = applicationContext?.let { LocationServices.getFusedLocationProviderClient(it) } private var isLocationNeedToPost: Boolean = false @@ -347,8 +346,6 @@ class NaviLocationManager( } } - override fun onActivityStarted(activity: Activity) {} - override fun onActivityDestroyed(activity: Activity) { try { fusedLocationClient?.removeLocationUpdates(locationCallback) @@ -357,14 +354,6 @@ class NaviLocationManager( } } - override fun onActivityPaused(activity: Activity) {} - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} - - override fun onActivityStopped(activity: Activity) {} - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} - fun postLocationData(activity: Activity) { if (isLocationNeedToUpdate()) { analyticsTracker.onRequestAndSendLocation() diff --git a/android/navi-common/src/main/java/com/navi/common/network/SignalStrengthManager.kt b/android/navi-common/src/main/java/com/navi/common/network/SignalStrengthManager.kt new file mode 100644 index 0000000000..34d3f5b05e --- /dev/null +++ b/android/navi-common/src/main/java/com/navi/common/network/SignalStrengthManager.kt @@ -0,0 +1,181 @@ +/* + * + * * Copyright © 2024-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.common.network + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.wifi.WifiManager +import android.telephony.PhoneStateListener +import android.telephony.SignalStrength +import android.telephony.TelephonyManager +import com.navi.analytics.model.SignalInfo +import com.navi.analytics.model.SignalType +import com.navi.analytics.utils.NaviTrackEvent +import com.navi.base.utils.orZero +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +interface SignalStrengthManager { + fun startTracking() + + fun stopTracking() +} + +class SignalStrengthManagerImpl +@Inject +constructor(@ApplicationContext private val context: Context) : SignalStrengthManager { + + private val connectivityManager: ConnectivityManager by lazy { + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + } + + private val telephonyManager: TelephonyManager by lazy { + context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + } + + private val wifiSignalStrengthReceiver: BroadcastReceiver by lazy { + object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == WifiManager.RSSI_CHANGED_ACTION) { + getWifiSignalStrength() + } + } + } + } + + private val cellularSignalStrengthListener: PhoneStateListener by lazy { + object : PhoneStateListener() { + override fun onSignalStrengthsChanged(signalStrength: SignalStrength?) { + super.onSignalStrengthsChanged(signalStrength) + if (isCellularNetworkActive()) { + signalStrength?.let { + NaviTrackEvent.signalInfo = + SignalInfo( + level = signalStrength.level.orZero(), + type = SignalType.CELLULAR, + ) + } + } + } + } + } + + private val networkCallback: ConnectivityManager.NetworkCallback by lazy { + object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + val networkCapabilities = connectivityManager.getNetworkCapabilities(network) + if (networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) { + if (isWifiNetworkActive()) { + getWifiSignalStrength() + } + } else if ( + networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == + true + ) { + if (isCellularNetworkActive()) { + startTrackingCellularSignalStrength() + } + } + } + + override fun onLost(network: Network) { + resetSignalStrengthInfo() + } + + override fun onUnavailable() { + resetSignalStrengthInfo() + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + resetSignalStrengthInfo() + } + } + } + + private fun resetSignalStrengthInfo() { + NaviTrackEvent.signalInfo = SignalInfo(level = -1, type = SignalType.UNKNOWN) + } + + override fun startTracking() { + startTrackingWifiSignalStrength() + startTrackingCellularSignalStrength() + monitorActiveNetworkType() + } + + override fun stopTracking() { + stopTrackingWifiSignalStrength() + stopTrackingCellularSignalStrength() + unregisterNetworkCallback() + } + + private fun startTrackingWifiSignalStrength() { + val intentFilter = IntentFilter(WifiManager.RSSI_CHANGED_ACTION) + context.registerReceiver(wifiSignalStrengthReceiver, intentFilter) + } + + private fun stopTrackingWifiSignalStrength() { + context.unregisterReceiver(wifiSignalStrengthReceiver) + } + + private fun startTrackingCellularSignalStrength() { + telephonyManager.listen( + cellularSignalStrengthListener, + PhoneStateListener.LISTEN_SIGNAL_STRENGTHS, + ) + } + + private fun stopTrackingCellularSignalStrength() { + telephonyManager.listen(cellularSignalStrengthListener, PhoneStateListener.LISTEN_NONE) + } + + private fun monitorActiveNetworkType() { + connectivityManager.registerDefaultNetworkCallback(networkCallback) + } + + private fun unregisterNetworkCallback() { + connectivityManager.unregisterNetworkCallback(networkCallback) + } + + private fun getWifiSignalStrength() { + val wifiManager = + context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + val wifiInfo = wifiManager.connectionInfo + val rssi = wifiInfo.rssi + + val level = mapRssiToSignalLevel(rssi) + + if (isWifiNetworkActive()) { + NaviTrackEvent.signalInfo = SignalInfo(level = level, type = SignalType.WIFI) + } + } + + private fun mapRssiToSignalLevel(rssi: Int): Int { + return when { + rssi >= -50 -> 4 + rssi >= -60 -> 3 + rssi >= -70 -> 2 + rssi >= -80 -> 1 + else -> 0 + } + } + + private fun isCellularNetworkActive(): Boolean { + val activeNetwork = connectivityManager.activeNetworkInfo + return activeNetwork?.type == ConnectivityManager.TYPE_MOBILE && activeNetwork.isConnected + } + + private fun isWifiNetworkActive(): Boolean { + val activeNetwork = connectivityManager.activeNetworkInfo + return activeNetwork?.type == ConnectivityManager.TYPE_WIFI && activeNetwork.isConnected + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/location/NaviLocationManager.kt b/android/navi-insurance/src/main/java/com/navi/insurance/location/NaviLocationManager.kt index 1a0e5be7bc..4da3c52744 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/location/NaviLocationManager.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/location/NaviLocationManager.kt @@ -16,13 +16,11 @@ package com.navi.insurance.location import android.Manifest import android.annotation.SuppressLint import android.app.Activity -import android.app.Application import android.content.Context import android.content.pm.PackageManager import android.location.Address import android.location.Geocoder import android.location.LocationManager -import android.os.Bundle import android.os.Handler import android.os.Looper import androidx.core.app.ActivityCompat @@ -35,6 +33,7 @@ import com.navi.base.AppServiceManager import com.navi.base.utils.orFalse import com.navi.base.utils.orZero import com.navi.base.utils.toDoubleWithSafe +import com.navi.common.NaviActivityLifecycleCallbacks import com.navi.insurance.analytics.InsuranceAnalyticsConstants import com.navi.insurance.analytics.NaviInsuranceAnalytics import com.navi.insurance.models.UserLocation @@ -42,7 +41,7 @@ import com.navi.insurance.sharedpref.NaviPreferenceManager import com.navi.insurance.util.* import kotlinx.coroutines.* -class NaviLocationManager(private val context: Context) : Application.ActivityLifecycleCallbacks { +class NaviLocationManager(private val context: Context) : NaviActivityLifecycleCallbacks() { private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) private var isLocationNeedToPost: Boolean = false private var locationUpdateCallback: ((userLocation: UserLocation) -> Unit)? = null @@ -390,8 +389,6 @@ class NaviLocationManager(private val context: Context) : Application.ActivityLi } } - override fun onActivityStarted(activity: Activity) {} - override fun onActivityDestroyed(activity: Activity) { try { fusedLocationClient.removeLocationUpdates(locationCallback) @@ -400,14 +397,6 @@ class NaviLocationManager(private val context: Context) : Application.ActivityLi } } - override fun onActivityPaused(activity: Activity) {} - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} - - override fun onActivityStopped(activity: Activity) {} - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} - fun postLocationData(activity: Activity) { if (isLocationNeedToUpdate()) requestLocationUpdates(activity, true) }