diff --git a/res/xml/wifi_network_details_fragment.xml b/res/xml/wifi_network_details_fragment.xml index fafbbb042f9..f9926cafd28 100644 --- a/res/xml/wifi_network_details_fragment.xml +++ b/res/xml/wifi_network_details_fragment.xml @@ -82,8 +82,12 @@ + android:selectable="false"> + + diff --git a/src/com/android/settings/wifi/WifiDetailPreference.java b/src/com/android/settings/wifi/WifiDetailPreference.java index 6d34ad1c68f..b62df56671f 100644 --- a/src/com/android/settings/wifi/WifiDetailPreference.java +++ b/src/com/android/settings/wifi/WifiDetailPreference.java @@ -19,6 +19,7 @@ package com.android.settings.wifi; import android.content.Context; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceViewHolder; +import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; @@ -37,6 +38,7 @@ public class WifiDetailPreference extends Preference { } public void setDetailText(String text) { + if (TextUtils.equals(mDetailText, text)) return; mDetailText = text; notifyChanged(); } diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java index b3f83b335e7..c0dd0a87700 100644 --- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java @@ -27,6 +27,7 @@ import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkBadging; @@ -67,6 +68,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.StringJoiner; +import java.util.stream.Collectors; /** * Controller for logic pertaining to displaying Wifi information for the @@ -100,7 +102,9 @@ public class WifiDetailPreferenceController extends PreferenceController impleme @VisibleForTesting static final String KEY_DNS_PREF = "dns"; @VisibleForTesting - static final String KEY_IPV6_ADDRESS_CATEGORY = "ipv6_details_category"; + static final String KEY_IPV6_CATEGORY = "ipv6_category"; + @VisibleForTesting + static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses"; private AccessPoint mAccessPoint; private final ConnectivityManagerWrapper mConnectivityManagerWrapper; @@ -133,8 +137,9 @@ public class WifiDetailPreferenceController extends PreferenceController impleme private WifiDetailPreference mGatewayPref; private WifiDetailPreference mSubnetPref; private WifiDetailPreference mDnsPref; + private PreferenceCategory mIpv6Category; + private Preference mIpv6AddressPref; - private PreferenceCategory mIpv6AddressCategory; private final IntentFilter mFilter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -241,8 +246,8 @@ public class WifiDetailPreferenceController extends PreferenceController impleme mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF); mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF); - mIpv6AddressCategory = - (PreferenceCategory) screen.findPreference(KEY_IPV6_ADDRESS_CATEGORY); + mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY); + mIpv6AddressPref = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF); mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */)); mForgetButton = (Button) mButtonsPref.findViewById(R.id.forget_button); @@ -315,8 +320,6 @@ public class WifiDetailPreferenceController extends PreferenceController impleme mFrequencyPref.setDetailText(band); updateIpLayerInfo(); - mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE - || mSignInButton.getVisibility() == View.VISIBLE); } private void exitActivity() { @@ -348,74 +351,69 @@ public class WifiDetailPreferenceController extends PreferenceController impleme mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]); } + private void updatePreference(WifiDetailPreference pref, String detailText) { + if (!TextUtils.isEmpty(detailText)) { + pref.setDetailText(detailText); + pref.setVisible(true); + } else { + pref.setVisible(false); + } + } + private void updateIpLayerInfo() { mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE); - - // Reset all fields - mIpv6AddressCategory.removeAll(); - mIpv6AddressCategory.setVisible(false); - mIpAddressPref.setVisible(false); - mSubnetPref.setVisible(false); - mGatewayPref.setVisible(false); - mDnsPref.setVisible(false); + mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE + || mSignInButton.getVisibility() == View.VISIBLE); if (mNetwork == null || mLinkProperties == null) { + mIpAddressPref.setVisible(false); + mSubnetPref.setVisible(false); + mGatewayPref.setVisible(false); + mDnsPref.setVisible(false); + mIpv6Category.setVisible(false); return; } - List addresses = mLinkProperties.getAddresses(); - // Set IPv4 and IPv6 addresses - for (int i = 0; i < addresses.size(); i++) { - InetAddress addr = addresses.get(i); - if (addr instanceof Inet4Address) { - mIpAddressPref.setDetailText(addr.getHostAddress()); - mIpAddressPref.setVisible(true); - } else if (addr instanceof Inet6Address) { - String ip = addr.getHostAddress(); - Preference pref = new Preference(mPrefContext); - pref.setKey(ip); - pref.setTitle(ip); - pref.setSelectable(false); - mIpv6AddressCategory.addPreference(pref); - mIpv6AddressCategory.setVisible(true); - } - } - - // Set up IPv4 gateway and subnet mask - String gateway = null; + // Find IPv4 and IPv6 addresses. + String ipv4Address = null; String subnet = null; + StringJoiner ipv6Addresses = new StringJoiner("\n"); + + for (LinkAddress addr : mLinkProperties.getLinkAddresses()) { + if (addr.getAddress() instanceof Inet4Address) { + ipv4Address = addr.getAddress().getHostAddress(); + subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength()); + } else if (addr.getAddress() instanceof Inet6Address) { + ipv6Addresses.add(addr.getAddress().getHostAddress()); + } + } + + // Find IPv4 default gateway. + String gateway = null; for (RouteInfo routeInfo : mLinkProperties.getRoutes()) { - if (routeInfo.hasGateway() && routeInfo.getGateway() instanceof Inet4Address) { + if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) { gateway = routeInfo.getGateway().getHostAddress(); - } - IpPrefix ipPrefix = routeInfo.getDestination(); - if (ipPrefix != null && ipPrefix.getAddress() instanceof Inet4Address - && ipPrefix.getPrefixLength() > 0) { - subnet = ipv4PrefixLengthToSubnetMask(ipPrefix.getPrefixLength()); + break; } } - if (!TextUtils.isEmpty(subnet)) { - mSubnetPref.setDetailText(subnet); - mSubnetPref.setVisible(true); - } + // Find IPv4 DNS addresses. + String dnsServers = mLinkProperties.getDnsServers().stream() + .filter(Inet4Address.class::isInstance) + .map(InetAddress::getHostAddress) + .collect(Collectors.joining(",")); - if (!TextUtils.isEmpty(gateway)) { - mGatewayPref.setDetailText(gateway); - mGatewayPref.setVisible(true); - } + // Update UI. + updatePreference(mIpAddressPref, ipv4Address); + updatePreference(mSubnetPref, subnet); + updatePreference(mGatewayPref, gateway); + updatePreference(mDnsPref, dnsServers); - // Set IPv4 DNS addresses - StringJoiner stringJoiner = new StringJoiner(","); - for (InetAddress dnsServer : mLinkProperties.getDnsServers()) { - if (dnsServer instanceof Inet4Address) { - stringJoiner.add(dnsServer.getHostAddress()); - } - } - String dnsText = stringJoiner.toString(); - if (!dnsText.isEmpty()) { - mDnsPref.setDetailText(dnsText); - mDnsPref.setVisible(true); + if (ipv6Addresses.length() > 0) { + mIpv6AddressPref.setSummary(ipv6Addresses.toString()); + mIpv6Category.setVisible(true); + } else { + mIpv6Category.setVisible(false); } } diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java index 0298195fcac..41825a5fc21 100644 --- a/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java @@ -75,9 +75,13 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -89,9 +93,6 @@ public class WifiDetailPreferenceControllerTest { private static final String MAC_ADDRESS = WifiInfo.DEFAULT_MAC_ADDRESS; private static final String SECURITY = "None"; - private InetAddress mIpv4Address; - private Inet6Address mIpv6Address; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PreferenceScreen mockScreen; @@ -120,35 +121,76 @@ public class WifiDetailPreferenceControllerTest { @Mock private WifiDetailPreference mockSubnetPref; @Mock private WifiDetailPreference mockDnsPref; @Mock private Button mockForgetButton; - @Mock private PreferenceCategory mockIpv6AddressCategory; + @Mock private PreferenceCategory mockIpv6Category; + @Mock private WifiDetailPreference mockIpv6AddressesPref; @Captor private ArgumentCaptor mCallbackCaptor; @Captor private ArgumentCaptor mForgetClickListener; + @Captor private ArgumentCaptor mIpv6AddressCaptor; private Context mContext = RuntimeEnvironment.application; private Lifecycle mLifecycle; private LinkProperties mLinkProperties; private WifiDetailPreferenceController mController; + // This class exists so that these values can be made static final. They can't be static final + // members of the test class, because any attempt to call IpPrefix or RouteInfo constructors + // during static initialization of the test class results in NoSuchMethorError being thrown + // when the test is run. + private static class Constants { + static final int IPV4_PREFIXLEN = 25; + static final LinkAddress IPV4_ADDR; + static final Inet4Address IPV4_GATEWAY; + static final RouteInfo IPV4_DEFAULT; + static final RouteInfo IPV4_SUBNET; + static final LinkAddress IPV6_LINKLOCAL; + static final LinkAddress IPV6_GLOBAL1; + static final LinkAddress IPV6_GLOBAL2; + static final InetAddress IPV4_DNS1; + static final InetAddress IPV4_DNS2; + static final InetAddress IPV6_DNS; + + private static LinkAddress ipv6LinkAddress(String addr) throws UnknownHostException { + return new LinkAddress(InetAddress.getByName(addr), 64); + } + + private static LinkAddress ipv4LinkAddress(String addr, int prefixlen) + throws UnknownHostException { + return new LinkAddress(InetAddress.getByName(addr), prefixlen); + } + + static { + try { + // We create our test constants in these roundabout ways because the robolectric + // shadows don't contain NetworkUtils.parseNumericAddress and other utility methods, + // so the easy ways to do things fail with NoSuchMethodError. + IPV4_ADDR = ipv4LinkAddress("192.0.2.2", IPV4_PREFIXLEN); + IPV4_GATEWAY = (Inet4Address) InetAddress.getByName("192.0.2.127"); + + final Inet4Address any4 = (Inet4Address) InetAddress.getByName("0.0.0.0"); + IpPrefix subnet = new IpPrefix(IPV4_ADDR.getAddress(), IPV4_PREFIXLEN); + IPV4_SUBNET = new RouteInfo(subnet, any4); + IPV4_DEFAULT = new RouteInfo(new IpPrefix(any4, 0), IPV4_GATEWAY); + + IPV6_LINKLOCAL = ipv6LinkAddress("fe80::211:25ff:fef8:7cb2%1"); + IPV6_GLOBAL1 = ipv6LinkAddress("2001:db8:1::211:25ff:fef8:7cb2"); + IPV6_GLOBAL2 = ipv6LinkAddress("2001:db8:1::3dfe:8902:f98f:739d"); + + IPV4_DNS1 = InetAddress.getByName("8.8.8.8"); + IPV4_DNS2 = InetAddress.getByName("8.8.4.4"); + IPV6_DNS = InetAddress.getByName("2001:4860:4860::64"); + } catch (UnknownHostException e) { + throw new RuntimeException("Invalid hardcoded IP addresss: " + e); + } + } + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); mLifecycle = new Lifecycle(); - try { - mIpv4Address = InetAddress.getByAddress( - new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 }); - mIpv6Address = Inet6Address.getByAddress( - "123", /* host */ - new byte[] { - (byte) 0xFE, (byte) 0x80, 0, 0, 0, 0, 0, 0, 0x02, 0x11, 0x25, - (byte) 0xFF, (byte) 0xFE, (byte) 0xF8, (byte) 0x7C, (byte) 0xB2}, - 1 /*scope id */); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - when(mockAccessPoint.getConfig()).thenReturn(mockWifiConfig); when(mockAccessPoint.getLevel()).thenReturn(LEVEL); when(mockAccessPoint.getSecurityString(false)).thenReturn(SECURITY); @@ -217,8 +259,10 @@ public class WifiDetailPreferenceControllerTest { .thenReturn(mockSubnetPref); when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_DNS_PREF)) .thenReturn(mockDnsPref); - when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_ADDRESS_CATEGORY)) - .thenReturn(mockIpv6AddressCategory); + when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_CATEGORY)) + .thenReturn(mockIpv6Category); + when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_ADDRESSES_PREF)) + .thenReturn(mockIpv6AddressesPref); } @Test @@ -330,26 +374,23 @@ public class WifiDetailPreferenceControllerTest { @Test public void ipAddressPref_shouldHaveDetailTextSet() { - LinkAddress ipv4Address = new LinkAddress(mIpv4Address, 32); - - mLinkProperties.addLinkAddress(ipv4Address); + mLinkProperties.addLinkAddress(Constants.IPV4_ADDR); mController.displayPreference(mockScreen); - verify(mockIpAddressPref).setDetailText(mIpv4Address.getHostAddress()); + verify(mockIpAddressPref).setDetailText(Constants.IPV4_ADDR.getAddress().getHostAddress()); } @Test public void gatewayAndSubnet_shouldHaveDetailTextSet() { - int prefixLength = 24; - IpPrefix subnet = new IpPrefix(mIpv4Address, prefixLength); - InetAddress gateway = mIpv4Address; - mLinkProperties.addRoute(new RouteInfo(subnet, gateway)); + mLinkProperties.addLinkAddress(Constants.IPV4_ADDR); + mLinkProperties.addRoute(Constants.IPV4_DEFAULT); + mLinkProperties.addRoute(Constants.IPV4_SUBNET); mController.displayPreference(mockScreen); - verify(mockSubnetPref).setDetailText("255.255.255.0"); - verify(mockGatewayPref).setDetailText(mIpv4Address.getHostAddress()); + verify(mockSubnetPref).setDetailText("255.255.255.128"); + verify(mockGatewayPref).setDetailText("192.0.2.127"); } @Test @@ -376,23 +417,96 @@ public class WifiDetailPreferenceControllerTest { @Test public void noLinkProperties_allIpDetailsHidden() { when(mockConnectivityManager.getLinkProperties(mockNetwork)).thenReturn(null); - reset(mockIpv6AddressCategory, mockIpAddressPref, mockSubnetPref, mockGatewayPref, + reset(mockIpv6Category, mockIpAddressPref, mockSubnetPref, mockGatewayPref, mockDnsPref); mController.displayPreference(mockScreen); - verify(mockIpv6AddressCategory).setVisible(false); + verify(mockIpv6Category).setVisible(false); verify(mockIpAddressPref).setVisible(false); verify(mockSubnetPref).setVisible(false); verify(mockGatewayPref).setVisible(false); verify(mockDnsPref).setVisible(false); - verify(mockIpv6AddressCategory, never()).setVisible(true); + verify(mockIpv6Category, never()).setVisible(true); verify(mockIpAddressPref, never()).setVisible(true); verify(mockSubnetPref, never()).setVisible(true); verify(mockGatewayPref, never()).setVisible(true); verify(mockDnsPref, never()).setVisible(true); } + // Convenience method to convert a LinkAddress to a string without a prefix length. + private String asString(LinkAddress l) { + return l.getAddress().getHostAddress(); + } + + // Pretend that the NetworkCallback was triggered with a new copy of lp. We need to create a + // new copy because the code only updates if !mLinkProperties.equals(lp). + private void updateLinkProperties(LinkProperties lp) { + mCallbackCaptor.getValue().onLinkPropertiesChanged(mockNetwork, new LinkProperties(lp)); + } + + private void verifyDisplayedIpv6Addresses(InOrder inOrder, LinkAddress... addresses) { + String text = Arrays.stream(addresses) + .map(address -> asString(address)) + .collect(Collectors.joining("\n")); + inOrder.verify(mockIpv6AddressesPref).setSummary(text); + } + + @Test + public void onLinkPropertiesChanged_updatesFields() { + mController.displayPreference(mockScreen); + mController.onResume(); + + InOrder inOrder = inOrder(mockIpAddressPref, mockGatewayPref, mockSubnetPref, + mockDnsPref, mockIpv6Category, mockIpv6AddressesPref); + + LinkProperties lp = new LinkProperties(); + + lp.addLinkAddress(Constants.IPV6_LINKLOCAL); + updateLinkProperties(lp); + verifyDisplayedIpv6Addresses(inOrder, Constants.IPV6_LINKLOCAL); + inOrder.verify(mockIpv6Category).setVisible(true); + + lp.addRoute(Constants.IPV4_DEFAULT); + updateLinkProperties(lp); + inOrder.verify(mockGatewayPref).setDetailText(Constants.IPV4_GATEWAY.getHostAddress()); + inOrder.verify(mockGatewayPref).setVisible(true); + + lp.addLinkAddress(Constants.IPV4_ADDR); + lp.addRoute(Constants.IPV4_SUBNET); + updateLinkProperties(lp); + inOrder.verify(mockIpAddressPref).setDetailText(asString(Constants.IPV4_ADDR)); + inOrder.verify(mockIpAddressPref).setVisible(true); + inOrder.verify(mockSubnetPref).setDetailText("255.255.255.128"); + inOrder.verify(mockSubnetPref).setVisible(true); + + lp.addLinkAddress(Constants.IPV6_GLOBAL1); + lp.addLinkAddress(Constants.IPV6_GLOBAL2); + updateLinkProperties(lp); + verifyDisplayedIpv6Addresses(inOrder, + Constants.IPV6_LINKLOCAL, + Constants.IPV6_GLOBAL1, + Constants.IPV6_GLOBAL2); + + lp.removeLinkAddress(Constants.IPV6_GLOBAL1); + updateLinkProperties(lp); + verifyDisplayedIpv6Addresses(inOrder, + Constants.IPV6_LINKLOCAL, + Constants.IPV6_GLOBAL2); + + lp.addDnsServer(Constants.IPV6_DNS); + updateLinkProperties(lp); + inOrder.verify(mockDnsPref, never()).setVisible(true); + + lp.addDnsServer(Constants.IPV4_DNS1); + lp.addDnsServer(Constants.IPV4_DNS2); + updateLinkProperties(lp); + inOrder.verify(mockDnsPref).setDetailText( + Constants.IPV4_DNS1.getHostAddress() + "," + + Constants.IPV4_DNS2.getHostAddress()); + inOrder.verify(mockDnsPref).setVisible(true); + } + @Test public void canForgetNetwork_noNetwork() { when(mockAccessPoint.getConfig()).thenReturn(null); @@ -496,28 +610,29 @@ public class WifiDetailPreferenceControllerTest { @Test public void ipv6AddressPref_shouldHaveHostAddressTextSet() { - LinkAddress ipv6Address = new LinkAddress(mIpv6Address, 128); - - mLinkProperties.addLinkAddress(ipv6Address); + mLinkProperties.addLinkAddress(Constants.IPV6_LINKLOCAL); + mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL1); + mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2); mController.displayPreference(mockScreen); - ArgumentCaptor preferenceCaptor = ArgumentCaptor.forClass(Preference.class); - verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture()); - assertThat(preferenceCaptor.getValue().getTitle()).isEqualTo(mIpv6Address.getHostAddress()); + List addrs = mIpv6AddressCaptor.getAllValues(); + + String expectedAddresses = String.join("\n", + asString(Constants.IPV6_LINKLOCAL), + asString(Constants.IPV6_GLOBAL1), + asString(Constants.IPV6_GLOBAL2)); + + verify(mockIpv6AddressesPref).setSummary(expectedAddresses); } @Test public void ipv6AddressPref_shouldNotBeSelectable() { - LinkAddress ipv6Address = new LinkAddress(mIpv6Address, 128); - - mLinkProperties.addLinkAddress(ipv6Address); + mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2); mController.displayPreference(mockScreen); - ArgumentCaptor preferenceCaptor = ArgumentCaptor.forClass(Preference.class); - verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture()); - assertThat(preferenceCaptor.getValue().isSelectable()).isFalse(); + assertThat(mockIpv6AddressesPref.isSelectable()).isFalse(); } @Test