Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0187221b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic 'wifi_detail_jank' into oc-dev

* changes:
  Reduce jank in the wifi detail status page.
  Improve testing of IP address display code.
parents 8c329af6 0bde06cd
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -82,8 +82,12 @@

    <!-- IPv6 Details -->
    <PreferenceCategory
            android:key="ipv6_details_category"
            android:key="ipv6_category"
            android:title="@string/wifi_details_ipv6_address_header"
            android:selectable="false">
        <Preference
                android:key="ipv6_addresses"
                android:selectable="false"/>
    </PreferenceCategory>

</PreferenceScreen>
+2 −0
Original line number Diff line number Diff line
@@ -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();
    }
+57 −59
Original line number Diff line number Diff line
@@ -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);
        mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
                || mSignInButton.getVisibility() == View.VISIBLE);

        // Reset all fields
        mIpv6AddressCategory.removeAll();
        mIpv6AddressCategory.setVisible(false);
        if (mNetwork == null || mLinkProperties == null) {
            mIpAddressPref.setVisible(false);
            mSubnetPref.setVisible(false);
            mGatewayPref.setVisible(false);
            mDnsPref.setVisible(false);

        if (mNetwork == null || mLinkProperties == null) {
            mIpv6Category.setVisible(false);
            return;
        }
        List<InetAddress> 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);
        // 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());
            }
        }

        // Set up IPv4 gateway and subnet mask
        // Find IPv4 default gateway.
        String gateway = null;
        String subnet = 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);
        }
    }

+159 −44
Original line number Diff line number Diff line
@@ -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<NetworkCallback> mCallbackCaptor;
    @Captor private ArgumentCaptor<View.OnClickListener> mForgetClickListener;
    @Captor private ArgumentCaptor<Preference> 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<Preference> preferenceCaptor = ArgumentCaptor.forClass(Preference.class);
        verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture());
        assertThat(preferenceCaptor.getValue().getTitle()).isEqualTo(mIpv6Address.getHostAddress());
        List <Preference> 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<Preference> preferenceCaptor = ArgumentCaptor.forClass(Preference.class);
        verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture());
        assertThat(preferenceCaptor.getValue().isSelectable()).isFalse();
        assertThat(mockIpv6AddressesPref.isSelectable()).isFalse();
    }

    @Test