Skip to content

Commit

Permalink
Merge branch 'release/5.195.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
daxtheduck committed Mar 27, 2024
2 parents d6053ae + 68ac9a6 commit b246eb8
Show file tree
Hide file tree
Showing 84 changed files with 1,647 additions and 1,046 deletions.
10 changes: 0 additions & 10 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
<!--
Note: This checklist is a reminder of our shared engineering expectations.
The items in Bold are required
If your PR involves UI changes:
1. Upload screenshots or screencasts that illustrate the changes before / after
2. Add them under the UI changes section (feel free to add more columns if needed)
If your PR does not involve UI changes, you can remove the **UI changes** section
At a minimum, make sure your changes are tested in API 23 and one of the more recent API levels available.
-->

Task/Issue URL:

Expand Down
21 changes: 6 additions & 15 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ ext {

android {
defaultConfig {
if (project.hasProperty('internal-testing')) {
applicationId "com.duckduckgo.dev.android"
} else {
applicationId "com.duckduckgo.mobile.android"
}
applicationId "com.duckduckgo.mobile.android"

minSdk min_sdk
targetSdk target_sdk
compileSdk compile_sdk
Expand Down Expand Up @@ -80,9 +77,7 @@ android {
}
buildTypes {
debug {
if (!project.hasProperty('internal-testing')) {
applicationIdSuffix ".debug"
}
applicationIdSuffix ".debug"
pseudoLocalesEnabled false
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_blue",
Expand Down Expand Up @@ -305,13 +300,9 @@ dependencies {
implementation project(':web-compat-store')

implementation project(':subscriptions-api')
implementation project(':network-protection-subscription-internal')
if (project.hasProperty('internal-testing')) {
implementation project(':subscriptions-impl')
internalImplementation project(':subscriptions-internal')
} else {
implementation project(":subscriptions-dummy-impl")
}

implementation project(':subscriptions-impl')
internalImplementation project(':subscriptions-internal')

implementation project(':user-agent-api')
implementation project(':user-agent-impl')
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,6 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
REMOTE_MESSAGE_SECONDARY_ACTION_CLICKED("m_remote_message_secondary_action_clicked"),
REMOTE_MESSAGE_ACTION_CLICKED("m_remote_message_action_clicked"),
REMOTE_MESSAGE_SHARED("m_remote_message_share"),

PRIVACY_PRO_IS_ENABLED_AND_ELIGIBLE("m_privacy-pro_is-enabled"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ import com.duckduckgo.app.firebutton.FireButtonScreenNoParams
import com.duckduckgo.app.global.view.launchDefaultAppActivity
import com.duckduckgo.app.permissions.PermissionsScreenNoParams
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.PRIVACY_PRO_IS_ENABLED_AND_ELIGIBLE
import com.duckduckgo.app.privatesearch.PrivateSearchScreenNoParams
import com.duckduckgo.app.settings.SettingsViewModel.Command
import com.duckduckgo.app.settings.SettingsViewModel.NetPEntryState
import com.duckduckgo.app.settings.SettingsViewModel.NetPEntryState.Hidden
import com.duckduckgo.app.settings.SettingsViewModel.NetPEntryState.Pending
import com.duckduckgo.app.settings.SettingsViewModel.NetPEntryState.ShowState
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.DAILY
import com.duckduckgo.app.webtrackingprotection.WebTrackingProtectionScreenNoParams
import com.duckduckgo.app.widget.AddWidgetLauncher
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
Expand Down Expand Up @@ -213,6 +215,7 @@ class SettingsActivity : DuckDuckGoActivity() {

private fun updatePrivacyPro(isPrivacyProEnabled: Boolean) {
if (isPrivacyProEnabled) {
pixel.fire(PRIVACY_PRO_IS_ENABLED_AND_ELIGIBLE, type = DAILY)
viewsPro.show()
} else {
viewsPro.gone()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ class SettingsViewModel @Inject constructor(
if (isPrivacyProEnabled()) Hidden else getNetworkProtectionEntryState(this)
},
isAutoconsentEnabled = autoconsent.isSettingEnabled(),
isPrivacyProEnabled = isPrivacyProEnabled() && subscriptions.isEligible(),
),
)
networkProtectionState.getConnectionStateFlow()
Expand Down
2 changes: 1 addition & 1 deletion app/version/version.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION=5.194.0
VERSION=5.195.0
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ subprojects {
if (dependencyPath == projectPath) continue
// internal modules have to use internalImplementation
// when a non internal configuration is built (i.e. PlayDebug) no internal dependencies should be found
def internalExceptions = [":network-protection-subscription-internal"]
def internalExceptions = []
if (dependencyPath.endsWith('internal') && !internalExceptions.contains(dependencyPath) && !name.toLowerCase().contains("internal")) {
throw new GradleException("Invalid dependency $projectPath -> $dependencyPath. " +
"'internal' modules must use internalImplementation")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.common.utils.network

import java.net.Inet4Address
import java.net.InetAddress

fun InetAddress.isCGNATed(): Boolean {
if (this !is Inet4Address) return false

// CGNAT is 100.64.0.0 -> 100.127.255.255
val firstOctet = address[0].toUInt()
val secondOctet = address[1].toUInt()

return firstOctet == 100u && (secondOctet in 64u..127u)
}
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.common.utils.network

import androidx.test.ext.junit.runners.AndroidJUnit4
import java.net.Inet4Address
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class NetworkExtensionsTest {

@Test
fun `test isCGNAT`() {
// cover all CGNAT range
for (octet2 in 64..127) {
for (octet3 in 0..255) {
for (octet4 in 0..255) {
assertTrue(Inet4Address.getByName("100.$octet2.$octet3.$octet4").isCGNATed())
}
}
}

// previous and next IP address
assertFalse(Inet4Address.getByName("100.63.255.255").isCGNATed())
assertFalse(Inet4Address.getByName("100.128.0.0").isCGNATed())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.duckduckgo.networkprotection.api

import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams
import kotlinx.coroutines.flow.Flow

interface NetworkProtectionWaitlist {

Expand All @@ -26,6 +27,12 @@ interface NetworkProtectionWaitlist {
*/
suspend fun getState(): NetPWaitlistState

/**
* Returns a flow of the states of the NetP access
* The caller DOES NOT need to specify the dispatcher when calling this method
*/
suspend fun getStateFlow(): Flow<NetPWaitlistState>

/**
* Call this method to get the [ActivityParams] corresponding to the activity to launch for the current
* state of the waitlist beta
Expand Down
1 change: 1 addition & 0 deletions network-protection/network-protection-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
implementation project(':privacy-config-api')
implementation project(':navigation-api')
implementation project(':subscriptions-api')
implementation project(':settings-api')

implementation AndroidX.appCompat
implementation AndroidX.constraintLayout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.appbuildconfig.api.isInternalBuild
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.networkprotection.impl.BuildConfig
import com.duckduckgo.networkprotection.impl.store.NetworkProtectionRepository
import com.duckduckgo.networkprotection.impl.waitlist.store.NetPWaitlistRepository
import com.duckduckgo.subscriptions.api.Subscriptions
import com.squareup.anvil.annotations.ContributesMultibinding
Expand All @@ -39,7 +38,6 @@ import okhttp3.Response
class NetpWaitlistRequestInterceptor @Inject constructor(
private val netpWaitlistRepository: NetPWaitlistRepository,
private val appBuildConfig: AppBuildConfig,
private val networkProtectionRepository: NetworkProtectionRepository,
private val subscriptions: Subscriptions,
) : ApiInterceptorPlugin, Interceptor {

Expand All @@ -65,11 +63,7 @@ class NetpWaitlistRequestInterceptor @Inject constructor(

chain.proceed(
newRequest.build().also { logcat { "headers: ${it.headers}" } },
).also {
if (runBlocking { subscriptions.isEnabled() }) {
networkProtectionRepository.vpnAccessRevoked = (it.code == 403)
}
}
)
} else {
chain.proceed(newRequest.build())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.networkprotection.impl.integration

import com.duckduckgo.common.utils.network.isCGNATed
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.mobile.android.vpn.VpnFeaturesRegistry
import com.duckduckgo.mobile.android.vpn.state.VpnStateCollectorPlugin
Expand All @@ -28,6 +29,8 @@ import com.duckduckgo.networkprotection.impl.settings.NetPSettingsLocalConfig
import com.duckduckgo.networkprotection.store.NetPExclusionListRepository
import com.duckduckgo.networkprotection.store.NetPGeoswitchingRepository
import com.squareup.anvil.annotations.ContributesMultibinding
import java.net.Inet4Address
import java.net.NetworkInterface
import javax.inject.Inject
import org.json.JSONObject

Expand All @@ -40,10 +43,12 @@ class NetPStateCollector @Inject constructor(
private val netPSettingsLocalConfig: NetPSettingsLocalConfig,
private val netPGeoswitchingRepository: NetPGeoswitchingRepository,
) : VpnStateCollectorPlugin {

override suspend fun collectVpnRelatedState(appPackageId: String?): JSONObject {
val isNetpRunning = vpnFeaturesRegistry.isFeatureRunning(NetPVpnFeature.NETP_VPN)
return JSONObject().apply {
put("enabled", isNetpRunning)
put("CGNATed", isCGNATed())
if (isNetpRunning) {
appPackageId?.let {
put("reportedAppProtected", !netPExclusionListRepository.getExcludedAppPackages().contains(it))
Expand All @@ -59,4 +64,16 @@ class NetPStateCollector @Inject constructor(
}

override val collectorName: String = "netPState"

private fun isCGNATed(): Boolean {
NetworkInterface.getNetworkInterfaces().asSequence().filter { !it.name.contains("tun") }.map { it.interfaceAddresses }.forEach { addrs ->
addrs.filter { it.address is Inet4Address && !it.address.isLoopbackAddress }.forEach { interfaceAddress ->
if (interfaceAddress.address.isCGNATed()) {
return true
}
}
}

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,17 @@ import com.duckduckgo.browser.api.ui.BrowserScreens.SettingsScreenNoParams
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.duckduckgo.networkprotection.impl.R
import com.duckduckgo.networkprotection.impl.store.NetworkProtectionRepository
import com.duckduckgo.networkprotection.impl.subscription.NetpSubscriptionManager
import com.duckduckgo.networkprotection.impl.subscription.isActive
import com.duckduckgo.networkprotection.impl.subscription.isExpired
import com.squareup.anvil.annotations.ContributesBinding
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import javax.inject.Inject

interface NetPDisabledNotificationBuilder {
fun buildDisabledNotification(context: Context): Notification
suspend fun buildDisabledNotification(context: Context): Notification?

fun buildSnoozeNotification(
context: Context,
Expand All @@ -55,7 +57,7 @@ interface NetPDisabledNotificationBuilder {
class RealNetPDisabledNotificationBuilder @Inject constructor(
private val netPNotificationActions: NetPNotificationActions,
private val globalActivityStarter: GlobalActivityStarter,
private val netpRepository: NetworkProtectionRepository,
private val netpSubscriptionManager: NetpSubscriptionManager,
) : NetPDisabledNotificationBuilder {
private val defaultDateTimeFormatter = SimpleDateFormat.getTimeInstance(DateFormat.SHORT)

Expand All @@ -72,11 +74,14 @@ class RealNetPDisabledNotificationBuilder @Inject constructor(
}
}

override fun buildDisabledNotification(context: Context): Notification {
return if (netpRepository.vpnAccessRevoked) {
override suspend fun buildDisabledNotification(context: Context): Notification? {
val vpnStatus = netpSubscriptionManager.getVpnStatus()
return if (vpnStatus.isExpired()) {
buildVpnAccessRevokedNotification(context)
} else {
} else if (vpnStatus.isActive()) {
buildVpnDisabledNotification(context)
} else {
null
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import com.duckduckgo.mobile.android.vpn.service.VpnServiceCallbacks
import com.duckduckgo.mobile.android.vpn.state.VpnStateMonitor.VpnStopReason
import com.duckduckgo.networkprotection.api.NetworkProtectionState
import com.duckduckgo.networkprotection.impl.settings.NetPSettingsLocalConfig
import com.duckduckgo.networkprotection.impl.store.NetworkProtectionRepository
import com.duckduckgo.networkprotection.impl.waitlist.NetPRemoteFeature
import com.squareup.anvil.annotations.ContributesMultibinding
import java.util.concurrent.atomic.AtomicReference
Expand All @@ -47,7 +46,6 @@ class NetPDisabledNotificationScheduler @Inject constructor(
@AppCoroutineScope private val coroutineScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
private val netPRemoteFeature: NetPRemoteFeature,
private val networkProtectionRepository: NetworkProtectionRepository,
) : VpnServiceCallbacks {

private var isNetPEnabled: AtomicReference<Boolean> = AtomicReference(false)
Expand Down Expand Up @@ -80,20 +78,6 @@ class NetPDisabledNotificationScheduler @Inject constructor(
}
}

override fun onVpnStartFailed(coroutineScope: CoroutineScope) {
coroutineScope.launch(dispatcherProvider.io()) {
if (networkProtectionRepository.vpnAccessRevoked) {
notificationManager.checkPermissionAndNotify(
context,
NETP_REMINDER_NOTIFICATION_ID,
netPDisabledNotificationBuilder.buildVpnAccessRevokedNotification(context),
)
// This is to clear the registered features and remove VPN
networkProtectionState.stop()
}
}
}

private suspend fun shouldShowImmediateNotification(): Boolean {
// When VPN is stopped and if AppTP has been enabled AND user has been onboarded, then we show the disabled notif
return isNetPEnabled.get() && networkProtectionState.isOnboarded() && netPRemoteFeature.waitlistBetaActive().isEnabled()
Expand Down Expand Up @@ -140,11 +124,13 @@ class NetPDisabledNotificationScheduler @Inject constructor(
coroutineScope.launch(dispatcherProvider.io()) {
logcat { "Showing disabled notification for NetP" }
if (!netPSettingsLocalConfig.vpnNotificationAlerts().isEnabled()) return@launch
notificationManager.checkPermissionAndNotify(
context,
NETP_REMINDER_NOTIFICATION_ID,
netPDisabledNotificationBuilder.buildDisabledNotification(context),
)
netPDisabledNotificationBuilder.buildDisabledNotification(context)?.let { notification ->
notificationManager.checkPermissionAndNotify(
context,
NETP_REMINDER_NOTIFICATION_ID,
notification,
)
}
}
}

Expand Down
Loading

0 comments on commit b246eb8

Please sign in to comment.