Show the status of private DNS.
This works as follows : Off → "Off" Opportunistic, inactive → "Automatic" Opportunistic, active → "On" (stealing a string from notifications for this) Strict, not resolved and/or not validated → "Couldn't connect" Strict, resolved and validated → Set up hostname Bug: 73641539 Test: manual, and updated tests pass Change-Id: Id1132467288d51aa9cb81a04db65dee438ddfad9
This commit is contained in:
@@ -24,6 +24,10 @@ import android.content.Context;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.ContentObserver;
|
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.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@@ -31,6 +35,7 @@ import android.provider.Settings;
|
|||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
import android.support.v7.preference.PreferenceScreen;
|
import android.support.v7.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.core.BasePreferenceController;
|
import com.android.settings.core.BasePreferenceController;
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
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.events.OnStop;
|
||||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class PrivateDnsPreferenceController extends BasePreferenceController
|
public class PrivateDnsPreferenceController extends BasePreferenceController
|
||||||
implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop {
|
implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop {
|
||||||
@@ -50,12 +57,15 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
|
|||||||
|
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final ContentObserver mSettingsObserver;
|
private final ContentObserver mSettingsObserver;
|
||||||
|
private final ConnectivityManager mConnectivityManager;
|
||||||
|
private LinkProperties mLatestLinkProperties;
|
||||||
private Preference mPreference;
|
private Preference mPreference;
|
||||||
|
|
||||||
public PrivateDnsPreferenceController(Context context) {
|
public PrivateDnsPreferenceController(Context context) {
|
||||||
super(context, KEY_PRIVATE_DNS_SETTINGS);
|
super(context, KEY_PRIVATE_DNS_SETTINGS);
|
||||||
mHandler = new Handler(Looper.getMainLooper());
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
mSettingsObserver = new PrivateDnsSettingsObserver(mHandler);
|
mSettingsObserver = new PrivateDnsSettingsObserver(mHandler);
|
||||||
|
mConnectivityManager = context.getSystemService(ConnectivityManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,11 +90,17 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
|
|||||||
for (Uri uri : SETTINGS_URIS) {
|
for (Uri uri : SETTINGS_URIS) {
|
||||||
mContext.getContentResolver().registerContentObserver(uri, false, mSettingsObserver);
|
mContext.getContentResolver().registerContentObserver(uri, false, mSettingsObserver);
|
||||||
}
|
}
|
||||||
|
final Network defaultNetwork = mConnectivityManager.getActiveNetwork();
|
||||||
|
if (defaultNetwork != null) {
|
||||||
|
mLatestLinkProperties = mConnectivityManager.getLinkProperties(defaultNetwork);
|
||||||
|
}
|
||||||
|
mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback, mHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
|
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
|
||||||
|
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,13 +108,23 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
|
|||||||
final Resources res = mContext.getResources();
|
final Resources res = mContext.getResources();
|
||||||
final ContentResolver cr = mContext.getContentResolver();
|
final ContentResolver cr = mContext.getContentResolver();
|
||||||
final String mode = PrivateDnsModeDialogPreference.getModeFromSettings(cr);
|
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) {
|
switch (mode) {
|
||||||
case PRIVATE_DNS_MODE_OFF:
|
case PRIVATE_DNS_MODE_OFF:
|
||||||
return res.getString(R.string.private_dns_mode_off);
|
return res.getString(R.string.private_dns_mode_off);
|
||||||
case PRIVATE_DNS_MODE_OPPORTUNISTIC:
|
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:
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -111,8 +137,25 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
|
|||||||
@Override
|
@Override
|
||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
if (mPreference != null) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -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_MODE;
|
||||||
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
|
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.any;
|
||||||
import static org.mockito.Matchers.anyString;
|
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.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.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.withSettings;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.arch.lifecycle.LifecycleOwner;
|
import android.arch.lifecycle.LifecycleOwner;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.database.ContentObserver;
|
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 android.provider.Settings;
|
||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
import android.support.v7.preference.PreferenceScreen;
|
import android.support.v7.preference.PreferenceScreen;
|
||||||
@@ -46,22 +58,45 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
import org.robolectric.shadow.api.Shadow;
|
import org.robolectric.shadow.api.Shadow;
|
||||||
import org.robolectric.shadows.ShadowContentResolver;
|
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)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
public class PrivateDnsPreferenceControllerTest {
|
public class PrivateDnsPreferenceControllerTest {
|
||||||
|
|
||||||
private final static String HOSTNAME = "dns.example.com";
|
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
|
@Mock
|
||||||
private PreferenceScreen mScreen;
|
private PreferenceScreen mScreen;
|
||||||
@Mock
|
@Mock
|
||||||
|
private ConnectivityManager mConnectivityManager;
|
||||||
|
@Mock
|
||||||
|
private Network mNetwork;
|
||||||
|
@Mock
|
||||||
private Preference mPreference;
|
private Preference mPreference;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
|
||||||
private PrivateDnsPreferenceController mController;
|
private PrivateDnsPreferenceController mController;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private ContentResolver mContentResolver;
|
private ContentResolver mContentResolver;
|
||||||
@@ -75,15 +110,41 @@ public class PrivateDnsPreferenceControllerTest {
|
|||||||
mContext = spy(RuntimeEnvironment.application);
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
mContentResolver = mContext.getContentResolver();
|
mContentResolver = mContext.getContentResolver();
|
||||||
mShadowContentResolver = Shadow.extract(mContentResolver);
|
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);
|
when(mScreen.findPreference(anyString())).thenReturn(mPreference);
|
||||||
|
|
||||||
mController = spy(new PrivateDnsPreferenceController(mContext));
|
mController = spy(new PrivateDnsPreferenceController(mContext));
|
||||||
|
|
||||||
mLifecycleOwner = () -> mLifecycle;
|
mLifecycleOwner = () -> mLifecycle;
|
||||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||||
mLifecycle.addObserver(mController);
|
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
|
@Test
|
||||||
public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() {
|
public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() {
|
||||||
mLifecycle.handleLifecycleEvent(ON_START);
|
mLifecycle.handleLifecycleEvent(ON_START);
|
||||||
@@ -113,20 +174,50 @@ public class PrivateDnsPreferenceControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getSummary_PrivateDnsModeOpportunistic() {
|
public void getSummary_PrivateDnsModeOpportunistic() {
|
||||||
|
mLifecycle.handleLifecycleEvent(ON_START);
|
||||||
setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
|
setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
|
||||||
setPrivateDnsProviderHostname(HOSTNAME);
|
setPrivateDnsProviderHostname(HOSTNAME);
|
||||||
mController.updateState(mPreference);
|
mController.updateState(mPreference);
|
||||||
verify(mController, atLeastOnce()).getSummary();
|
verify(mController, atLeastOnce()).getSummary();
|
||||||
verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
|
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
|
@Test
|
||||||
public void getSummary_PrivateDnsModeProviderHostname() {
|
public void getSummary_PrivateDnsModeProviderHostname() {
|
||||||
|
mLifecycle.handleLifecycleEvent(ON_START);
|
||||||
setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
|
setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
|
||||||
setPrivateDnsProviderHostname(HOSTNAME);
|
setPrivateDnsProviderHostname(HOSTNAME);
|
||||||
mController.updateState(mPreference);
|
mController.updateState(mPreference);
|
||||||
verify(mController, atLeastOnce()).getSummary();
|
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);
|
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) {
|
private void setPrivateDnsMode(String mode) {
|
||||||
|
Reference in New Issue
Block a user