diff --git a/res/values/strings.xml b/res/values/strings.xml index e4406f33f6c..6bab5949026 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2941,6 +2941,12 @@ Tethering Hotspot & Tethering + + Hotspot on, tethering + + Hotspot on + + Tethering "Can\u2019t tether or use portable hotspots while Data Saver is on" diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index 910e1eb092b..253ed7652d3 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -253,7 +253,8 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde new NfcPreferenceController(context).updateNonIndexableKeys(result); new MobilePlanPreferenceController(context, null /* MobilePlanClickHandler */) .updateNonIndexableKeys(result); - new MobileNetworkPreferenceController(context).updateNonIndexableKeys(result); + new MobileNetworkPreferenceController(context) + .updateNonIndexableKeys(result); // Remove Airplane Mode settings if it's a stationary device such as a TV. new AirplaneModePreferenceController(context, null /* fragment */) diff --git a/src/com/android/settings/network/MobileNetworkPreferenceController.java b/src/com/android/settings/network/MobileNetworkPreferenceController.java index 7c8cd405459..c7abf905cd9 100644 --- a/src/com/android/settings/network/MobileNetworkPreferenceController.java +++ b/src/com/android/settings/network/MobileNetworkPreferenceController.java @@ -17,25 +17,39 @@ package com.android.settings.network; import android.content.Context; import android.os.UserManager; +import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; import com.android.settings.Utils; import com.android.settings.core.PreferenceController; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnPause; +import com.android.settings.core.lifecycle.events.OnResume; import static android.os.UserHandle.myUserId; import static android.os.UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS; import static com.android.settingslib.RestrictedLockUtils.hasBaseUserRestriction; -public class MobileNetworkPreferenceController extends PreferenceController { +public class MobileNetworkPreferenceController extends PreferenceController implements + LifecycleObserver, OnResume, OnPause { private static final String KEY_MOBILE_NETWORK_SETTINGS = "mobile_network_settings"; private final UserManager mUserManager; private final boolean mIsSecondaryUser; + private final TelephonyManager mTelephonyManager; + private Preference mPreference; + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + PhoneStateListener mPhoneStateListener; public MobileNetworkPreferenceController(Context context) { super(context); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mIsSecondaryUser = !mUserManager.isAdminUser(); } @@ -46,8 +60,40 @@ public class MobileNetworkPreferenceController extends PreferenceController { && !hasBaseUserRestriction(mContext, DISALLOW_CONFIG_MOBILE_NETWORKS, myUserId()); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + mPreference = screen.findPreference(getPreferenceKey()); + } + } + @Override public String getPreferenceKey() { return KEY_MOBILE_NETWORK_SETTINGS; } + + @Override + public void onResume() { + if (isAvailable()) { + if (mPhoneStateListener == null) { + mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + if (mPreference != null) { + mPreference.setSummary(mTelephonyManager.getNetworkOperatorName()); + } + } + }; + } + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + } + } + + @Override + public void onPause() { + if (mPhoneStateListener != null) { + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + } + } } diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index d2b0621446a..f52230b61d0 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -81,16 +81,22 @@ public class NetworkDashboardFragment extends DashboardFragment implements new MobilePlanPreferenceController(context, this); final WifiMasterSwitchPreferenceController wifiPreferenceController = new WifiMasterSwitchPreferenceController(context, mMetricsFeatureProvider); + final MobileNetworkPreferenceController mobileNetworkPreferenceController = + new MobileNetworkPreferenceController(context); + final VpnPreferenceController vpnPreferenceController = + new VpnPreferenceController(context); final Lifecycle lifecycle = getLifecycle(); lifecycle.addObserver(airplaneModePreferenceController); lifecycle.addObserver(mobilePlanPreferenceController); lifecycle.addObserver(wifiPreferenceController); + lifecycle.addObserver(mobileNetworkPreferenceController); + lifecycle.addObserver(vpnPreferenceController); final List controllers = new ArrayList<>(); controllers.add(airplaneModePreferenceController); - controllers.add(new MobileNetworkPreferenceController(context)); + controllers.add(mobileNetworkPreferenceController); controllers.add(new TetherPreferenceController(context)); - controllers.add(new VpnPreferenceController(context)); + controllers.add(vpnPreferenceController); controllers.add(new ProxyPreferenceController(context)); controllers.add(mobilePlanPreferenceController); controllers.add(wifiPreferenceController); diff --git a/src/com/android/settings/network/TetherPreferenceController.java b/src/com/android/settings/network/TetherPreferenceController.java index e24c873b10b..236fa96a282 100644 --- a/src/com/android/settings/network/TetherPreferenceController.java +++ b/src/com/android/settings/network/TetherPreferenceController.java @@ -15,17 +15,23 @@ */ package com.android.settings.network; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.net.ConnectivityManager; import android.os.UserHandle; import android.os.UserManager; +import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; +import com.android.settings.R; import com.android.settings.TetherSettings; import com.android.settings.core.PreferenceController; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import static android.os.UserManager.DISALLOW_CONFIG_TETHERING; import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced; @@ -36,28 +42,60 @@ public class TetherPreferenceController extends PreferenceController { private static final String KEY_TETHER_SETTINGS = "tether_settings"; private final boolean mAdminDisallowedTetherConfig; + private final AtomicReference mBluetoothPan; private final ConnectivityManager mConnectivityManager; + private final BluetoothAdapter mBluetoothAdapter; private final UserManager mUserManager; + private final BluetoothProfile.ServiceListener mBtProfileServiceListener = + new android.bluetooth.BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothPan.set((BluetoothPan) proxy); + updateSummary(); + } + + public void onServiceDisconnected(int profile) { + mBluetoothPan.set(null); + } + }; + + private Preference mPreference; + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + TetherPreferenceController() { + super(null); + mAdminDisallowedTetherConfig = false; + mBluetoothPan = null; + mConnectivityManager = null; + mBluetoothAdapter = null; + mUserManager = null; + } + public TetherPreferenceController(Context context) { super(context); + mBluetoothPan = new AtomicReference<>(); mAdminDisallowedTetherConfig = checkIfRestrictionEnforced( context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null; mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter != null) { + mBluetoothAdapter.getProfileProxy(context, mBtProfileServiceListener, + BluetoothProfile.PAN); + } } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - final Preference preference = screen.findPreference(KEY_TETHER_SETTINGS); - if (preference != null && !mAdminDisallowedTetherConfig) { - preference.setTitle( + mPreference = screen.findPreference(KEY_TETHER_SETTINGS); + if (mPreference != null && !mAdminDisallowedTetherConfig) { + mPreference.setTitle( com.android.settingslib.Utils.getTetheringLabel(mConnectivityManager)); // Grey out if provisioning is not available. - preference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext)); + mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext)); } } @@ -70,6 +108,11 @@ public class TetherPreferenceController extends PreferenceController { return !isBlocked; } + @Override + public void updateState(Preference preference) { + updateSummary(); + } + @Override public void updateNonIndexableKeys(List keys) { if (!mUserManager.isAdminUser() || !mConnectivityManager.isTetheringSupported()) { @@ -81,4 +124,60 @@ public class TetherPreferenceController extends PreferenceController { public String getPreferenceKey() { return KEY_TETHER_SETTINGS; } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + void updateSummary() { + if (mPreference == null) { + // Preference is not ready yet. + return; + } + String[] allTethered = mConnectivityManager.getTetheredIfaces(); + String[] wifiTetherRegex = mConnectivityManager.getTetherableWifiRegexs(); + String[] bluetoothRegex = mConnectivityManager.getTetherableBluetoothRegexs(); + + boolean hotSpotOn = false; + boolean tetherOn = false; + if (allTethered != null) { + if (wifiTetherRegex != null) { + for (String tethered : allTethered) { + for (String regex : wifiTetherRegex) { + if (tethered.matches(regex)) { + hotSpotOn = true; + break; + } + } + } + } + if (allTethered.length > 1) { + // We have more than 1 tethered connection + tetherOn = true; + } else if (allTethered.length == 1) { + // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of + // tether. + tetherOn = !hotSpotOn; + } else { + // No tethered connection. + tetherOn = false; + } + } + if (!tetherOn + && bluetoothRegex != null && bluetoothRegex.length > 0 + && mBluetoothAdapter != null + && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { + // Check bluetooth state. It's not included in mConnectivityManager.getTetheredIfaces. + final BluetoothPan pan = mBluetoothPan.get(); + tetherOn = pan != null && pan.isTetheringOn(); + } + if (!hotSpotOn && !tetherOn) { + // Both off + mPreference.setSummary(R.string.switch_off_text); + } else if (hotSpotOn && tetherOn) { + // Both on + mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on); + } else if (hotSpotOn) { + mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off); + } else { + mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on); + } + } } diff --git a/src/com/android/settings/network/VpnPreferenceController.java b/src/com/android/settings/network/VpnPreferenceController.java index f7e230ffd05..86ff175412d 100644 --- a/src/com/android/settings/network/VpnPreferenceController.java +++ b/src/com/android/settings/network/VpnPreferenceController.java @@ -16,38 +16,74 @@ package com.android.settings.network; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; +import android.util.Log; +import android.util.SparseArray; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.settings.R; import com.android.settings.core.PreferenceController; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnPause; +import com.android.settings.core.lifecycle.events.OnResume; import com.android.settingslib.RestrictedLockUtils; +import java.util.List; -public class VpnPreferenceController extends PreferenceController { + +public class VpnPreferenceController extends PreferenceController implements LifecycleObserver, + OnResume, OnPause { private static final String KEY_VPN_SETTINGS = "vpn_settings"; + private static final NetworkRequest REQUEST = new NetworkRequest.Builder() + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .build(); + private static final String TAG = "VpnPreferenceController"; private final String mToggleable; - private final boolean mIsSecondaryUser; + private final UserManager mUserManager; + private final ConnectivityManager mConnectivityManager; + private final IConnectivityManager mConnectivityManagerService; + private Preference mPreference; public VpnPreferenceController(Context context) { super(context); mToggleable = Settings.Global.getString(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); - mIsSecondaryUser = !UserManager.get(context).isAdminUser(); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mConnectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mConnectivityManagerService = IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); + mPreference = screen.findPreference(KEY_VPN_SETTINGS); // Manually set dependencies for Wifi when not toggleable. if (mToggleable == null || !mToggleable.contains(Settings.Global.RADIO_WIFI)) { - final Preference pref = screen.findPreference(KEY_VPN_SETTINGS); - if (pref != null) { - pref.setDependency(AirplaneModePreferenceController.KEY_TOGGLE_AIRPLANE); + if (mPreference != null) { + mPreference.setDependency(AirplaneModePreferenceController.KEY_TOGGLE_AIRPLANE); } } } @@ -62,4 +98,96 @@ public class VpnPreferenceController extends PreferenceController { public String getPreferenceKey() { return KEY_VPN_SETTINGS; } + + @Override + public void onPause() { + if (isAvailable()) { + mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); + } + } + + @Override + public void onResume() { + if (isAvailable()) { + mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback); + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + void updateSummary() { + if (mPreference == null) { + return; + } + // Copied from SystemUI::SecurityControllerImpl + SparseArray vpns = new SparseArray<>(); + try { + final List users = mUserManager.getUsers(); + for (UserInfo user : users) { + VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id); + if (cfg == null) { + continue; + } else if (cfg.legacy) { + // Legacy VPNs should do nothing if the network is disconnected. Third-party + // VPN warnings need to continue as traffic can still go to the app. + final LegacyVpnInfo legacyVpn = + mConnectivityManagerService.getLegacyVpnInfo(user.id); + if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) { + continue; + } + } + vpns.put(user.id, cfg); + } + } catch (RemoteException rme) { + // Roll back to previous state + Log.e(TAG, "Unable to list active VPNs", rme); + return; + } + final UserInfo userInfo = mUserManager.getUserInfo(UserHandle.myUserId()); + final int uid; + if (userInfo.isRestricted()) { + uid = userInfo.restrictedProfileParentId; + } else { + uid = userInfo.id; + } + VpnConfig vpn = vpns.get(uid); + final String vpnName; + if (vpn == null) { + vpnName = null; + } else { + vpnName = getNameForVpnConfig(vpn, UserHandle.of(uid)); + } + new Handler(Looper.getMainLooper()).post(() -> mPreference.setSummary(vpnName)); + } + + private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) { + if (cfg.legacy) { + return mContext.getString(R.string.bluetooth_connected); + } + // The package name for an active VPN is stored in the 'user' field of its VpnConfig + final String vpnPackage = cfg.user; + try { + Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), + 0 /* flags */, user); + return VpnConfig.getVpnLabel(userContext, vpnPackage).toString(); + } catch (PackageManager.NameNotFoundException nnfe) { + Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe); + return null; + } + } + + // Copied from SystemUI::SecurityControllerImpl + private final ConnectivityManager.NetworkCallback + mNetworkCallback = new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + Log.d(TAG, "onAvailable " + network.netId); + updateSummary(); + } + + @Override + public void onLost(Network network) { + Log.d(TAG, "onLost " + network.netId); + updateSummary(); + } + }; } diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java index d3d6836d180..1ae40aa1294 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java @@ -19,8 +19,15 @@ import android.content.Context; import android.net.ConnectivityManager; import android.os.UserHandle; import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; + import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.core.lifecycle.Lifecycle; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +39,10 @@ import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(SettingsRobolectricTestRunner.class) @@ -44,15 +55,23 @@ public class MobileNetworkPreferenceControllerTest { private UserManager mUserManager; @Mock private ConnectivityManager mConnectivityManager; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private PreferenceScreen mScreen; + private Lifecycle mLifecycle; private MobileNetworkPreferenceController mController; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mLifecycle = new Lifecycle(); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) .thenReturn(mConnectivityManager); + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)) + .thenReturn(mTelephonyManager); } @Test @@ -78,4 +97,45 @@ public class MobileNetworkPreferenceControllerTest { mController = new MobileNetworkPreferenceController(mContext); assertThat(mController.isAvailable()).isFalse(); } + + @Test + public void goThroughLifecycle_isAvailable_shouldListenToServiceChange() { + mController = spy(new MobileNetworkPreferenceController(mContext)); + mLifecycle.addObserver(mController); + doReturn(true).when(mController).isAvailable(); + + mLifecycle.onResume(); + verify(mTelephonyManager).listen(mController.mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE); + + mLifecycle.onPause(); + verify(mTelephonyManager).listen(mController.mPhoneStateListener, + PhoneStateListener.LISTEN_NONE); + } + + @Test + public void serviceStateChange_shouldUpdatePrefSummary() { + final String testCarrierName = "test"; + final Preference mPreference = mock(Preference.class); + mController = spy(new MobileNetworkPreferenceController(mContext)); + mLifecycle.addObserver(mController); + + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + doReturn(true).when(mController).isAvailable(); + + // Display pref and go through lifecycle to set up listener. + mController.displayPreference(mScreen); + mLifecycle.onResume(); + verify(mController).onResume(); + verify(mTelephonyManager).listen(mController.mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE); + + // Trigger listener update + when(mTelephonyManager.getNetworkOperatorName()).thenReturn(testCarrierName); + mController.mPhoneStateListener.onServiceStateChanged(null); + + // Carrier name should be set. + verify(mPreference).setSummary(testCarrierName); + } + } diff --git a/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java new file mode 100644 index 00000000000..3d6a2335172 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 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.network; + + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.net.ConnectivityManager; +import android.os.UserManager; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class TetherPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private BluetoothAdapter mBluetoothAdapter; + @Mock + private UserManager mUserManager; + @Mock + private Preference mPreference; + + private TetherPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = spy(TetherPreferenceController.class); + ReflectionHelpers.setField(mController, "mContext", mContext); + ReflectionHelpers.setField(mController, "mConnectivityManager", mConnectivityManager); + ReflectionHelpers.setField(mController, "mBluetoothAdapter", mBluetoothAdapter); + ReflectionHelpers.setField(mController, "mUserManager", mUserManager); + } + + @Test + public void updateSummary_noPreference_noInteractionWithConnectivityManager() { + mController.updateSummary(); + verifyNoMoreInteractions(mConnectivityManager); + } + + @Test + public void updateSummary_wifiTethered_shouldShowHotspotMessage() { + ReflectionHelpers.setField(mController, "mPreference", mPreference); + when(mConnectivityManager.getTetheredIfaces()).thenReturn(new String[]{"123"}); + when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"123"}); + + mController.updateSummary(); + verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_on_tether_off); + } + + @Test + public void updateSummary_btThetherOn_shouldShowTetherMessage() { + ReflectionHelpers.setField(mController, "mPreference", mPreference); + when(mConnectivityManager.getTetheredIfaces()).thenReturn(new String[]{"123"}); + when(mConnectivityManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"123"}); + + mController.updateSummary(); + verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_off_tether_on); + } + + @Test + public void updateSummary_tetherOff_shouldShowTetherOffMessage() { + ReflectionHelpers.setField(mController, "mPreference", mPreference); + when(mConnectivityManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"123"}); + when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"456"}); + + mController.updateSummary(); + verify(mPreference).setSummary(R.string.switch_off_text); + } + + @Test + public void updateSummary_wifiBtTetherOn_shouldShowHotspotAndTetherMessage() { + ReflectionHelpers.setField(mController, "mPreference", mPreference); + when(mConnectivityManager.getTetheredIfaces()).thenReturn(new String[]{"123", "456"}); + when(mConnectivityManager.getTetherableWifiRegexs()).thenReturn(new String[]{"456"}); + when(mConnectivityManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"23"}); + + mController.updateSummary(); + verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_on_tether_on); + } + +} diff --git a/tests/robotests/src/com/android/settings/network/VpnPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/VpnPreferenceControllerTest.java new file mode 100644 index 00000000000..2a0b873a726 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/VpnPreferenceControllerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 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.network; + + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.NetworkRequest; +import android.os.IBinder; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowServiceManager; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class VpnPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private IBinder mBinder; + @Mock + private IConnectivityManager mConnectivityManagerService; + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + private VpnPreferenceController mController; + private Lifecycle mLifecycle; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) + .thenReturn(mConnectivityManager); + when(mBinder.queryLocalInterface("android.net.IConnectivityManager")) + .thenReturn(mConnectivityManagerService); + ShadowServiceManager.addService(Context.CONNECTIVITY_SERVICE, mBinder); + when(mScreen.findPreference(anyString())).thenReturn(mPreference); + + mController = spy(new VpnPreferenceController(mContext)); + mLifecycle = new Lifecycle(); + mLifecycle.addObserver(mController); + } + + @Test + public void displayPreference_available_shouldSetDependency() { + + doReturn(true).when(mController).isAvailable(); + mController.displayPreference(mScreen); + + verify(mPreference).setDependency(AirplaneModePreferenceController.KEY_TOGGLE_AIRPLANE); + } + + @Test + public void goThroughLifecycle_shouldRegisterUnregisterListener() { + doReturn(true).when(mController).isAvailable(); + + mLifecycle.onResume(); + verify(mConnectivityManager).registerNetworkCallback( + any(NetworkRequest.class), any(ConnectivityManager.NetworkCallback.class)); + + mLifecycle.onPause(); + verify(mConnectivityManager).unregisterNetworkCallback( + any(ConnectivityManager.NetworkCallback.class)); + } + +}