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

Make pause vpn during calls available to prod #4463

Merged
merged 5 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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