Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Header with App Version #5347

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import com.duckduckgo.app.browser.pageloadpixel.PageLoadedHandler
import com.duckduckgo.app.browser.pageloadpixel.firstpaint.PagePaintedHandler
import com.duckduckgo.app.browser.print.PrintInjector
import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin
import com.duckduckgo.app.browser.trafficquality.CustomHeaderAllowedChecker
import com.duckduckgo.app.browser.trafficquality.remote.AndroidFeaturesHeaderProvider
import com.duckduckgo.app.browser.uriloaded.UriLoadedManager
import com.duckduckgo.app.global.model.Site
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
Expand Down Expand Up @@ -148,10 +150,18 @@ class BrowserWebViewClientTest {
private val mockDuckPlayer: DuckPlayer = mock()
private val navigationHistory: NavigationHistory = mock()
private val mockDuckDuckGoUrlDetector: DuckDuckGoUrlDetector = mock()
private val mockCustomHeaderGracePeriodChecker: CustomHeaderAllowedChecker = mock()
private val mockFeaturesHeaderProvider: AndroidFeaturesHeaderProvider = mock()
private val openInNewTabFlow: MutableSharedFlow<OpenDuckPlayerInNewTab> = MutableSharedFlow()
private val mockUriLoadedManager: UriLoadedManager = mock()
private val mockAndroidBrowserConfigFeature: AndroidBrowserConfigFeature = mock()
private val mockAndroidFeaturesHeaderPlugin = AndroidFeaturesHeaderPlugin(mockDuckDuckGoUrlDetector, mockAndroidBrowserConfigFeature, mock())
private val mockAndroidFeaturesHeaderPlugin = AndroidFeaturesHeaderPlugin(
mockDuckDuckGoUrlDetector,
mockCustomHeaderGracePeriodChecker,
mockAndroidBrowserConfigFeature,
mockFeaturesHeaderProvider,
mock(),
)

@UiThreadTest
@Before
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.duckduckgo.app.browser.trafficquality

import com.duckduckgo.app.browser.DuckDuckGoUrlDetector
import com.duckduckgo.app.browser.trafficquality.Result.Allowed
import com.duckduckgo.app.browser.trafficquality.Result.NotAllowed
import com.duckduckgo.app.browser.trafficquality.remote.AndroidFeaturesHeaderProvider
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider.CustomHeadersPlugin
Expand All @@ -27,23 +29,42 @@ import javax.inject.Inject
@ContributesMultibinding(scope = AppScope::class)
class AndroidFeaturesHeaderPlugin @Inject constructor(
private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector,
private val customHeaderAllowedChecker: CustomHeaderAllowedChecker,
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
private val androidFeaturesHeaderProvider: AndroidFeaturesHeaderProvider,
private val appVersionProvider: AppVersionHeaderProvider,
) : CustomHeadersPlugin {

override fun getHeaders(url: String): Map<String, String> {
if (androidBrowserConfigFeature.self().isEnabled() &&
androidBrowserConfigFeature.featuresRequestHeader().isEnabled() &&
duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(url)
) {
androidFeaturesHeaderProvider.provide()?.let { headerValue ->
return mapOf(X_DUCKDUCKGO_ANDROID_HEADER to headerValue)
if (isFeatureEnabled() && duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(url)) {
return when (val result = customHeaderAllowedChecker.isAllowed()) {
is Allowed -> {
val headers = mutableMapOf<String, String>()
androidFeaturesHeaderProvider.provide(result.config)?.let { headerValue ->
headers.put(X_DUCKDUCKGO_ANDROID_HEADER, headerValue)
}
appVersionProvider.provide(isStub = false).let { headerValue ->
headers.put(X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER, headerValue)
}
headers
}

NotAllowed -> {
mapOf(X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER to appVersionProvider.provide(isStub = true))
}
}
} else {
return emptyMap()
}
return emptyMap()
}

private fun isFeatureEnabled(): Boolean {
return androidBrowserConfigFeature.self().isEnabled() &&
androidBrowserConfigFeature.featuresRequestHeader().isEnabled()
}

companion object {
internal const val X_DUCKDUCKGO_ANDROID_HEADER = "x-duckduckgo-android"
internal const val X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER = "x-duckduckgo-android-version"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.browser.trafficquality

import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import javax.inject.Inject

interface AppVersionHeaderProvider {
fun provide(isStub: Boolean): String
}

@ContributesBinding(AppScope::class)
class RealAppVersionHeaderProvider @Inject constructor(
private val appBuildConfig: AppBuildConfig,

) : AppVersionHeaderProvider {
override fun provide(isStub: Boolean): String {
return if (isStub) {
APP_VERSION_QUALITY_DEFAULT_VALUE
} else {
appBuildConfig.versionName
}
}

companion object {
const val APP_VERSION_QUALITY_DEFAULT_VALUE = "other_versions"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.browser.trafficquality

import com.duckduckgo.app.browser.trafficquality.Result.Allowed
import com.duckduckgo.app.browser.trafficquality.Result.NotAllowed
import com.duckduckgo.app.browser.trafficquality.remote.FeaturesRequestHeaderStore
import com.duckduckgo.app.browser.trafficquality.remote.TrafficQualityAppVersion
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.temporal.ChronoUnit
import javax.inject.Inject

interface CustomHeaderAllowedChecker {
fun isAllowed(): Result<TrafficQualityAppVersion>
}

sealed class Result<out R> {
data class Allowed(val config: TrafficQualityAppVersion) : Result<TrafficQualityAppVersion>()
data object NotAllowed : Result<Nothing>()
}

@ContributesBinding(AppScope::class)
class RealCustomHeaderGracePeriodChecker @Inject constructor(
private val appBuildConfig: AppBuildConfig,
private val featuresRequestHeaderStore: FeaturesRequestHeaderStore,
) : CustomHeaderAllowedChecker {
override fun isAllowed(): Result<TrafficQualityAppVersion> {
val config = featuresRequestHeaderStore.getConfig()
val versionConfig = config.find { it.appVersion == appBuildConfig.versionCode }
malmstein marked this conversation as resolved.
Show resolved Hide resolved
return if (versionConfig != null) {
if (shouldSendHeader(versionConfig)) {
Allowed(versionConfig)
} else {
NotAllowed
}
} else {
NotAllowed
}
}

private fun shouldSendHeader(versionConfig: TrafficQualityAppVersion): Boolean {
val appBuildDateMillis = appBuildConfig.buildDateTimeMillis
if (appBuildDateMillis == 0L) {
return false
}

val appBuildDate = LocalDateTime.ofEpochSecond(appBuildDateMillis / 1000, 0, ZoneOffset.UTC)
val now = LocalDateTime.now(ZoneOffset.UTC)

val daysSinceBuild = ChronoUnit.DAYS.between(appBuildDate, now)
val daysUntilLoggingStarts = versionConfig.daysUntilLoggingStarts
val daysForAppVersionLogging = daysUntilLoggingStarts + versionConfig.daysLogging

return daysSinceBuild in daysUntilLoggingStarts..daysForAppVersionLogging
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,29 @@

package com.duckduckgo.app.browser.trafficquality.remote

import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.autoconsent.api.Autoconsent
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection
import com.duckduckgo.networkprotection.api.NetworkProtectionState
import com.duckduckgo.privacy.config.api.Gpc
import com.squareup.anvil.annotations.ContributesBinding
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.temporal.ChronoUnit
import javax.inject.Inject
import kotlinx.coroutines.runBlocking

interface AndroidFeaturesHeaderProvider {
fun provide(): String?
fun provide(config: TrafficQualityAppVersion): String?
}

@ContributesBinding(AppScope::class)
class RealAndroidFeaturesHeaderProvider @Inject constructor(
private val appBuildConfig: AppBuildConfig,
private val featuresRequestHeaderStore: FeaturesRequestHeaderStore,
private val autoconsent: Autoconsent,
private val gpc: Gpc,
private val appTrackingProtection: AppTrackingProtection,
private val networkProtectionState: NetworkProtectionState,
) : AndroidFeaturesHeaderProvider {

override fun provide(): String? {
val config = featuresRequestHeaderStore.getConfig()
val versionConfig = config.find { it.appVersion == appBuildConfig.versionCode }
return if (versionConfig != null && shouldLogValue(versionConfig)) {
logFeature(versionConfig)
} else {
null
}
}

private fun shouldLogValue(versionConfig: TrafficQualityAppVersion): Boolean {
val appBuildDateMillis = appBuildConfig.buildDateTimeMillis
if (appBuildDateMillis == 0L) {
return false
}

val appBuildDate = LocalDateTime.ofEpochSecond(appBuildDateMillis / 1000, 0, ZoneOffset.UTC)
val now = LocalDateTime.now(ZoneOffset.UTC)

val daysSinceBuild = ChronoUnit.DAYS.between(appBuildDate, now)
val daysUntilLoggingStarts = versionConfig.daysUntilLoggingStarts
val daysForAppVersionLogging = versionConfig.daysUntilLoggingStarts + versionConfig.daysLogging

return daysSinceBuild in daysUntilLoggingStarts..daysForAppVersionLogging
override fun provide(config: TrafficQualityAppVersion): String? {
return logFeature(config)
}

private fun logFeature(versionConfig: TrafficQualityAppVersion): String? {
Expand Down
Loading
Loading