• 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.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
20 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
21 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
22 import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
23 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
24 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
25 
26 import static androidx.lifecycle.Lifecycle.Event.ON_START;
27 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
28 
29 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
30 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 
34 import static org.mockito.ArgumentMatchers.anyString;
35 import static org.mockito.ArgumentMatchers.nullable;
36 import static org.mockito.Mockito.CALLS_REAL_METHODS;
37 import static org.mockito.Mockito.atLeastOnce;
38 import static org.mockito.Mockito.doNothing;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.reset;
41 import static org.mockito.Mockito.spy;
42 import static org.mockito.Mockito.verify;
43 import static org.mockito.Mockito.when;
44 import static org.mockito.Mockito.withSettings;
45 
46 import android.content.ComponentName;
47 import android.content.ContentResolver;
48 import android.content.Context;
49 import android.net.ConnectivityManager;
50 import android.net.ConnectivityManager.NetworkCallback;
51 import android.net.LinkProperties;
52 import android.net.Network;
53 import android.os.Handler;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.provider.Settings;
57 
58 import androidx.lifecycle.LifecycleOwner;
59 import androidx.preference.Preference;
60 import androidx.preference.PreferenceScreen;
61 
62 import com.android.settings.R;
63 import com.android.settings.testutils.shadow.ShadowUserManager;
64 import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
65 import com.android.settingslib.core.lifecycle.Lifecycle;
66 
67 import org.junit.Before;
68 import org.junit.Test;
69 import org.junit.runner.RunWith;
70 import org.mockito.ArgumentCaptor;
71 import org.mockito.Captor;
72 import org.mockito.Mock;
73 import org.mockito.MockitoAnnotations;
74 import org.robolectric.RobolectricTestRunner;
75 import org.robolectric.RuntimeEnvironment;
76 import org.robolectric.annotation.Config;
77 import org.robolectric.shadow.api.Shadow;
78 import org.robolectric.shadows.ShadowContentResolver;
79 
80 import java.net.InetAddress;
81 import java.net.UnknownHostException;
82 import java.util.Arrays;
83 import java.util.Collections;
84 import java.util.List;
85 
86 @RunWith(RobolectricTestRunner.class)
87 @Config(shadows = {
88     ShadowUserManager.class,
89     ShadowDevicePolicyManager.class
90 })
91 public class PrivateDnsPreferenceControllerTest {
92 
93     private final static String HOSTNAME = "dns.example.com";
94     private final static List<InetAddress> NON_EMPTY_ADDRESS_LIST;
95     static {
96         try {
97             NON_EMPTY_ADDRESS_LIST = Arrays.asList(
98                     InetAddress.getByAddress(new byte[] { 8, 8, 8, 8 }));
99         } catch (UnknownHostException e) {
100             throw new RuntimeException("Invalid hardcoded IP addresss: " + e);
101         }
102     }
103 
104     @Mock
105     private PreferenceScreen mScreen;
106     @Mock
107     private ConnectivityManager mConnectivityManager;
108     @Mock
109     private Network mNetwork;
110     @Mock
111     private Preference mPreference;
112     @Captor
113     private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
114     private PrivateDnsPreferenceController mController;
115     private Context mContext;
116     private ContentResolver mContentResolver;
117     private ShadowContentResolver mShadowContentResolver;
118     private Lifecycle mLifecycle;
119     private LifecycleOwner mLifecycleOwner;
120     private ShadowUserManager mShadowUserManager;
121 
122     @Before
setUp()123     public void setUp() {
124         MockitoAnnotations.initMocks(this);
125         mContext = spy(RuntimeEnvironment.application);
126         mContentResolver = mContext.getContentResolver();
127         mShadowContentResolver = Shadow.extract(mContentResolver);
128         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
129                 .thenReturn(mConnectivityManager);
130         doNothing().when(mConnectivityManager).registerDefaultNetworkCallback(
131                 mCallbackCaptor.capture(), nullable(Handler.class));
132 
133         when(mScreen.findPreference(anyString())).thenReturn(mPreference);
134 
135         mController = spy(new PrivateDnsPreferenceController(mContext));
136 
137         mLifecycleOwner = () -> mLifecycle;
138         mLifecycle = new Lifecycle(mLifecycleOwner);
139         mLifecycle.addObserver(mController);
140 
141         mShadowUserManager = ShadowUserManager.getShadow();
142     }
143 
updateLinkProperties(LinkProperties lp)144     private void updateLinkProperties(LinkProperties lp) {
145         NetworkCallback nc = mCallbackCaptor.getValue();
146         // The network callback that has been captured by the captor is the `mNetworkCallback'
147         // member of mController. mController being a spy, it has copied that member from the
148         // original object it was spying on, which means the object returned by the captor
149         // has a reference to the original object instead of the mock as its outer instance
150         // and will call methods and modify members of the original object instead of the spy,
151         // so methods subsequently called on the spy will not be aware of the changes. To work
152         // around this, the following code will create a new instance of the same class with
153         // the same code, but it sets the spy as the outer instance.
154         // A more recent version of Mockito would have made possible to create the spy with
155         // spy(PrivateDnsPreferenceController.class, withSettings().useConstructor(mContext))
156         // and that would have solved the problem by removing the original object entirely
157         // in a more elegant manner, but useConstructor(Object...) is only available starting
158         // with Mockito 2.7.14. Other solutions involve modifying the code under test for
159         // the sake of the test.
160         nc = mock(nc.getClass(), withSettings().useConstructor().outerInstance(mController)
161                 .defaultAnswer(CALLS_REAL_METHODS));
162         nc.onLinkPropertiesChanged(mNetwork, lp);
163     }
164 
165     @Test
getAvailibilityStatus_availableByDefault()166     public void getAvailibilityStatus_availableByDefault() {
167         assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
168     }
169 
170     @Test
171     @Config(qualifiers = "mcc999")
getAvailabilityStatus_unsupportedWhenSet()172     public void getAvailabilityStatus_unsupportedWhenSet() {
173         assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
174     }
175 
176     @Test
goThroughLifecycle_shouldRegisterUnregisterSettingsObserver()177     public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() {
178         mLifecycle.handleLifecycleEvent(ON_START);
179         verify(mContext, atLeastOnce()).getContentResolver();
180         assertThat(mShadowContentResolver.getContentObservers(
181                 Settings.Global.getUriFor(PRIVATE_DNS_MODE))).isNotEmpty();
182         assertThat(mShadowContentResolver.getContentObservers(
183                 Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER))).isNotEmpty();
184 
185 
186         mLifecycle.handleLifecycleEvent(ON_STOP);
187         verify(mContext, atLeastOnce()).getContentResolver();
188         assertThat(mShadowContentResolver.getContentObservers(
189                 Settings.Global.getUriFor(PRIVATE_DNS_MODE))).isEmpty();
190         assertThat(mShadowContentResolver.getContentObservers(
191                 Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER))).isEmpty();
192     }
193 
194     @Test
getSummary_PrivateDnsModeOff()195     public void getSummary_PrivateDnsModeOff() {
196         setPrivateDnsMode(PRIVATE_DNS_MODE_OFF);
197         setPrivateDnsProviderHostname(HOSTNAME);
198         mController.updateState(mPreference);
199         verify(mController, atLeastOnce()).getSummary();
200         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_off));
201     }
202 
203     @Test
getSummary_PrivateDnsModeOpportunistic()204     public void getSummary_PrivateDnsModeOpportunistic() {
205         mLifecycle.handleLifecycleEvent(ON_START);
206         setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
207         setPrivateDnsProviderHostname(HOSTNAME);
208         mController.updateState(mPreference);
209         verify(mController, atLeastOnce()).getSummary();
210         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
211 
212         LinkProperties lp = mock(LinkProperties.class);
213         when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST);
214         updateLinkProperties(lp);
215         mController.updateState(mPreference);
216         verify(mPreference).setSummary(getResourceString(R.string.switch_on_text));
217 
218         reset(mPreference);
219         lp = mock(LinkProperties.class);
220         when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList());
221         updateLinkProperties(lp);
222         mController.updateState(mPreference);
223         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
224     }
225 
226     @Test
getSummary_PrivateDnsModeProviderHostname()227     public void getSummary_PrivateDnsModeProviderHostname() {
228         mLifecycle.handleLifecycleEvent(ON_START);
229         setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
230         setPrivateDnsProviderHostname(HOSTNAME);
231         mController.updateState(mPreference);
232         verify(mController, atLeastOnce()).getSummary();
233         verify(mPreference).setSummary(
234                 getResourceString(R.string.private_dns_mode_provider_failure));
235 
236         LinkProperties lp = mock(LinkProperties.class);
237         when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST);
238         updateLinkProperties(lp);
239         mController.updateState(mPreference);
240         verify(mPreference).setSummary(HOSTNAME);
241 
242         reset(mPreference);
243         lp = mock(LinkProperties.class);
244         when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList());
245         updateLinkProperties(lp);
246         mController.updateState(mPreference);
247         verify(mPreference).setSummary(
248                 getResourceString(R.string.private_dns_mode_provider_failure));
249     }
250 
251     @Test
getSummary_PrivateDnsDefaultMode()252     public void getSummary_PrivateDnsDefaultMode() {
253         // Default mode is opportunistic, unless overridden by a Settings push.
254         setPrivateDnsMode("");
255         setPrivateDnsProviderHostname("");
256         mController.updateState(mPreference);
257         verify(mController, atLeastOnce()).getSummary();
258         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
259 
260         reset(mController);
261         reset(mPreference);
262         // Pretend an emergency gservices setting has disabled default-opportunistic.
263         Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, PRIVATE_DNS_MODE_OFF);
264         mController.updateState(mPreference);
265         verify(mController, atLeastOnce()).getSummary();
266         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_off));
267 
268         reset(mController);
269         reset(mPreference);
270         // The user interacting with the Private DNS menu, explicitly choosing
271         // opportunistic mode, will be able to use despite the change to the
272         // default setting above.
273         setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC);
274         mController.updateState(mPreference);
275         verify(mController, atLeastOnce()).getSummary();
276         verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic));
277     }
278 
279     @Test
isEnabled_canBeDisabledByAdmin()280     public void isEnabled_canBeDisabledByAdmin() {
281         final int userId = UserHandle.myUserId();
282         final List<UserManager.EnforcingUser> enforcingUsers = Collections.singletonList(
283                 new UserManager.EnforcingUser(userId,
284                         UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)
285         );
286         mShadowUserManager.setUserRestrictionSources(
287                 UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
288                 UserHandle.of(userId),
289                 enforcingUsers);
290 
291         ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(
292                 new ComponentName("test", "test"));
293 
294         mController.updateState(mPreference);
295         verify(mPreference).setEnabled(false);
296     }
297 
298     @Test
isEnabled_isEnabledByDefault()299     public void isEnabled_isEnabledByDefault() {
300         mController.updateState(mPreference);
301         verify(mPreference).setEnabled(true);
302     }
303 
setPrivateDnsMode(String mode)304     private void setPrivateDnsMode(String mode) {
305         Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, mode);
306     }
307 
setPrivateDnsProviderHostname(String name)308     private void setPrivateDnsProviderHostname(String name) {
309         Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, name);
310     }
311 
getResourceString(int which)312     private String getResourceString(int which) {
313         return mContext.getResources().getString(which);
314     }
315 }
316