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

Commit 0e8e89f5 authored by Chalard Jean's avatar Chalard Jean Committed by android-build-merger
Browse files

Show the status of private DNS. am: f249555f

am: 43a42e45

Change-Id: I7ba6ec19ab4f373d6d78609f5a8a6bd76749ead7
parents c7174ce3 43a42e45
Loading
Loading
Loading
Loading
+46 −3
Original line number Diff line number Diff line
@@ -24,6 +24,10 @@ import android.content.Context;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -31,6 +35,7 @@ import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.internal.util.ArrayUtils;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
@@ -38,6 +43,8 @@ import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.core.lifecycle.LifecycleObserver;

import java.net.InetAddress;
import java.util.List;

public class PrivateDnsPreferenceController extends BasePreferenceController
        implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop {
@@ -50,12 +57,15 @@ public class PrivateDnsPreferenceController extends BasePreferenceController

    private final Handler mHandler;
    private final ContentObserver mSettingsObserver;
    private final ConnectivityManager mConnectivityManager;
    private LinkProperties mLatestLinkProperties;
    private Preference mPreference;

    public PrivateDnsPreferenceController(Context context) {
        super(context, KEY_PRIVATE_DNS_SETTINGS);
        mHandler = new Handler(Looper.getMainLooper());
        mSettingsObserver = new PrivateDnsSettingsObserver(mHandler);
        mConnectivityManager = context.getSystemService(ConnectivityManager.class);
    }

    @Override
@@ -80,11 +90,17 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
        for (Uri uri : SETTINGS_URIS) {
            mContext.getContentResolver().registerContentObserver(uri, false, mSettingsObserver);
        }
        final Network defaultNetwork = mConnectivityManager.getActiveNetwork();
        if (defaultNetwork != null) {
            mLatestLinkProperties = mConnectivityManager.getLinkProperties(defaultNetwork);
        }
        mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback, mHandler);
    }

    @Override
    public void onStop() {
        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
    }

    @Override
@@ -92,13 +108,23 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
        final Resources res = mContext.getResources();
        final ContentResolver cr = mContext.getContentResolver();
        final String mode = PrivateDnsModeDialogPreference.getModeFromSettings(cr);
        final LinkProperties lp = mLatestLinkProperties;
        final List<InetAddress> dnses = (lp == null) ? null : lp.getValidatedPrivateDnsServers();
        final boolean dnsesResolved = !ArrayUtils.isEmpty(dnses);
        switch (mode) {
            case PRIVATE_DNS_MODE_OFF:
                return res.getString(R.string.private_dns_mode_off);
            case PRIVATE_DNS_MODE_OPPORTUNISTIC:
                return res.getString(R.string.private_dns_mode_opportunistic);
                // TODO (b/79122154) : create a string specifically for this, instead of
                // hijacking a string from notifications. This is necessary at this time
                // because string freeze is in the past and this string has the right
                // content at this moment.
                return dnsesResolved ? res.getString(R.string.switch_on_text)
                        : res.getString(R.string.private_dns_mode_opportunistic);
            case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
                return PrivateDnsModeDialogPreference.getHostnameFromSettings(cr);
                return dnsesResolved
                        ? PrivateDnsModeDialogPreference.getHostnameFromSettings(cr)
                        : res.getString(R.string.private_dns_mode_provider_failure);
        }
        return "";
    }
@@ -111,8 +137,25 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
        @Override
        public void onChange(boolean selfChange) {
            if (mPreference != null) {
                PrivateDnsPreferenceController.this.updateState(mPreference);
                updateState(mPreference);
            }
        }
    }

    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
        @Override
        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
            mLatestLinkProperties = lp;
            if (mPreference != null) {
                updateState(mPreference);
            }
        }
        @Override
        public void onLost(Network network) {
            mLatestLinkProperties = null;
            if (mPreference != null) {
                updateState(mPreference);
            }
        }
    };
}
+91 −0
Original line number Diff line number Diff line
@@ -24,17 +24,29 @@ import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
import static org.mockito.Mockito.when;

import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.os.Handler;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -46,22 +58,45 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContentResolver;
import org.robolectric.shadows.ShadowServiceManager;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@RunWith(SettingsRobolectricTestRunner.class)
public class PrivateDnsPreferenceControllerTest {

