• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.network;
18 
19 import static android.arch.lifecycle.Lifecycle.Event.ON_START;
20 import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
21 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
22 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
23 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
24 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
25 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
26 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
27 import static com.google.common.truth.Truth.assertThat;
28 import static org.mockito.ArgumentMatchers.nullable;
29 import static org.mockito.Matchers.any;
30 import static org.mockito.Matchers.anyString;
31 import static org.mockito.Mockito.CALLS_REAL_METHODS;
32 import static org.mockito.Mockito.atLeastOnce;
33 import static org.mockito.Mockito.doNothing;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.reset;
36 import static org.mockito.Mockito.spy;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.withSettings;
40 import static org.mockito.Mockito.when;
41 
42 import android.arch.lifecycle.LifecycleOwner;
43 import android.content.Context;
44 import android.content.ContentResolver;
45 import android.database.ContentObserver;
46 import android.net.ConnectivityManager;
47 import android.net.ConnectivityManager.NetworkCallback;
48 import android.net.LinkProperties;
49 import android.net.Network;
50 import android.os.Handler;
51 import android.provider.Settings;
52 import android.support.v7.preference.Preference;
53 import android.support.v7.preference.PreferenceScreen;
54 
55 import com.android.settings.R;
56 import com.android.settings.testutils.SettingsRobolectricTestRunner;
57 import com.android.settingslib.core.lifecycle.Lifecycle;
58 
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.ArgumentCaptor;
63 import org.mockito.Captor;
64 import org.mockito.Mock;
65 import org.mockito.MockitoAnnotations;
66 import org.robolectric.RuntimeEnvironment;
67 import org.robolectric.shadow.api.Shadow;
68 import org.robolectric.shadows.ShadowContentResolver;
69 import org.robolectric.shadows.ShadowServiceManager;
70 
71 import java.net.InetAddress;
72 import java.net.UnknownHostException;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.List;
76 
77 @RunWith(SettingsRobolectricTestRunner.class)
78 public class PrivateDnsPreferenceControllerTest {
79 
80     private final static String HOSTNAME = "dns.example.com";
81     private final static List<InetAddress> NON_EMPTY_ADDRESS_LIST;
82     static {
83         try {
84             NON_EMPTY_ADDRESS_LIST = Arrays.asList(
85                     InetAddress.getByAddress(new byte[] { 8, 8, 8, 8 }));
86         } catch (UnknownHostException e) {
87             throw new RuntimeException("Invalid hardcoded IP addresss: " + e);
88         }
89     }
90 
91     @Mock
92     private PreferenceScreen mScreen;
93     @Mock
94     private ConnectivityManager mConnectivityManager;
95     @Mock
96     private Network mNetwork;
97     @Mock
98     private Preference mPreference;
99     @Captor
100     private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
101     private PrivateDnsPreferenceController mController;
102     private Context mContext;
103     private ContentResolver mContentResolver;
104     private ShadowContentResolver mShadowContentResolver;
105     private Lifecycle mLifecycle;
106     private LifecycleOwner mLifecycleOwner;
107 
108     @Before
setUp()109     public void setUp() {
110         MockitoAnnotations.initMocks(this);
111         mContext = spy(RuntimeEnvironment.application);
112         mContentResolver = mContext.getContentResolver();
113         mShadowContentResolver = Shadow.extract(mContentResolver);
114         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
115                 .thenReturn(mConnectivityManager);
116         doNothing().when(mConnectivityManager).registerDefaultNetworkCallback(
117                 mCallbackCaptor.capture(), nullable(Handler.class));
118 
119         when(mScreen.findPreference(anyString())).thenReturn(mPreference);
120 
121         mController = spy(new PrivateDnsPreferenceController(mContext));
122 
123         mLifecycleOwner = () -> mLifecycle;
124         mLifecycle = new Lifecycle(mLifecycleOwner);
125         mLifecycle.addObserver(mController);
126     }
127 
updateLinkProperties(LinkProperties lp)128     private void updateLinkProperties(LinkProperties lp) {
129         NetworkCallback nc = mCallbackCaptor.getValue();
130         // The network callback that has been captured by the captor is the `mNetworkCallback'
131         // member of mController. mController being a spy, it has copied that member from the
132         // original object it was spying on, which means the object returned by the captor
133         // has a reference to the original object instead of the mock as its outer instance
134         // and will call methods and modify members of the original object instead of the spy,
135         // so methods subsequently called on the spy will not be aware of the changes. To work
136         // around this, the following code will create a new instance of the same class with
137         // the same code, but it sets the spy as the outer instance.
138         // A more recent version of Mockito would have made possible to create the spy with
139         // spy(PrivateDnsPreferenceController.class, withSettings().useConstructor(mContext))
140         // and that would have solved the problem by removing the original object entirely
141         // in a more elegant manner, but useConstructor(Object...) is only available starting
142         // with Mockito 2.7.14. Other solutions involve modifying the code under test for
143         // the sake of the test.
144         nc = mock(nc.getClass(), withSettings().useConstructor().outerInstance(mController)
145                 .defaultAnswer(CALLS_REAL_METHODS));
146         nc.onLinkPropertiesChanged(mNetwork, lp);
147     }
148 
149     @Test
goThroughLifecycle_shouldRegisterUnregisterSettingsObserver()150     public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() {
151         mLifecycle.handleLifecycleEvent(ON_START);
152         verify(mContext, atLeastOnce()).getContentResolver();
153         assertThat(mShadowContentResolver.getContentObservers(
154                 Settings.Global.getUriFor(PRIVATE_DNS_MODE))).isNotEmpty();
155         assertThat(mShadowContentResolver.getContentObservers(
156                 Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER))).isNotEmpty();
157 
158 
159         mLifecycle.handleLifecycleEvent(ON_STOP);
160         verify(mContext, atLeastOnce()).getContentResolver();
161         assertThat(mShadowContentResolver.getContentObservers(
162                 Settings.Global.getUriFor(PRIVATE_DNS_MODE))).isEmpty();
163         assertThat(mShadowContentResolver.getContentObservers(
164                 Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER))).isEmpty();
165     }
166 
167     @Test
getSummary_PrivateDnsModeOff()168     public void getSummary_PrivateDnsModeOff() {
169         setPrivateDnsMode(PRIVATE_DNS_MODE_OFF);
170         setPrivateDnsProviderHostname(HOSTNAME);
171         mController.updateState(mPreference);
172         verify(mController, atLeastOnce()).getSummary();
173         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_off));
174     }
175 
176     @Test
getSummary_PrivateDnsModeOpportunistic()177     public void getSummary_PrivateDnsModeOpportunistic() {
178         mLifecycle.handleLifecycleEvent(ON_START);
179         setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
180         setPrivateDnsProviderHostname(HOSTNAME);
181         mController.updateState(mPreference);
182         verify(mController, atLeastOnce()).getSummary();
183         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
184 
185         LinkProperties lp = mock(LinkProperties.class);
186         when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST);
187         updateLinkProperties(lp);
188         mController.updateState(mPreference);
189         verify(mPreference).setSummary(getResourceString(R.string.switch_on_text));
190 
191         reset(mPreference);
192         lp = mock(LinkProperties.class);
193         when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList());
194         updateLinkProperties(lp);
195         mController.updateState(mPreference);
196         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
197     }
198 
199     @Test
getSummary_PrivateDnsModeProviderHostname()200     public void getSummary_PrivateDnsModeProviderHostname() {
201         mLifecycle.handleLifecycleEvent(ON_START);
202         setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
203         setPrivateDnsProviderHostname(HOSTNAME);
204         mController.updateState(mPreference);
205         verify(mController, atLeastOnce()).getSummary();
206         verify(mPreference).setSummary(
207                 getResourceString(R.string.private_dns_mode_provider_failure));
208 
209         LinkProperties lp = mock(LinkProperties.class);
210         when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST);
211         updateLinkProperties(lp);
212         mController.updateState(mPreference);
213         verify(mPreference).setSummary(HOSTNAME);
214 
215         reset(mPreference);
216         lp = mock(LinkProperties.class);
217         when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList());
218         updateLinkProperties(lp);
219         mController.updateState(mPreference);
220         verify(mPreference).setSummary(
221                 getResourceString(R.string.private_dns_mode_provider_failure));
222     }
223 
224     @Test
getSummary_PrivateDnsDefaultMode()225     public void getSummary_PrivateDnsDefaultMode() {
226         // Default mode is opportunistic, unless overridden by a Settings push.
227         setPrivateDnsMode("");
228         setPrivateDnsProviderHostname("");
229         mController.updateState(mPreference);
230         verify(mController, atLeastOnce()).getSummary();
231         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
232 
233         reset(mController);
234         reset(mPreference);
235         // Pretend an emergency gservices setting has disabled default-opportunistic.
236         Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, PRIVATE_DNS_MODE_OFF);
237         mController.updateState(mPreference);
238         verify(mController, atLeastOnce()).getSummary();
239         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_off));
240 
241         reset(mController);
242         reset(mPreference);
243         // The user interacting with the Private DNS menu, explicitly choosing
244         // opportunistic mode, will be able to use despite the change to the
245         // default setting above.
246         setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
247         mController.updateState(mPreference);
248         verify(mController, atLeastOnce()).getSummary();
249         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
250     }
251 
setPrivateDnsMode(String mode)252     private void setPrivateDnsMode(String mode) {
253         Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, mode);
254     }
255 
setPrivateDnsProviderHostname(String name)256     private void setPrivateDnsProviderHostname(String name) {
257         Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, name);
258     }
259 
getResourceString(int which)260     private String getResourceString(int which) {
261         return mContext.getResources().getString(which);
262     }
263 }
264