Loading src/com/android/settings/network/PrivateDnsPreferenceController.java +46 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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 Loading @@ -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 Loading @@ -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 ""; } Loading @@ -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); } } }; } tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java +91 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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); Loading Loading @@ -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) { Loading Loading
src/com/android/settings/network/PrivateDnsPreferenceController.java +46 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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 Loading @@ -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 Loading @@ -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 ""; } Loading @@ -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); } } }; }
tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java +91 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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); Loading Loading @@ -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) { Loading