@@ -19,31 +19,74 @@ package com.duckduckgo.app.browser.httperrors
1919import android.content.Context
2020import android.content.SharedPreferences
2121import androidx.core.content.edit
22+ import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
2223import com.duckduckgo.app.statistics.pixels.Pixel
24+ import com.duckduckgo.browser.api.WebViewVersionProvider
2325import com.duckduckgo.di.scopes.AppScope
26+ import com.duckduckgo.mobile.android.vpn.network.ExternalVpnDetector
27+ import com.duckduckgo.networkprotection.api.NetworkProtectionState
2428import com.squareup.anvil.annotations.ContributesBinding
2529import java.time.Instant
2630import java.util.concurrent.TimeUnit
2731import javax.inject.Inject
2832
2933interface HttpErrorPixels {
3034 fun updateCountPixel (httpErrorPixelName : HttpErrorPixelName )
35+ suspend fun update5xxCountPixel (httpErrorPixelName : HttpErrorPixelName , statusCode : Int )
3136 fun fireCountPixel (httpErrorPixelName : HttpErrorPixelName )
37+ fun fire5xxCountPixels ()
3238}
3339
3440@ContributesBinding(AppScope ::class )
3541class RealHttpErrorPixels @Inject constructor(
3642 private val pixel : Pixel ,
3743 private val context : Context ,
44+ private val webViewVersionProvider : WebViewVersionProvider ,
45+ private val networkProtectionState : NetworkProtectionState ,
46+ private val externalVpnDetector : ExternalVpnDetector ,
47+ private val androidBrowserConfig : AndroidBrowserConfigFeature ,
3848) : HttpErrorPixels {
3949
4050 private val preferences: SharedPreferences by lazy { context.getSharedPreferences(FILENAME , Context .MODE_PRIVATE ) }
51+ private val pixel5xxKeys: MutableSet <String > by lazy {
52+ preferences.getStringSet(PIXEL_5XX_KEYS_SET , mutableSetOf ()) ? : mutableSetOf ()
53+ }
4154
4255 override fun updateCountPixel (httpErrorPixelName : HttpErrorPixelName ) {
4356 val count = preferences.getInt(httpErrorPixelName.appendCountSuffix(), 0 )
4457 preferences.edit { putInt(httpErrorPixelName.appendCountSuffix(), count + 1 ) }
4558 }
4659
60+ override suspend fun update5xxCountPixel (
61+ httpErrorPixelName : HttpErrorPixelName ,
62+ statusCode : Int ,
63+ ) {
64+ // Kill switch
65+ if (! androidBrowserConfig.self().isEnabled() || ! androidBrowserConfig.httpError5xxPixel().isEnabled()) {
66+ return
67+ }
68+
69+ val pProVpnConnected = runCatching {
70+ networkProtectionState.isRunning()
71+ }.getOrDefault(false )
72+
73+ val externalVpnConnected = runCatching {
74+ externalVpnDetector.isExternalVpnDetected()
75+ }.getOrDefault(false )
76+
77+ val webViewFullVersion = webViewVersionProvider.getFullVersion()
78+
79+ val pixelPrefKey = " ${httpErrorPixelName.pixelName} |$statusCode |$pProVpnConnected |$externalVpnConnected |$webViewFullVersion |_count"
80+
81+ val updatedSet = pixel5xxKeys
82+ updatedSet.add(pixelPrefKey)
83+ val count = preferences.getInt(pixelPrefKey, 0 )
84+ preferences.edit {
85+ putInt(pixelPrefKey, count + 1 )
86+ putStringSet(PIXEL_5XX_KEYS_SET , updatedSet)
87+ }
88+ }
89+
4790 override fun fireCountPixel (httpErrorPixelName : HttpErrorPixelName ) {
4891 val now = Instant .now().toEpochMilli()
4992
@@ -64,6 +107,49 @@ class RealHttpErrorPixels @Inject constructor(
64107 }
65108 }
66109
110+ override fun fire5xxCountPixels () {
111+ // Kill switch
112+ if (! androidBrowserConfig.self().isEnabled() || ! androidBrowserConfig.httpError5xxPixel().isEnabled()) {
113+ return
114+ }
115+
116+ val now = Instant .now().toEpochMilli()
117+ val updatedSet = pixel5xxKeys
118+ updatedSet.forEach { pixelKey ->
119+ val count = preferences.getInt(pixelKey, 0 )
120+ if (count != 0 ) {
121+ val timestamp = preferences.getLong(" ${pixelKey} _timestamp" , 0L )
122+ if (timestamp == 0L || now >= timestamp) {
123+ pixelKey.split(" |" ).let { split ->
124+ if (split.size == 6 ) {
125+ val httpErrorPixelName = HttpErrorPixelName .WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY
126+ val statusCode = split[1 ].toInt()
127+ val pProVpnConnected = split[2 ].toBoolean()
128+ val externalVpnConnected = split[3 ].toBoolean()
129+ val webViewFullVersion = split[4 ]
130+ pixel.fire(
131+ httpErrorPixelName,
132+ mapOf (
133+ HttpErrorPixelParameters .HTTP_ERROR_CODE_COUNT to count.toString(),
134+ " error_code" to statusCode.toString(),
135+ " ppro_user" to pProVpnConnected.toString(),
136+ " vpn_user" to externalVpnConnected.toString(),
137+ " webview_version" to webViewFullVersion,
138+ ),
139+ )
140+ }
141+ }
142+ .also {
143+ preferences.edit {
144+ putLong(" ${pixelKey} _timestamp" , now.plus(TimeUnit .HOURS .toMillis(WINDOW_INTERVAL_HOURS )))
145+ putInt(pixelKey, 0 )
146+ }
147+ }
148+ }
149+ }
150+ }
151+ }
152+
67153 private fun HttpErrorPixelName.appendTimestampSuffix (): String {
68154 return " ${this .pixelName} _timestamp"
69155 }
@@ -75,5 +161,6 @@ class RealHttpErrorPixels @Inject constructor(
75161 companion object {
76162 private const val FILENAME = " com.duckduckgo.app.browser.httperrors"
77163 private const val WINDOW_INTERVAL_HOURS = 24L
164+ internal const val PIXEL_5XX_KEYS_SET = " pixel_5xx_keys_set"
78165 }
79166}
0 commit comments