Skip to content

Commit

Permalink
Make pause vpn during calls available to prod (#4463)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1203137811378537/1206289136451167/f

### Description
See attached task description

### Steps to test this PR
https://app.asana.com/0/0/1207157151658716/f
  • Loading branch information
karlenDimla committed Apr 29, 2024
1 parent 0648836 commit 02162d7
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package="com.duckduckgo.networkprotection.impl">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

<application>
<activity
android:name=".management.NetworkProtectionManagementActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ class NetworkProtectionManagementViewModel @Inject constructor(
)
}

override fun onCreate(owner: LifecycleOwner) {
networkProtectionPixels.reportVpnScreenShown()
}

override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
viewModelScope.launch(dispatcherProvider.io()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,12 @@ enum class NetworkProtectionPixelNames(
NETP_ENABLE_FROM_SETTINGS_TILE_DAILY("m_netp_ev_enable_from_settings_tile_d", enqueue = true),
NETP_DISABLE_FROM_SETTINGS_TILE("m_netp_ev_disable_from_settings_tile_c", enqueue = true),
NETP_DISABLE_FROM_SETTINGS_TILE_DAILY("m_netp_ev_disable_from_settings_tile_d", enqueue = true),
NETP_VPN_SCREEN_SHOWN("m_netp_imp_vpn_screen_c"),
NETP_VPN_SCREEN_SHOWN_DAILY("m_netp_imp_vpn_screen_d"),
NETP_VPN_SETTINGS_SHOWN("m_netp_imp_vpn_settings_screen_c"),
NETP_VPN_SETTINGS_SHOWN_DAILY("m_netp_imp_vpn_settings_screen_d"),
NETP_PAUSE_ON_CALL_ENABLED("m_netp_ev_enabled_pause_vpn_during_calls_c"),
NETP_PAUSE_ON_CALL_ENABLED_DAILY("m_netp_ev_enabled_pause_vpn_during_calls_d"),
NETP_PAUSE_ON_CALL_DISABLED("m_netp_ev_disabled_pause_vpn_during_calls_c"),
NETP_PAUSE_ON_CALL_DISABLED_DAILY("m_netp_ev_disabled_pause_vpn_during_calls_d"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@ interface NetworkProtectionPixels {

fun reportVpnEnabledFromQuickSettingsTile()
fun reportVpnDisabledFromQuickSettingsTile()

fun reportVpnScreenShown()

fun reportVpnSettingsShown()

fun reportEnabledPauseDuringCalls()

fun reportDisabledPauseDuringCalls()
}

@ContributesBinding(AppScope::class)
Expand Down Expand Up @@ -534,6 +542,26 @@ class RealNetworkProtectionPixel @Inject constructor(
tryToFireDailyPixel(NETP_DISABLE_FROM_SETTINGS_TILE_DAILY)
}

override fun reportVpnScreenShown() {
firePixel(NETP_VPN_SCREEN_SHOWN)
tryToFireDailyPixel(NETP_VPN_SCREEN_SHOWN_DAILY)
}

override fun reportVpnSettingsShown() {
firePixel(NETP_VPN_SETTINGS_SHOWN)
tryToFireDailyPixel(NETP_VPN_SETTINGS_SHOWN_DAILY)
}

override fun reportEnabledPauseDuringCalls() {
firePixel(NETP_PAUSE_ON_CALL_ENABLED)
tryToFireDailyPixel(NETP_PAUSE_ON_CALL_ENABLED_DAILY)
}

override fun reportDisabledPauseDuringCalls() {
firePixel(NETP_PAUSE_ON_CALL_DISABLED)
tryToFireDailyPixel(NETP_PAUSE_ON_CALL_ENABLED_DAILY)
}

private fun firePixel(
p: NetworkProtectionPixelNames,
payload: Map<String, String> = emptyMap(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ interface NetPSettingsLocalConfig {
*/
@Toggle.DefaultValue(true)
fun vpnExcludeLocalNetworkRoutes(): Toggle

/**
* When `true` the VPN will automatically pause when a call is started and will automatically restart after.
*/
@Toggle.DefaultValue(false)
fun vpnPauseDuringCalls(): Toggle
}

@ContributesBinding(AppScope::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,21 @@

package com.duckduckgo.networkprotection.impl.settings

import android.Manifest.permission
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.common.ui.DuckDuckGoActivity
import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.common.utils.extensions.launchAlwaysOnSystemSettings
import com.duckduckgo.common.utils.extensions.launchApplicationInfoSettings
import com.duckduckgo.common.utils.extensions.launchIgnoreBatteryOptimizationSettings
import com.duckduckgo.common.utils.plugins.PluginPoint
import com.duckduckgo.di.scopes.ActivityScope
Expand All @@ -35,8 +40,10 @@ import com.duckduckgo.networkprotection.impl.databinding.ActivityNetpVpnSettings
import com.duckduckgo.networkprotection.impl.settings.NetPVpnSettingsViewModel.RecommendedSettings
import com.duckduckgo.networkprotection.impl.settings.NetPVpnSettingsViewModel.ViewState
import javax.inject.Inject
import kotlin.math.absoluteValue
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.logcat

@InjectWith(
scope = ActivityScope::class,
Expand Down Expand Up @@ -98,20 +105,68 @@ class NetPVpnSettingsActivity : DuckDuckGoActivity() {
}
binding.unrestrictedBatteryUsage.setPrimaryText(getString(batteryTextTitle))
binding.unrestrictedBatteryUsage.setSecondaryText(getString(batteryTextByline))

// val alwaysOnLeadingIcon = if (state.alwaysOnState) R.drawable.ic_check_color_24 else R.drawable.ic_alert_color_24
// binding.alwaysOn.setLeadingIconResource(alwaysOnLeadingIcon)
}

private fun renderViewState(viewState: ViewState) {
binding.excludeLocalNetworks.quietlySetIsChecked(viewState.excludeLocalNetworks) { _, isChecked ->
viewModel.onExcludeLocalRoutes(isChecked)
}

binding.pauseWhileCalling.quietlySetIsChecked(viewState.pauseDuringWifiCalls) { _, isChecked ->
if (isChecked && hasPhoneStatePermission()) {
viewModel.onEnablePauseDuringWifiCalls()
} else if (isChecked) {
binding.pauseWhileCalling.setIsChecked(false)
if (shouldShowRequestPermissionRationale(permission.READ_PHONE_STATE)) {
TextAlertDialogBuilder(this)
.setTitle(R.string.netpGrantPhonePermissionTitle)
.setMessage(R.string.netpGrantPhonePermissionByline)
.setPositiveButton(R.string.netpGrantPhonePermissionActionPositive)
.setNegativeButton(R.string.netpGrantPhonePermissionActionNegative)
.addEventListener(
object : TextAlertDialogBuilder.EventListener() {
override fun onPositiveButtonClicked() {
// User denied the permission 2+ times
this@NetPVpnSettingsActivity.launchApplicationInfoSettings()
}
},
)
.show()
} else {
requestPermissions(arrayOf(permission.READ_PHONE_STATE), permission.READ_PHONE_STATE.hashCode().absoluteValue)
}
} else {
binding.pauseWhileCalling.setIsChecked(false)
viewModel.onDisablePauseDuringWifiCalls()
}
}

binding.unrestrictedBatteryUsage.setOnClickListener {
this.launchIgnoreBatteryOptimizationSettings()
}
}

private fun hasPhoneStatePermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
permission.READ_PHONE_STATE,
) == PackageManager.PERMISSION_GRANTED
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
permission.READ_PHONE_STATE.hashCode().absoluteValue -> {
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
binding.pauseWhileCalling.setIsChecked(granted)
if (!granted) {
logcat { "READ_PHONE_STATE permission denied" }
}
}
else -> {}
}
}

private fun setupUiElements() {
binding.vpnNotifications.setClickListener {
globalActivityStarter.start(this, NetPNotificationSettingsScreenNoParams)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import com.duckduckgo.common.utils.extensions.isIgnoringBatteryOptimizations
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.networkprotection.api.NetworkProtectionState
import com.duckduckgo.networkprotection.impl.pixels.NetworkProtectionPixels
import com.duckduckgo.networkprotection.impl.snooze.VpnDisableOnCall
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
Expand All @@ -49,6 +51,8 @@ class NetPVpnSettingsViewModel @Inject constructor(
private val dispatcherProvider: DispatcherProvider,
private val netPSettingsLocalConfig: NetPSettingsLocalConfig,
private val networkProtectionState: NetworkProtectionState,
private val vpnDisableOnCall: VpnDisableOnCall,
private val networkProtectionPixels: NetworkProtectionPixels,
@InternalApi private val isIgnoringBatteryOptimizations: () -> Boolean,
) : ViewModel(), DefaultLifecycleObserver {

Expand All @@ -64,10 +68,12 @@ class NetPVpnSettingsViewModel @Inject constructor(
replay = 1,
onBufferOverflow = DROP_OLDEST,
)

internal fun viewState(): Flow<ViewState> = _viewState.asStateFlow()

internal data class ViewState(
val excludeLocalNetworks: Boolean = false,
val pauseDuringWifiCalls: Boolean = false,
)

init {
Expand All @@ -80,11 +86,21 @@ class NetPVpnSettingsViewModel @Inject constructor(
return _recommendedSettingsState.distinctUntilChanged()
}

override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
networkProtectionPixels.reportVpnSettingsShown()
}

override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
viewModelScope.launch(dispatcherProvider.io()) {
val excludeLocalRoutes = netPSettingsLocalConfig.vpnExcludeLocalNetworkRoutes().isEnabled()
_viewState.emit(_viewState.value.copy(excludeLocalNetworks = excludeLocalRoutes))
_viewState.emit(
_viewState.value.copy(
excludeLocalNetworks = excludeLocalRoutes,
pauseDuringWifiCalls = vpnDisableOnCall.isEnabled(),
),
)
}
}

Expand Down Expand Up @@ -115,6 +131,16 @@ class NetPVpnSettingsViewModel @Inject constructor(
}
}

internal fun onEnablePauseDuringWifiCalls() {
networkProtectionPixels.reportEnabledPauseDuringCalls()
vpnDisableOnCall.enable()
}

internal fun onDisablePauseDuringWifiCalls() {
networkProtectionPixels.reportDisabledPauseDuringCalls()
vpnDisableOnCall.disable()
}

data class RecommendedSettings(val isIgnoringBatteryOptimizations: Boolean)
}

Expand Down

0 comments on commit 02162d7

Please sign in to comment.