    private final static String HOSTNAME = "dns.example.com";
    private final static List<InetAddress> NON_EMPTY_ADDRESS_LIST;
    static {
        try {
            NON_EMPTY_ADDRESS_LIST = Arrays.asList(
                    InetAddress.getByAddress(new byte[] { 8, 8, 8, 8 }));
        } catch (UnknownHostException e) {
            throw new RuntimeException("Invalid hardcoded IP addresss: " + e);
        }
    }

    @Mock
    private PreferenceScreen mScreen;
    @Mock
    private ConnectivityManager mConnectivityManager;
    @Mock
    private Network mNetwork;
    @Mock
    private Preference mPreference;
    @Captor
    private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
    private PrivateDnsPreferenceController mController;
    private Context mContext;
    private ContentResolver mContentResolver;
@@ -75,15 +110,41 @@ public class PrivateDnsPreferenceControllerTest {
        mContext = spy(RuntimeEnvironment.application);
        mContentResolver = mContext.getContentResolver();
        mShadowContentResolver = Shadow.extract(mContentResolver);
        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
                .thenReturn(mConnectivityManager);
        doNothing().when(mConnectivityManager).registerDefaultNetworkCallback(
                mCallbackCaptor.capture(), nullable(Handler.class));

        when(mScreen.findPreference(anyString())).thenReturn(mPreference);

        mController = spy(new PrivateDnsPreferenceController(mContext));

        mLifecycleOwner = () -> mLifecycle;
        mLifecycle = new Lifecycle(mLifecycleOwner);
        mLifecycle.addObserver(mController);
    }

    private void updateLinkProperties(LinkProperties lp) {
        NetworkCallback nc = mCallbackCaptor.getValue();
        // The network callback that has been captured by the captor is the `mNetworkCallback'
        // member of mController. mController being a spy, it has copied that member from the
        // original object it was spying on, which means the object returned by the captor
        // has a reference to the original object instead of the mock as its outer instance
        // and will call methods and modify members of the original object instead of the spy,
        // so methods subsequently called on the spy will not be aware of the changes. To work
        // around this, the following code will create a new instance of the same class with
        // the same code, but it sets the spy as the outer instance.
        // A more recent version of Mockito would have made possible to create the spy with
        // spy(PrivateDnsPreferenceController.class, withSettings().useConstructor(mContext))
        // and that would have solved the problem by removing the original object entirely
        // in a more elegant manner, but useConstructor(Object...) is only available starting
        // with Mockito 2.7.14. Other solutions involve modifying the code under test for
        // the sake of the test.
        nc = mock(nc.getClass(), withSettings().useConstructor().outerInstance(mController)
                .defaultAnswer(CALLS_REAL_METHODS));
        nc.onLinkPropertiesChanged(mNetwork, lp);
    }

    @Test
    public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() {
        mLifecycle.handleLifecycleEvent(ON_START);
@@ -113,20 +174,50 @@ public class PrivateDnsPreferenceControllerTest {

    @Test
    public void getSummary_PrivateDnsModeOpportunistic() {
        mLifecycle.handleLifecycleEvent(ON_START);
        setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
        setPrivateDnsProviderHostname(HOSTNAME);
        mController.updateState(mPreference);
        verify(mController, atLeastOnce()).getSummary();
        verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));

        LinkProperties lp = mock(LinkProperties.class);
        when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST);
        updateLinkProperties(lp);
        mController.updateState(mPreference);
        verify(mPreference).setSummary(getResourceString(R.string.switch_on_text));

        reset(mPreference);
        lp = mock(LinkProperties.class);
        when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList());
        updateLinkProperties(lp);
        mController.updateState(mPreference);
        verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
    }

    @Test
    public void getSummary_PrivateDnsModeProviderHostname() {
        mLifecycle.handleLifecycleEvent(ON_START);
        setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
        setPrivateDnsProviderHostname(HOSTNAME);
        mController.updateState(mPreference);
        verify(mController, atLeastOnce()).getSummary();
        verify(mPreference).setSummary(
                getResourceString(R.string.private_dns_mode_provider_failure));

        LinkProperties lp = mock(LinkProperties.class);
        when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST);
        updateLinkProperties(lp);
        mController.updateState(mPreference);
        verify(mPreference).setSummary(HOSTNAME);

        reset(mPreference);
        lp = mock(LinkProperties.class);
        when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList());
        updateLinkProperties(lp);
        mController.updateState(mPreference);
        verify(mPreference).setSummary(
                getResourceString(R.string.private_dns_mode_provider_failure));
    }

    private void setPrivateDnsMode(String mode) {