diff --git a/res/values/strings.xml b/res/values/strings.xml index 9f8df7b6467..7cfb966837c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -184,6 +184,11 @@ bluetooth + + + Pair right ear + + Pair left ear Pair your other ear diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index f330b19e2a1..efb2bf7036b 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -42,6 +42,12 @@ settings:searchable="false" settings:controller="com.android.settings.bluetooth.LeAudioBluetoothDetailsHeaderController"/> + + + diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceController.java index 1c787babad2..6a641b30fcc 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceController.java @@ -18,10 +18,11 @@ import android.content.Context; import android.os.Build; import android.service.notification.NotificationListenerFilter; +import androidx.preference.Preference; + import com.android.settings.core.BasePreferenceController; import com.android.settings.notification.NotificationBackend; - public class BridgedAppsLinkPreferenceController extends BasePreferenceController { private ComponentName mCn; @@ -61,7 +62,6 @@ public class BridgedAppsLinkPreferenceController extends BasePreferenceControlle if (mTargetSdk > Build.VERSION_CODES.S) { return AVAILABLE; } - mNlf = mNm.getListenerFilter(mCn, mUserId); if (!mNlf.areAllTypesAllowed() || !mNlf.getDisallowedPackages().isEmpty()) { return AVAILABLE; @@ -69,4 +69,10 @@ public class BridgedAppsLinkPreferenceController extends BasePreferenceControlle } return DISABLED_DEPENDENT_SETTING; } + + @Override + public void updateState(Preference pref) { + pref.setEnabled(getAvailabilityStatus() == AVAILABLE); + super.updateState(pref); + } } diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index da25f17c138..e6feebb92ab 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -233,11 +233,7 @@ public class NotificationAccessDetails extends DashboardFragment { apc.updateState(screen.findPreference(apc.getPreferenceKey())); getPreferenceControllers().forEach(controllers -> { controllers.forEach(controller -> { - if (controller instanceof TypeFilterPreferenceController) { - TypeFilterPreferenceController tfpc = - (TypeFilterPreferenceController) controller; - tfpc.updateState(screen.findPreference(tfpc.getPreferenceKey())); - } + controller.updateState(screen.findPreference(controller.getPreferenceKey())); }); }); } @@ -249,11 +245,7 @@ public class NotificationAccessDetails extends DashboardFragment { apc.updateState(screen.findPreference(apc.getPreferenceKey())); getPreferenceControllers().forEach(controllers -> { controllers.forEach(controller -> { - if (controller instanceof TypeFilterPreferenceController) { - TypeFilterPreferenceController tfpc = - (TypeFilterPreferenceController) controller; - tfpc.updateState(screen.findPreference(tfpc.getPreferenceKey())); - } + controller.updateState(screen.findPreference(controller.getPreferenceKey())); }); }); } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java b/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java new file mode 100644 index 00000000000..d14a9b1144a --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.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.bluetooth; + +import android.content.Context; + +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.ButtonPreference; + +/** + * This class handles button preference logic to display for hearing aid device. + */ +public class BluetoothDetailsPairOtherController extends BluetoothDetailsController { + private static final String KEY_PAIR_OTHER = "hearing_aid_pair_other_button"; + + private ButtonPreference mPreference; + + public BluetoothDetailsPairOtherController(Context context, + PreferenceFragmentCompat fragment, + CachedBluetoothDevice device, + Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + lifecycle.addObserver(this); + } + + @Override + public boolean isAvailable() { + return getButtonPreferenceVisibility(mCachedDevice); + } + + @Override + public String getPreferenceKey() { + return KEY_PAIR_OTHER; + } + + @Override + protected void init(PreferenceScreen screen) { + final int side = mCachedDevice.getDeviceSide(); + final int stringRes = (side == HearingAidProfile.DeviceSide.SIDE_LEFT) + ? R.string.bluetooth_pair_right_ear_button + : R.string.bluetooth_pair_left_ear_button; + + mPreference = screen.findPreference(getPreferenceKey()); + mPreference.setTitle(stringRes); + mPreference.setOnClickListener(v -> launchPairingDetail()); + } + + @Override + protected void refresh() { + mPreference.setVisible(getButtonPreferenceVisibility(mCachedDevice)); + } + + private boolean getButtonPreferenceVisibility(CachedBluetoothDevice cachedDevice) { + return isBinauralMode(cachedDevice) && isOnlyOneSideConnected(cachedDevice); + } + + private void launchPairingDetail() { + new SubSettingLauncher(mContext) + .setDestination(BluetoothPairingDetail.class.getName()) + .setSourceMetricsCategory( + ((BluetoothDeviceDetailsFragment) mFragment).getMetricsCategory()) + .launch(); + } + + private boolean isBinauralMode(CachedBluetoothDevice cachedDevice) { + return cachedDevice.getDeviceMode() == HearingAidProfile.DeviceMode.MODE_BINAURAL; + } + + private boolean isOnlyOneSideConnected(CachedBluetoothDevice cachedDevice) { + if (!cachedDevice.isConnectedHearingAidDevice()) { + return false; + } + + final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + if (subDevice != null && subDevice.isConnectedHearingAidDevice()) { + return false; + } + + return true; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index c118a43131d..999e34da6bf 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -251,6 +251,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment lifecycle)); controllers.add(new BluetoothDetailsRelatedToolsController(context, this, mCachedDevice, lifecycle)); + controllers.add(new BluetoothDetailsPairOtherController(context, this, mCachedDevice, + lifecycle)); } return controllers; } diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java index f888ea7c8e0..19222612f3a 100644 --- a/src/com/android/settings/notification/ConfigureNotificationSettings.java +++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java @@ -107,7 +107,6 @@ public class ConfigureNotificationSettings extends DashboardFragment implements mNotificationAssistantPreferenceController = use(NotificationAssistantPreferenceController.class); mNotificationAssistantPreferenceController.setFragment(this); - mNotificationAssistantPreferenceController.setBackend(new NotificationBackend()); } private static List buildPreferenceControllers(Context context, diff --git a/src/com/android/settings/notification/NotificationAssistantPreferenceController.java b/src/com/android/settings/notification/NotificationAssistantPreferenceController.java index 91031c8e4de..a6179e5306f 100644 --- a/src/com/android/settings/notification/NotificationAssistantPreferenceController.java +++ b/src/com/android/settings/notification/NotificationAssistantPreferenceController.java @@ -44,6 +44,7 @@ public class NotificationAssistantPreferenceController extends TogglePreferenceC public NotificationAssistantPreferenceController(Context context) { super(context, KEY_NAS); mUserManager = UserManager.get(context); + mNotificationBackend = new NotificationBackend(); } @Override @@ -101,4 +102,9 @@ public class NotificationAssistantPreferenceController extends TogglePreferenceC void setBackend(NotificationBackend backend) { mNotificationBackend = backend; } -} \ No newline at end of file + + @Override + public boolean isSliceable() { + return (mFragment != null && mFragment instanceof ConfigureNotificationSettings); + } +} diff --git a/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java b/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java index cb56c351448..962b6c2e53b 100644 --- a/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java +++ b/src/com/android/settings/vpn2/AdvancedVpnFeatureProvider.java @@ -47,4 +47,9 @@ public interface AdvancedVpnFeatureProvider { * Returns {@code true} advanced vpn is removable. */ boolean isAdvancedVpnRemovable(); + + /** + * Returns {@code true} if the disconnect dialog is enabled when advanced vpn is connected. + */ + boolean isDisconnectDialogEnabled(); } diff --git a/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java b/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java index c5bc69c042d..b8f58a9fde7 100644 --- a/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java +++ b/src/com/android/settings/vpn2/AdvancedVpnFeatureProviderImpl.java @@ -46,4 +46,9 @@ public class AdvancedVpnFeatureProviderImpl implements AdvancedVpnFeatureProvide public boolean isAdvancedVpnRemovable() { return true; } + + @Override + public boolean isDisconnectDialogEnabled() { + return true; + } } diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java index 3b875eb1293..a91bb6c7e66 100644 --- a/src/com/android/settings/vpn2/VpnSettings.java +++ b/src/com/android/settings/vpn2/VpnSettings.java @@ -366,7 +366,7 @@ public class VpnSettings extends RestrictedSettingsFragment implements public void setShownPreferences(final Collection updates) { retainAllPreference(updates); - final PreferenceGroup vpnGroup = getPreferenceScreen(); + final PreferenceGroup vpnGroup = mPreferenceScreen; updatePreferenceGroup(vpnGroup, updates); // Show all new preferences on the screen @@ -448,14 +448,16 @@ public class VpnSettings extends RestrictedSettingsFragment implements } else if (preference instanceof AppPreference) { AppPreference pref = (AppPreference) preference; boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED); + String vpnPackageName = pref.getPackageName(); - if (!connected) { + if ((!connected) || (isAdvancedVpn(mFeatureProvider, vpnPackageName, getContext()) + && !mFeatureProvider.isDisconnectDialogEnabled())) { try { UserHandle user = UserHandle.of(pref.getUserId()); - Context userContext = getActivity().createPackageContextAsUser( - getActivity().getPackageName(), 0 /* flags */, user); + Context userContext = getContext().createPackageContextAsUser( + getContext().getPackageName(), 0 /* flags */, user); PackageManager pm = userContext.getPackageManager(); - Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName()); + Intent appIntent = pm.getLaunchIntentForPackage(vpnPackageName); if (appIntent != null) { userContext.startActivityAsUser(appIntent, user); return true; diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java new file mode 100644 index 00000000000..cfa6d41e0d7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java @@ -0,0 +1,121 @@ +/* + * 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.widget.ButtonPreference; + +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 org.robolectric.RobolectricTestRunner; + +/** Tests for {@link BluetoothDetailsPairOtherController}. */ +@RunWith(RobolectricTestRunner.class) +public class BluetoothDetailsPairOtherControllerTest extends BluetoothDetailsControllerTestBase { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private CachedBluetoothDevice mSubCachedDevice; + private BluetoothDetailsPairOtherController mController; + private ButtonPreference mPreference; + + @Override + public void setUp() { + super.setUp(); + + mController = new BluetoothDetailsPairOtherController(mContext, mFragment, mCachedDevice, + mLifecycle); + mPreference = new ButtonPreference(mContext); + mPreference.setKey(mController.getPreferenceKey()); + mScreen.addPreference(mPreference); + } + + @Test + public void init_leftSideDevice_expectedTitle() { + when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidProfile.DeviceSide.SIDE_LEFT); + + mController.init(mScreen); + + assertThat(mPreference.getTitle().toString()).isEqualTo( + mContext.getString(R.string.bluetooth_pair_right_ear_button)); + } + + @Test + public void init_rightSideDevice_expectedTitle() { + when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidProfile.DeviceSide.SIDE_RIGHT); + + mController.init(mScreen); + + assertThat(mPreference.getTitle().toString()).isEqualTo( + mContext.getString(R.string.bluetooth_pair_left_ear_button)); + } + + @Test + public void isAvailable_isConnectedHearingAidDevice_available() { + when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_notConnectedHearingAidDevice_notAvailable() { + when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidProfile.DeviceMode.MODE_MONAURAL); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_subDeviceIsConnectedHearingAidDevice_notAvailable() { + when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mSubCachedDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getSubDevice()).thenReturn(mSubCachedDevice); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_subDeviceNotConnectedHearingAidDevice_available() { + when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mSubCachedDevice.isConnectedHearingAidDevice()).thenReturn(false); + when(mCachedDevice.getSubDevice()).thenReturn(mSubCachedDevice); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_subDeviceNotExist_available() { + when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true); + when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidProfile.DeviceMode.MODE_BINAURAL); + when(mCachedDevice.getSubDevice()).thenReturn(null); + + assertThat(mController.isAvailable()).isTrue(); + } +} diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceControllerTest.java index c5941313946..87998798193 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsLinkPreferenceControllerTest.java @@ -31,6 +31,7 @@ import android.content.Context; import android.os.Build; import android.service.notification.NotificationListenerFilter; +import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -68,6 +69,11 @@ public class BridgedAppsLinkPreferenceControllerTest { mController.setTargetSdk(Build.VERSION_CODES.CUR_DEVELOPMENT + 1); assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + + // disables field + Preference p = new Preference(mContext); + mController.updateState(p); + assertThat(p.isEnabled()).isFalse(); } @Test @@ -77,6 +83,11 @@ public class BridgedAppsLinkPreferenceControllerTest { when(mNm.getListenerFilter(mCn, 0)).thenReturn(new NotificationListenerFilter()); assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + + // disables field + Preference p = new Preference(mContext); + mController.updateState(p); + assertThat(p.isEnabled()).isFalse(); } @Test @@ -88,6 +99,11 @@ public class BridgedAppsLinkPreferenceControllerTest { when(mNm.getListenerFilter(mCn, 0)).thenReturn(nlf); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + + // enables field + Preference p = new Preference(mContext); + mController.updateState(p); + assertThat(p.isEnabled()).isTrue(); } @Test @@ -97,5 +113,10 @@ public class BridgedAppsLinkPreferenceControllerTest { when(mNm.getListenerFilter(mCn, 0)).thenReturn(new NotificationListenerFilter()); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + + // enables field + Preference p = new Preference(mContext); + mController.updateState(p); + assertThat(p.isEnabled()).isTrue(); } } diff --git a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java index d6ba33a3bc2..953a524750d 100644 --- a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java +++ b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java @@ -21,16 +21,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Looper; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArraySet; import androidx.preference.Preference; @@ -48,6 +53,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -58,13 +64,18 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) public class VpnSettingsTest { - private static final String ADVANCED_VPN_GROUP_KEY = "advanced_vpn_group"; - private static final String VPN_GROUP_KEY = "vpn_group"; - private static final String ADVANCED_VPN_GROUP_TITLE = "advanced_vpn_group_title"; - private static final String VPN_GROUP_TITLE = "vpn_group_title"; - private static final String FAKE_PACKAGE_NAME = "com.fake.package.name"; - private static final String ADVANCED_VPN_GROUP_PACKAGE_NAME = "com.advanced.package.name"; private static final int USER_ID_1 = UserHandle.USER_NULL; + private static final String VPN_GROUP_KEY = "vpn_group"; + private static final String VPN_GROUP_TITLE = "vpn_group_title"; + private static final String VPN_PACKAGE_NAME = "vpn.package.name"; + private static final String VPN_LAUNCH_INTENT = "vpn.action"; + private static final String ADVANCED_VPN_GROUP_KEY = "advanced_vpn_group"; + private static final String ADVANCED_VPN_GROUP_TITLE = "advanced_vpn_group_title"; + private static final String ADVANCED_VPN_PACKAGE_NAME = "advanced.vpn.package.name"; + private static final String ADVANCED_VPN_LAUNCH_INTENT = "advanced.vpn.action"; + + private final Intent mVpnIntent = new Intent().setAction(VPN_LAUNCH_INTENT); + private final Intent mAdvancedVpnIntent = new Intent().setAction(ADVANCED_VPN_LAUNCH_INTENT); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -108,7 +119,7 @@ public class VpnSettingsTest { when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getVpnPreferenceGroupTitle(mContext)) .thenReturn(VPN_GROUP_TITLE); when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.getAdvancedVpnPackageName()) - .thenReturn(ADVANCED_VPN_GROUP_PACKAGE_NAME); + .thenReturn(ADVANCED_VPN_PACKAGE_NAME); when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnSupported(any())) .thenReturn(true); when(mContext.getPackageManager()).thenReturn(mPackageManager); @@ -122,7 +133,7 @@ public class VpnSettingsTest { public void setShownAdvancedPreferences_hasGeneralVpn_returnsVpnCountAs1() { Set updates = new ArraySet<>(); AppPreference pref = - spy(new AppPreference(mContext, USER_ID_1, FAKE_PACKAGE_NAME)); + spy(new AppPreference(mContext, USER_ID_1, VPN_PACKAGE_NAME)); updates.add(pref); mVpnSettings.setShownAdvancedPreferences(updates); @@ -136,7 +147,7 @@ public class VpnSettingsTest { public void setShownAdvancedPreferences_hasAdvancedVpn_returnsAdvancedVpnCountAs1() { Set updates = new ArraySet<>(); AppPreference pref = - spy(new AppPreference(mContext, USER_ID_1, ADVANCED_VPN_GROUP_PACKAGE_NAME)); + spy(new AppPreference(mContext, USER_ID_1, ADVANCED_VPN_PACKAGE_NAME)); updates.add(pref); mVpnSettings.setShownAdvancedPreferences(updates); @@ -175,7 +186,7 @@ public class VpnSettingsTest { List opEntries = new ArrayList<>(); List apps = new ArrayList<>(); AppOpsManager.PackageOps packageOps = - new AppOpsManager.PackageOps(FAKE_PACKAGE_NAME, uid, opEntries); + new AppOpsManager.PackageOps(VPN_PACKAGE_NAME, uid, opEntries); apps.add(packageOps); when(mAppOpsManager.getPackagesForOps((int[]) any())).thenReturn(apps); when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isAdvancedVpnSupported(any())) @@ -185,4 +196,90 @@ public class VpnSettingsTest { mFakeFeatureFactory.getAdvancedVpnFeatureProvider(), mAppOpsManager)).isEmpty(); } + + @Test + public void clickVpn_VpnConnected_doesNotStartVpnLaunchIntent() + throws PackageManager.NameNotFoundException { + Set updates = new ArraySet<>(); + AppPreference pref = spy(new AppPreference(mContext, USER_ID_1, VPN_PACKAGE_NAME)); + pref.setState(AppPreference.STATE_CONNECTED); + updates.add(pref); + when(mContext.createPackageContextAsUser(any(), anyInt(), any())).thenReturn(mContext); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getLaunchIntentForPackage(any())).thenReturn(mVpnIntent); + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + doNothing().when(mContext).startActivityAsUser(captor.capture(), any()); + mVpnSettings.setShownPreferences(updates); + + mVpnSettings.onPreferenceClick(pref); + + verify(mContext, never()).startActivityAsUser(any(), any()); + } + + @Test + public void clickVpn_VpnDisconnected_startsVpnLaunchIntent() + throws PackageManager.NameNotFoundException { + Set updates = new ArraySet<>(); + AppPreference pref = spy(new AppPreference(mContext, USER_ID_1, VPN_PACKAGE_NAME)); + pref.setState(AppPreference.STATE_DISCONNECTED); + updates.add(pref); + when(mContext.createPackageContextAsUser(any(), anyInt(), any())).thenReturn(mContext); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getLaunchIntentForPackage(any())).thenReturn(mVpnIntent); + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + doNothing().when(mContext).startActivityAsUser(captor.capture(), any()); + mVpnSettings.setShownPreferences(updates); + + mVpnSettings.onPreferenceClick(pref); + + verify(mContext).startActivityAsUser(captor.capture(), any()); + assertThat(TextUtils.equals(captor.getValue().getAction(), + VPN_LAUNCH_INTENT)).isTrue(); + } + + @Test + public void clickAdvancedVpn_VpnConnectedDisconnectDialogDisabled_startsAppLaunchIntent() + throws PackageManager.NameNotFoundException { + Set updates = new ArraySet<>(); + AppPreference pref = + spy(new AppPreference(mContext, USER_ID_1, ADVANCED_VPN_PACKAGE_NAME)); + pref.setState(AppPreference.STATE_CONNECTED); + updates.add(pref); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isDisconnectDialogEnabled()) + .thenReturn(false); + when(mContext.createPackageContextAsUser(any(), anyInt(), any())).thenReturn(mContext); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getLaunchIntentForPackage(any())).thenReturn(mAdvancedVpnIntent); + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + doNothing().when(mContext).startActivityAsUser(captor.capture(), any()); + mVpnSettings.setShownAdvancedPreferences(updates); + + mVpnSettings.onPreferenceClick(pref); + + verify(mContext).startActivityAsUser(captor.capture(), any()); + assertThat(TextUtils.equals(captor.getValue().getAction(), + ADVANCED_VPN_LAUNCH_INTENT)).isTrue(); + } + + @Test + public void clickAdvancedVpn_VpnConnectedDisconnectDialogEnabled_doesNotStartAppLaunchIntent() + throws PackageManager.NameNotFoundException { + Set updates = new ArraySet<>(); + AppPreference pref = + spy(new AppPreference(mContext, USER_ID_1, ADVANCED_VPN_PACKAGE_NAME)); + pref.setState(AppPreference.STATE_CONNECTED); + updates.add(pref); + when(mFakeFeatureFactory.mAdvancedVpnFeatureProvider.isDisconnectDialogEnabled()) + .thenReturn(true); + when(mContext.createPackageContextAsUser(any(), anyInt(), any())).thenReturn(mContext); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getLaunchIntentForPackage(any())).thenReturn(mAdvancedVpnIntent); + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + doNothing().when(mContext).startActivityAsUser(captor.capture(), any()); + mVpnSettings.setShownAdvancedPreferences(updates); + + mVpnSettings.onPreferenceClick(pref); + + verify(mContext, never()).startActivityAsUser(any(), any()); + } }