From 75b75d446c1e7daa9f51eb64ee977622a40af64a Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Mon, 23 May 2022 05:45:28 +0000 Subject: [PATCH] Make VPN by Google One always appear in VPN settings Bug: 233559781 Test: manually test Change-Id: I175ab126ff92f773ab25a1fa64e4262b324fd353 --- .../vpn2/AdvancedVpnFeatureProvider.java | 5 + .../vpn2/AdvancedVpnFeatureProviderImpl.java | 5 + .../settings/vpn2/AppManagementFragment.java | 22 +++- .../android/settings/vpn2/VpnSettings.java | 23 +++- .../vpn2/AppManagementFragmentTest.java | 102 ++++++++++++++++++ .../settings/vpn2/VpnSettingsTest.java | 39 +++++++ 6 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java diff --git a/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java b/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java index c5702a26378..cb56c351448 100644 --- a/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java +++ b/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java @@ -42,4 +42,9 @@ public interface AdvancedVpnFeatureProvider { * Returns the title of vpn preference group. */ String getVpnPreferenceGroupTitle(Context context); + + /** + * Returns {@code true} advanced vpn is removable. + */ + boolean isAdvancedVpnRemovable(); } diff --git a/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java b/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java index 0bfaf646ba7..c5bc69c042d 100644 --- a/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java +++ b/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java @@ -41,4 +41,9 @@ public class AdvancedVpnFeatureProviderImpl implements AdvancedVpnFeatureProvide public String getVpnPreferenceGroupTitle(Context context) { return null; } + + @Override + public boolean isAdvancedVpnRemovable() { + return true; + } } diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java index d4ee5b9c476..d2fa5fccbfc 100644 --- a/src/com/android/settings/vpn2/AppManagementFragment.java +++ b/src/com/android/settings/vpn2/AppManagementFragment.java @@ -48,6 +48,7 @@ import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedPreference; @@ -71,6 +72,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment private PackageManager mPackageManager; private DevicePolicyManager mDevicePolicyManager; private VpnManager mVpnManager; + private AdvancedVpnFeatureProvider mFeatureProvider; // VPN app info private final int mUserId = UserHandle.myUserId(); @@ -122,6 +124,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment mPackageManager = getContext().getPackageManager(); mDevicePolicyManager = getContext().getSystemService(DevicePolicyManager.class); mVpnManager = getContext().getSystemService(VpnManager.class); + mFeatureProvider = FeatureFactory.getFactory(getContext()).getAdvancedVpnFeatureProvider(); mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN); mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN); @@ -283,7 +286,16 @@ public class AppManagementFragment extends SettingsPreferenceFragment } } - private void updateRestrictedViews() { + @VisibleForTesting + void updateRestrictedViews() { + if (mFeatureProvider.isAdvancedVpnSupported(getContext()) + && !mFeatureProvider.isAdvancedVpnRemovable() + && TextUtils.equals(mPackageName, mFeatureProvider.getAdvancedVpnPackageName())) { + mPreferenceForget.setVisible(false); + } else { + mPreferenceForget.setVisible(true); + } + if (isAdded()) { mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, mUserId); @@ -314,6 +326,14 @@ public class AppManagementFragment extends SettingsPreferenceFragment } } + @VisibleForTesting + void init(String packageName, AdvancedVpnFeatureProvider featureProvider, + RestrictedPreference preference) { + mPackageName = packageName; + mFeatureProvider = featureProvider; + mPreferenceForget = preference; + } + private String getAlwaysOnVpnPackage() { return mVpnManager.getAlwaysOnVpnPackageForUser(mUserId); } diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java index 5712b3fd1cd..43805956b6e 100644 --- a/src/com/android/settings/vpn2/VpnSettings.java +++ b/src/com/android/settings/vpn2/VpnSettings.java @@ -241,7 +241,8 @@ public class VpnSettings extends RestrictedSettingsFragment implements // Run heavy RPCs before switching to UI thread final List vpnProfiles = loadVpnProfiles(); - final List vpnApps = getVpnApps(context, /* includeProfiles */ true); + final List vpnApps = getVpnApps(context, /* includeProfiles */ true, + mFeatureProvider); final Map connectedLegacyVpns = getConnectedLegacyVpns(); final Set connectedAppVpns = getConnectedAppVpns(); @@ -571,7 +572,15 @@ public class VpnSettings extends RestrictedSettingsFragment implements return result; } - static List getVpnApps(Context context, boolean includeProfiles) { + static List getVpnApps(Context context, boolean includeProfiles, + AdvancedVpnFeatureProvider featureProvider) { + return getVpnApps(context, includeProfiles, featureProvider, + context.getSystemService(AppOpsManager.class)); + } + + @VisibleForTesting + static List getVpnApps(Context context, boolean includeProfiles, + AdvancedVpnFeatureProvider featureProvider, AppOpsManager aom) { List result = Lists.newArrayList(); final Set profileIds; @@ -584,8 +593,6 @@ public class VpnSettings extends RestrictedSettingsFragment implements profileIds = Collections.singleton(UserHandle.myUserId()); } - // Fetch VPN-enabled apps from AppOps. - AppOpsManager aom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); List apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN, OP_ACTIVATE_PLATFORM_VPN}); if (apps != null) { @@ -603,7 +610,7 @@ public class VpnSettings extends RestrictedSettingsFragment implements allowed = true; } } - if (allowed) { + if (allowed || isAdvancedVpn(featureProvider, pkg.getPackageName(), context)) { result.add(new AppVpnInfo(userId, pkg.getPackageName())); } } @@ -613,6 +620,12 @@ public class VpnSettings extends RestrictedSettingsFragment implements return result; } + private static boolean isAdvancedVpn(AdvancedVpnFeatureProvider featureProvider, + String packageName, Context context) { + return featureProvider.isAdvancedVpnSupported(context) + && TextUtils.equals(packageName, featureProvider.getAdvancedVpnPackageName()); + } + private static List loadVpnProfiles() { final ArrayList result = Lists.newArrayList(); diff --git a/tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java b/tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java new file mode 100644 index 00000000000..80bb39372c0 --- /dev/null +++ b/tests/unit/src/com/android/settings/vpn2/AppManagementFragmentTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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.settings.vpn2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Looper; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.RestrictedPreference; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class AppManagementFragmentTest { + private static final String FAKE_PACKAGE_NAME = "com.fake.package.name"; + private static final String ADVANCED_VPN_GROUP_PACKAGE_NAME = "com.advanced.package.name"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private AppManagementFragment mFragment; + private Context mContext; + private FakeFeatureFactory mFakeFeatureFactory; + private RestrictedPreference mPreferenceForget; + + @Before + @UiThreadTest + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFragment = spy(new AppManagementFragment()); + mContext = spy(ApplicationProvider.getApplicationContext()); + mPreferenceForget = new RestrictedPreference(mContext); + + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); + mFragment.init(ADVANCED_VPN_GROUP_PACKAGE_NAME, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mPreferenceForget); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getAdvancedVpnPackageName()) + .thenReturn(ADVANCED_VPN_GROUP_PACKAGE_NAME); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnSupported(any())) + .thenReturn(true); + } + + @Test + public void updateRestrictedViews_isAdvancedVpn_hidesForgetPreference() { + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnRemovable()) + .thenReturn(false); + mFragment.updateRestrictedViews(); + assertThat(mPreferenceForget.isVisible()).isFalse(); + } + + @Test + public void updateRestrictedViews_isNotAdvancedVpn_showsForgetPreference() { + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnRemovable()) + .thenReturn(false); + mFragment.init(FAKE_PACKAGE_NAME, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mPreferenceForget); + mFragment.updateRestrictedViews(); + assertThat(mPreferenceForget.isVisible()).isTrue(); + } + + @Test + public void updateRestrictedViews_isAdvancedVpnRemovable_showsForgetPreference() { + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnRemovable()) + .thenReturn(true); + mFragment.init(FAKE_PACKAGE_NAME, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mPreferenceForget); + mFragment.updateRestrictedViews(); + assertThat(mPreferenceForget.isVisible()).isTrue(); + } +} diff --git a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java index 1b3b49c63c3..86bd1e741a2 100644 --- a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java +++ b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Looper; @@ -45,9 +46,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; +import java.util.List; import java.util.Set; @RunWith(AndroidJUnit4.class) @@ -63,6 +67,9 @@ public class VpnSettingsTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + private AppOpsManager mAppOpsManager; + private VpnSettings mVpnSettings; private Context mContext; private PreferenceManager mPreferenceManager; @@ -98,6 +105,8 @@ public class VpnSettingsTest { .thenReturn(VPN_GROUP_TITLE); when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getAdvancedVpnPackageName()) .thenReturn(ADVANCED_VPN_GROUP_PACKAGE_NAME); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnSupported(any())) + .thenReturn(true); doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); doReturn(mContext).when(mContext).createPackageContextAsUser(any(), anyInt(), any()); doReturn(mPreferenceManager).when(mVpnGroup).getPreferenceManager(); @@ -143,4 +152,34 @@ public class VpnSettingsTest { assertThat(mAdvancedVpnGroup.isVisible()).isFalse(); assertThat(mVpnGroup.isVisible()).isFalse(); } + + @Test + public void getVpnApps_isAdvancedVpn_returnsOne() { + int uid = 1111; + List opEntries = new ArrayList<>(); + List apps = new ArrayList<>(); + AppOpsManager.PackageOps packageOps = + new AppOpsManager.PackageOps(ADVANCED_VPN_GROUP_PACKAGE_NAME, uid, opEntries); + apps.add(packageOps); + when(mAppOpsManager.getPackagesForOps((int[]) any())).thenReturn(apps); + + assertThat(VpnSettings.getVpnApps(mContext, /* includeProfiles= */ false, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), + mAppOpsManager).size()).isEqualTo(1); + } + + @Test + public void getVpnApps_isNotAdvancedVpn_returnsEmpty() { + int uid = 1111; + List opEntries = new ArrayList<>(); + List apps = new ArrayList<>(); + AppOpsManager.PackageOps packageOps = + new AppOpsManager.PackageOps(FAKE_PACKAGE_NAME, uid, opEntries); + apps.add(packageOps); + when(mAppOpsManager.getPackagesForOps((int[]) any())).thenReturn(apps); + + assertThat(VpnSettings.getVpnApps(mContext, /* includeProfiles= */ false, + mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), + mAppOpsManager)).isEmpty(); + } }