Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/vpn/data/repository/impl/VpnRepositoryImplTest.kt 0 → 100644 +350 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * 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.android.systemui.statusbar.policy.vpn.data.repository.impl import android.content.packageManager import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest import android.net.connectivityManager import android.net.vpnManager import android.os.userManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.net.VpnConfig import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.policy.vpn.data.repository.realVpnRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyString import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class VpnRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest by lazy { kosmos.realVpnRepository } private val mockNetwork: Network = mock() private val mockValidatedCapabilities: NetworkCapabilities = mock<NetworkCapabilities>().apply { whenever(hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)).thenReturn(true) } private val mockNonValidatedCapabilities: NetworkCapabilities = mock<NetworkCapabilities>().apply { whenever(hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)).thenReturn(false) } private val mockLinkProperties: android.net.LinkProperties = mock<android.net.LinkProperties>().apply { whenever(interfaceName).thenReturn("test_interface") } @Before fun setUp() { whenever(kosmos.userManager.users).thenReturn(listOf(primaryUser)) whenever(kosmos.userManager.getProfileIdsWithDisabled(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) whenever(kosmos.connectivityManager.allNetworks).thenReturn(arrayOf(mockNetwork)) whenever(kosmos.connectivityManager.getLinkProperties(mockNetwork)) .thenReturn(mockLinkProperties) whenever(kosmos.connectivityManager.getNetworkCapabilities(mockNetwork)) .thenReturn(mockNonValidatedCapabilities) whenever(kosmos.packageManager.getApplicationInfo(anyString(), anyInt())) .thenReturn(nonBrandedAppInfo) } @Test fun isVpnEnabled_whenVpnConnected_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) assertThat(vpnState?.isEnabled).isFalse() getVpnChangeCallback().onAvailable(mockNetwork) assertThat(vpnState?.isEnabled).isTrue() } @Test fun isVpnEnabled_whenVpnDisconnected_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isEnabled).isTrue() whenever(kosmos.vpnManager.getVpnConfig(anyInt())).thenReturn(null) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isEnabled).isFalse() } @Test fun isVpnBranded_whenVpnIsBrandedSystemApp_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(brandedSystemAppInfo) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isTrue() } @Test fun isVpnBranded_whenVpnIsBrandedNonSystemApp_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(brandedNonSystemAppInfo) // Use non-system app getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isFalse() } @Test fun isVpnBranded_whenVpnIsNotBranded_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(nonBrandedAppInfo) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isFalse() } @Test fun isVpnValidated_whenVpnIsValidated_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isValidated).isTrue() } @Test fun isVpnValidated_whenVpnIsNotValidated_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockNonValidatedCapabilities) assertThat(vpnState?.isValidated).isFalse() } @Test fun isVpnEnabled_whenVpnEnabledForOtherUser_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) assertThat(vpnState?.isEnabled).isFalse() setupMultipleUsers() setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig) getVpnChangeCallback().onAvailable(mockNetwork) assertThat(vpnState?.isEnabled).isFalse() } @Test fun isVpnBranded_whenBrandedVpnOnOtherUser_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) setupAppInfo(brandedAppInfo) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isFalse() } @Test fun isVpnValidated_whenVpnOnOtherUserIsValidated_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id, secondaryUser.id)) setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isValidated).isTrue() } @Test fun isVpnValidated_whenVpnOnOtherUserIsNotValidated_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id, secondaryUser.id)) setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockNonValidatedCapabilities) assertThat(vpnState?.isValidated).isFalse() } @Test fun isVpnValidated_whenNoEnabledProfiles_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf()) setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockNonValidatedCapabilities) assertThat(vpnState?.isValidated).isTrue() } @Test fun vpnState_combinesAllStates() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) assertThat(vpnState?.isEnabled).isFalse() assertThat(vpnState?.isBranded).isFalse() assertThat(vpnState?.isValidated).isFalse() setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(brandedSystemAppInfo) val callback = getVpnChangeCallback() callback.onAvailable(mockNetwork) callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isEnabled).isTrue() assertThat(vpnState?.isBranded).isTrue() assertThat(vpnState?.isValidated).isTrue() } private fun setupVpnConfig(userId: Int, config: VpnConfig?) { whenever(kosmos.vpnManager.getVpnConfig(userId)).thenReturn(config) } private fun setupAppInfo(appInfo: android.content.pm.ApplicationInfo) { whenever(kosmos.packageManager.getApplicationInfo(anyString(), anyInt())) .thenReturn(appInfo) } private fun setupMultipleUsers() { whenever(kosmos.userManager.users).thenReturn(listOf(primaryUser, secondaryUser)) whenever(kosmos.userManager.getProfileIdsWithDisabled(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) } private fun getVpnChangeCallback(): ConnectivityManager.NetworkCallback { val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() val requestCaptor = argumentCaptor<NetworkRequest>() verify(kosmos.connectivityManager, atLeastOnce()) .registerNetworkCallback(requestCaptor.capture(), callbackCaptor.capture()) return callbackCaptor.lastValue } companion object { private const val IS_BRANDED_KEY = "com.android.systemui.IS_BRANDED" private val primaryUser = android.content.pm.UserInfo(0, "primary", 0) private val secondaryUser = android.content.pm.UserInfo(10, "secondary", 0) private val brandedAppInfo = android.content.pm.ApplicationInfo().apply { metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, true) } } private val nonBrandedAppInfo = android.content.pm.ApplicationInfo().apply { metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, false) } } private val brandedSystemAppInfo = android.content.pm.ApplicationInfo().apply { flags = android.content.pm.ApplicationInfo.FLAG_SYSTEM metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, true) } } private val brandedNonSystemAppInfo = android.content.pm.ApplicationInfo().apply { flags = 0 // Not a system app metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, true) } } private val testVpnConfig = VpnConfig().apply { user = "testuser" interfaze = "test_interface" } } } packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/systemstatusicons/ui/viewmodel/SystemStatusIconsViewModelTest.kt +11 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,8 @@ import com.android.systemui.statusbar.policy.bluetooth.data.repository.bluetooth import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.statusbar.policy.fakeHotspotController import com.android.systemui.statusbar.policy.fakeNextAlarmController import com.android.systemui.statusbar.policy.vpn.data.repository.vpnRepository import com.android.systemui.statusbar.policy.vpn.shared.model.VpnState import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose import com.android.systemui.statusbar.systemstatusicons.data.repository.statusBarConfigIconSlotNames import com.android.systemui.testKosmos Loading Loading @@ -79,6 +81,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { private lateinit var slotMute: String private lateinit var slotNextAlarm: String private lateinit var slotVibrate: String private lateinit var slotVpn: String private lateinit var slotWifi: String private lateinit var slotZen: String Loading @@ -93,6 +96,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { slotMute = context.getString(com.android.internal.R.string.status_bar_mute) slotNextAlarm = context.getString(com.android.internal.R.string.status_bar_alarm_clock) slotVibrate = context.getString(com.android.internal.R.string.status_bar_volume) slotVpn = context.getString(com.android.internal.R.string.status_bar_vpn) slotWifi = context.getString(com.android.internal.R.string.status_bar_wifi) slotZen = context.getString(com.android.internal.R.string.status_bar_zen) } Loading Loading @@ -194,6 +198,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { showEthernet() showVibrate() showHotspot() showVpn() assertThat(underTest.activeSlotNames) .containsExactly( Loading @@ -204,6 +209,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { slotHotspot, slotNextAlarm, slotVibrate, slotVpn, slotZen, ) .inOrder() Loading @@ -221,6 +227,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { slotHotspot, slotMute, slotNextAlarm, slotVpn, slotWifi, slotZen, ) Loading Loading @@ -304,4 +311,8 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { private fun Kosmos.showHotspot() { fakeHotspotController.isHotspotEnabled = true } private fun Kosmos.showVpn() { vpnRepository.vpnState.value = VpnState(isEnabled = true) } } packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/systemstatusicons/vpn/ui/viewmodel/VpnIconViewModelTest.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with 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.android.systemui.statusbar.systemstatusicons.vpn.ui.viewmodel import android.content.testableContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.statusbar.policy.vpn.data.repository.vpnRepository import com.android.systemui.statusbar.policy.vpn.shared.model.VpnState import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class VpnIconViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest = kosmos.vpnIconViewModelFactory.create(kosmos.testableContext).apply { activateIn(kosmos.testScope) } @Test fun visible_isFalse_byDefault() = kosmos.runTest { assertThat(underTest.visible).isFalse() } @Test fun visible_vpnIsEnabled_isTrue() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true) assertThat(underTest.visible).isTrue() } @Test fun visible_vpnStateChanges_flips() = kosmos.runTest { assertThat(underTest.visible).isFalse() vpnRepository.vpnState.value = VpnState(isEnabled = true) assertThat(underTest.visible).isTrue() vpnRepository.vpnState.value = VpnState(isEnabled = false) assertThat(underTest.visible).isFalse() } @Test fun icon_notVisible_isNull() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = false) assertThat(underTest.icon).isNull() } @Test fun notBranded_validated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = false, isValidated = true) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_STANDARD_VALIDATED) } @Test fun notBranded_notValidated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = false, isValidated = false) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_STANDARD_NOT_VALIDATED) } @Test fun branded_validated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = true, isValidated = true) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_BRANDED_VALIDATED) } @Test fun branded_notValidated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = true, isValidated = false) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_BRANDED_NOT_VALIDATED) } companion object { private val BASE_CONTENT_DESCRIPTION = ContentDescription.Resource(R.string.accessibility_vpn_on) private val EXPECTED_ICON_STANDARD_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_vpn_ic, contentDescription = BASE_CONTENT_DESCRIPTION, ) private val EXPECTED_ICON_STANDARD_NOT_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_no_internet_vpn_ic, contentDescription = BASE_CONTENT_DESCRIPTION, ) private val EXPECTED_ICON_BRANDED_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_branded_vpn, contentDescription = BASE_CONTENT_DESCRIPTION, ) private val EXPECTED_ICON_BRANDED_NOT_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_no_internet_branded_vpn, contentDescription = BASE_CONTENT_DESCRIPTION, ) } } packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +7 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ import android.media.session.MediaSessionManager; import android.nearby.NearbyManager; import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.VpnManager; import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.IDeviceIdleController; Loading Loading @@ -218,6 +219,12 @@ public class FrameworkServicesModule { return context.getSystemService(ConnectivityManager.class); } @Provides @Singleton static VpnManager provideVpnManager(Context context) { return context.getSystemService(VpnManager.class); } @Provides @Singleton static ContentResolver provideContentResolver(Context context) { Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +13 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback; import com.android.systemui.statusbar.policy.vpn.data.repository.VpnRepository; import com.android.systemui.supervision.data.model.SupervisionModel; public interface SecurityController extends CallbackController<SecurityControllerCallback>, Loading @@ -48,11 +49,21 @@ public interface SecurityController extends CallbackController<SecurityControlle @Deprecated int getDeviceOwnerType(ComponentName admin); boolean isNetworkLoggingEnabled(); /** @deprecated Use {@link VpnRepository#getVpnState()} instead. */ @Deprecated boolean isVpnEnabled(); boolean isVpnRestricted(); /** Whether the VPN network is validated. */ /** * Whether the VPN network is validated. * @deprecated Use {@link VpnRepository#getVpnState()} instead. */ @Deprecated boolean isVpnValidated(); /** Whether the VPN app should use branded VPN iconography. */ /** * Whether the VPN app should use branded VPN iconography. * @deprecated Use {@link VpnRepository#getVpnState()} instead. */ @Deprecated boolean isVpnBranded(); String getPrimaryVpnName(); String getWorkProfileVpnName(); Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/vpn/data/repository/impl/VpnRepositoryImplTest.kt 0 → 100644 +350 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * 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.android.systemui.statusbar.policy.vpn.data.repository.impl import android.content.packageManager import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest import android.net.connectivityManager import android.net.vpnManager import android.os.userManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.net.VpnConfig import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.policy.vpn.data.repository.realVpnRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyString import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class VpnRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest by lazy { kosmos.realVpnRepository } private val mockNetwork: Network = mock() private val mockValidatedCapabilities: NetworkCapabilities = mock<NetworkCapabilities>().apply { whenever(hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)).thenReturn(true) } private val mockNonValidatedCapabilities: NetworkCapabilities = mock<NetworkCapabilities>().apply { whenever(hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)).thenReturn(false) } private val mockLinkProperties: android.net.LinkProperties = mock<android.net.LinkProperties>().apply { whenever(interfaceName).thenReturn("test_interface") } @Before fun setUp() { whenever(kosmos.userManager.users).thenReturn(listOf(primaryUser)) whenever(kosmos.userManager.getProfileIdsWithDisabled(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) whenever(kosmos.connectivityManager.allNetworks).thenReturn(arrayOf(mockNetwork)) whenever(kosmos.connectivityManager.getLinkProperties(mockNetwork)) .thenReturn(mockLinkProperties) whenever(kosmos.connectivityManager.getNetworkCapabilities(mockNetwork)) .thenReturn(mockNonValidatedCapabilities) whenever(kosmos.packageManager.getApplicationInfo(anyString(), anyInt())) .thenReturn(nonBrandedAppInfo) } @Test fun isVpnEnabled_whenVpnConnected_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) assertThat(vpnState?.isEnabled).isFalse() getVpnChangeCallback().onAvailable(mockNetwork) assertThat(vpnState?.isEnabled).isTrue() } @Test fun isVpnEnabled_whenVpnDisconnected_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isEnabled).isTrue() whenever(kosmos.vpnManager.getVpnConfig(anyInt())).thenReturn(null) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isEnabled).isFalse() } @Test fun isVpnBranded_whenVpnIsBrandedSystemApp_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(brandedSystemAppInfo) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isTrue() } @Test fun isVpnBranded_whenVpnIsBrandedNonSystemApp_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(brandedNonSystemAppInfo) // Use non-system app getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isFalse() } @Test fun isVpnBranded_whenVpnIsNotBranded_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(nonBrandedAppInfo) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isFalse() } @Test fun isVpnValidated_whenVpnIsValidated_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isValidated).isTrue() } @Test fun isVpnValidated_whenVpnIsNotValidated_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupVpnConfig(primaryUser.id, testVpnConfig) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockNonValidatedCapabilities) assertThat(vpnState?.isValidated).isFalse() } @Test fun isVpnEnabled_whenVpnEnabledForOtherUser_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) assertThat(vpnState?.isEnabled).isFalse() setupMultipleUsers() setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig) getVpnChangeCallback().onAvailable(mockNetwork) assertThat(vpnState?.isEnabled).isFalse() } @Test fun isVpnBranded_whenBrandedVpnOnOtherUser_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) setupAppInfo(brandedAppInfo) getVpnChangeCallback().onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isBranded).isFalse() } @Test fun isVpnValidated_whenVpnOnOtherUserIsValidated_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id, secondaryUser.id)) setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isValidated).isTrue() } @Test fun isVpnValidated_whenVpnOnOtherUserIsNotValidated_isFalse() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id, secondaryUser.id)) setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockNonValidatedCapabilities) assertThat(vpnState?.isValidated).isFalse() } @Test fun isVpnValidated_whenNoEnabledProfiles_isTrue() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) setupMultipleUsers() whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf()) setupVpnConfig(primaryUser.id, null) setupVpnConfig(secondaryUser.id, testVpnConfig.apply { user = secondaryUser.name }) val callback = getVpnChangeCallback() callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockNonValidatedCapabilities) assertThat(vpnState?.isValidated).isTrue() } @Test fun vpnState_combinesAllStates() = kosmos.runTest { val vpnState by collectLastValue(underTest.vpnState) assertThat(vpnState?.isEnabled).isFalse() assertThat(vpnState?.isBranded).isFalse() assertThat(vpnState?.isValidated).isFalse() setupVpnConfig(primaryUser.id, testVpnConfig) setupAppInfo(brandedSystemAppInfo) val callback = getVpnChangeCallback() callback.onAvailable(mockNetwork) callback.onLinkPropertiesChanged(mockNetwork, mockLinkProperties) callback.onCapabilitiesChanged(mockNetwork, mockValidatedCapabilities) assertThat(vpnState?.isEnabled).isTrue() assertThat(vpnState?.isBranded).isTrue() assertThat(vpnState?.isValidated).isTrue() } private fun setupVpnConfig(userId: Int, config: VpnConfig?) { whenever(kosmos.vpnManager.getVpnConfig(userId)).thenReturn(config) } private fun setupAppInfo(appInfo: android.content.pm.ApplicationInfo) { whenever(kosmos.packageManager.getApplicationInfo(anyString(), anyInt())) .thenReturn(appInfo) } private fun setupMultipleUsers() { whenever(kosmos.userManager.users).thenReturn(listOf(primaryUser, secondaryUser)) whenever(kosmos.userManager.getProfileIdsWithDisabled(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) whenever(kosmos.userManager.getEnabledProfileIds(primaryUser.id)) .thenReturn(intArrayOf(primaryUser.id)) } private fun getVpnChangeCallback(): ConnectivityManager.NetworkCallback { val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() val requestCaptor = argumentCaptor<NetworkRequest>() verify(kosmos.connectivityManager, atLeastOnce()) .registerNetworkCallback(requestCaptor.capture(), callbackCaptor.capture()) return callbackCaptor.lastValue } companion object { private const val IS_BRANDED_KEY = "com.android.systemui.IS_BRANDED" private val primaryUser = android.content.pm.UserInfo(0, "primary", 0) private val secondaryUser = android.content.pm.UserInfo(10, "secondary", 0) private val brandedAppInfo = android.content.pm.ApplicationInfo().apply { metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, true) } } private val nonBrandedAppInfo = android.content.pm.ApplicationInfo().apply { metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, false) } } private val brandedSystemAppInfo = android.content.pm.ApplicationInfo().apply { flags = android.content.pm.ApplicationInfo.FLAG_SYSTEM metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, true) } } private val brandedNonSystemAppInfo = android.content.pm.ApplicationInfo().apply { flags = 0 // Not a system app metaData = android.os.Bundle().apply { putBoolean(IS_BRANDED_KEY, true) } } private val testVpnConfig = VpnConfig().apply { user = "testuser" interfaze = "test_interface" } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/systemstatusicons/ui/viewmodel/SystemStatusIconsViewModelTest.kt +11 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,8 @@ import com.android.systemui.statusbar.policy.bluetooth.data.repository.bluetooth import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.statusbar.policy.fakeHotspotController import com.android.systemui.statusbar.policy.fakeNextAlarmController import com.android.systemui.statusbar.policy.vpn.data.repository.vpnRepository import com.android.systemui.statusbar.policy.vpn.shared.model.VpnState import com.android.systemui.statusbar.systemstatusicons.SystemStatusIconsInCompose import com.android.systemui.statusbar.systemstatusicons.data.repository.statusBarConfigIconSlotNames import com.android.systemui.testKosmos Loading Loading @@ -79,6 +81,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { private lateinit var slotMute: String private lateinit var slotNextAlarm: String private lateinit var slotVibrate: String private lateinit var slotVpn: String private lateinit var slotWifi: String private lateinit var slotZen: String Loading @@ -93,6 +96,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { slotMute = context.getString(com.android.internal.R.string.status_bar_mute) slotNextAlarm = context.getString(com.android.internal.R.string.status_bar_alarm_clock) slotVibrate = context.getString(com.android.internal.R.string.status_bar_volume) slotVpn = context.getString(com.android.internal.R.string.status_bar_vpn) slotWifi = context.getString(com.android.internal.R.string.status_bar_wifi) slotZen = context.getString(com.android.internal.R.string.status_bar_zen) } Loading Loading @@ -194,6 +198,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { showEthernet() showVibrate() showHotspot() showVpn() assertThat(underTest.activeSlotNames) .containsExactly( Loading @@ -204,6 +209,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { slotHotspot, slotNextAlarm, slotVibrate, slotVpn, slotZen, ) .inOrder() Loading @@ -221,6 +227,7 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { slotHotspot, slotMute, slotNextAlarm, slotVpn, slotWifi, slotZen, ) Loading Loading @@ -304,4 +311,8 @@ class SystemStatusIconsViewModelTest : SysuiTestCase() { private fun Kosmos.showHotspot() { fakeHotspotController.isHotspotEnabled = true } private fun Kosmos.showVpn() { vpnRepository.vpnState.value = VpnState(isEnabled = true) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/systemstatusicons/vpn/ui/viewmodel/VpnIconViewModelTest.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with 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.android.systemui.statusbar.systemstatusicons.vpn.ui.viewmodel import android.content.testableContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.statusbar.policy.vpn.data.repository.vpnRepository import com.android.systemui.statusbar.policy.vpn.shared.model.VpnState import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @android.platform.test.annotations.EnabledOnRavenwood class VpnIconViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest = kosmos.vpnIconViewModelFactory.create(kosmos.testableContext).apply { activateIn(kosmos.testScope) } @Test fun visible_isFalse_byDefault() = kosmos.runTest { assertThat(underTest.visible).isFalse() } @Test fun visible_vpnIsEnabled_isTrue() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true) assertThat(underTest.visible).isTrue() } @Test fun visible_vpnStateChanges_flips() = kosmos.runTest { assertThat(underTest.visible).isFalse() vpnRepository.vpnState.value = VpnState(isEnabled = true) assertThat(underTest.visible).isTrue() vpnRepository.vpnState.value = VpnState(isEnabled = false) assertThat(underTest.visible).isFalse() } @Test fun icon_notVisible_isNull() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = false) assertThat(underTest.icon).isNull() } @Test fun notBranded_validated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = false, isValidated = true) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_STANDARD_VALIDATED) } @Test fun notBranded_notValidated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = false, isValidated = false) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_STANDARD_NOT_VALIDATED) } @Test fun branded_validated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = true, isValidated = true) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_BRANDED_VALIDATED) } @Test fun branded_notValidated_hasExpectedIcon() = kosmos.runTest { vpnRepository.vpnState.value = VpnState(isEnabled = true, isBranded = true, isValidated = false) assertThat(underTest.icon).isEqualTo(EXPECTED_ICON_BRANDED_NOT_VALIDATED) } companion object { private val BASE_CONTENT_DESCRIPTION = ContentDescription.Resource(R.string.accessibility_vpn_on) private val EXPECTED_ICON_STANDARD_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_vpn_ic, contentDescription = BASE_CONTENT_DESCRIPTION, ) private val EXPECTED_ICON_STANDARD_NOT_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_no_internet_vpn_ic, contentDescription = BASE_CONTENT_DESCRIPTION, ) private val EXPECTED_ICON_BRANDED_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_branded_vpn, contentDescription = BASE_CONTENT_DESCRIPTION, ) private val EXPECTED_ICON_BRANDED_NOT_VALIDATED = Icon.Resource( res = R.drawable.stat_sys_no_internet_branded_vpn, contentDescription = BASE_CONTENT_DESCRIPTION, ) } }
packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +7 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ import android.media.session.MediaSessionManager; import android.nearby.NearbyManager; import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.VpnManager; import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.IDeviceIdleController; Loading Loading @@ -218,6 +219,12 @@ public class FrameworkServicesModule { return context.getSystemService(ConnectivityManager.class); } @Provides @Singleton static VpnManager provideVpnManager(Context context) { return context.getSystemService(VpnManager.class); } @Provides @Singleton static ContentResolver provideContentResolver(Context context) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +13 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback; import com.android.systemui.statusbar.policy.vpn.data.repository.VpnRepository; import com.android.systemui.supervision.data.model.SupervisionModel; public interface SecurityController extends CallbackController<SecurityControllerCallback>, Loading @@ -48,11 +49,21 @@ public interface SecurityController extends CallbackController<SecurityControlle @Deprecated int getDeviceOwnerType(ComponentName admin); boolean isNetworkLoggingEnabled(); /** @deprecated Use {@link VpnRepository#getVpnState()} instead. */ @Deprecated boolean isVpnEnabled(); boolean isVpnRestricted(); /** Whether the VPN network is validated. */ /** * Whether the VPN network is validated. * @deprecated Use {@link VpnRepository#getVpnState()} instead. */ @Deprecated boolean isVpnValidated(); /** Whether the VPN app should use branded VPN iconography. */ /** * Whether the VPN app should use branded VPN iconography. * @deprecated Use {@link VpnRepository#getVpnState()} instead. */ @Deprecated boolean isVpnBranded(); String getPrimaryVpnName(); String getWorkProfileVpnName(); Loading