blob: e58811b4c08e8215ed9bd32d6d938957e6162d57 [file] [log] [blame]
Remi NGUYEN VAN6a7a5a12018-04-02 21:18:52 +09001/*
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
17package com.android.server.connectivity;
18
19import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
20import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
21import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
22import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
23import static android.net.NetworkPolicy.LIMIT_DISABLED;
24import static android.net.NetworkPolicy.SNOOZE_NEVER;
25import static android.net.NetworkPolicy.WARNING_DISABLED;
26import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
27
28import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
29import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
30
31import static junit.framework.TestCase.assertEquals;
32import static junit.framework.TestCase.assertNotNull;
33
34import static org.mockito.ArgumentMatchers.any;
35import static org.mockito.ArgumentMatchers.anyInt;
36import static org.mockito.ArgumentMatchers.argThat;
37import static org.mockito.ArgumentMatchers.eq;
38import static org.mockito.Mockito.times;
39import static org.mockito.Mockito.verify;
40import static org.mockito.Mockito.when;
41
42import android.app.usage.NetworkStatsManager;
43import android.content.BroadcastReceiver;
44import android.content.Context;
45import android.content.Intent;
46import android.content.pm.ApplicationInfo;
47import android.content.res.Resources;
48import android.net.ConnectivityManager;
49import android.net.Network;
50import android.net.NetworkCapabilities;
51import android.net.NetworkPolicy;
52import android.net.NetworkPolicyManager;
53import android.net.NetworkTemplate;
54import android.net.StringNetworkSpecifier;
55import android.os.Handler;
56import android.provider.Settings;
57import android.support.test.filters.SmallTest;
58import android.support.test.runner.AndroidJUnit4;
59import android.telephony.TelephonyManager;
60import android.test.mock.MockContentResolver;
61import android.util.DataUnit;
62import android.util.RecurrenceRule;
63
64import com.android.internal.R;
65import com.android.internal.util.test.FakeSettingsProvider;
66import com.android.server.LocalServices;
67import com.android.server.net.NetworkPolicyManagerInternal;
68import com.android.server.net.NetworkStatsManagerInternal;
69
70import org.junit.After;
71import org.junit.Before;
72import org.junit.Test;
73import org.junit.runner.RunWith;
74import org.mockito.ArgumentCaptor;
75import org.mockito.Mock;
76import org.mockito.Mockito;
77import org.mockito.MockitoAnnotations;
78
79import java.time.Clock;
80import java.time.Instant;
81import java.time.Period;
82import java.time.ZoneId;
83import java.time.ZonedDateTime;
84import java.time.temporal.ChronoUnit;
85
86@RunWith(AndroidJUnit4.class)
87@SmallTest
88public class MultipathPolicyTrackerTest {
89 private static final Network TEST_NETWORK = new Network(123);
90 private static final int POLICY_SNOOZED = -100;
91
92 @Mock private Context mContext;
93 @Mock private Resources mResources;
94 @Mock private Handler mHandler;
95 @Mock private MultipathPolicyTracker.Dependencies mDeps;
96 @Mock private Clock mClock;
97 @Mock private ConnectivityManager mCM;
98 @Mock private NetworkPolicyManager mNPM;
99 @Mock private NetworkStatsManager mStatsManager;
100 @Mock private NetworkPolicyManagerInternal mNPMI;
101 @Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal;
102 @Mock private TelephonyManager mTelephonyManager;
103 private MockContentResolver mContentResolver;
104
105 private ArgumentCaptor<BroadcastReceiver> mConfigChangeReceiverCaptor;
106
107 private MultipathPolicyTracker mTracker;
108
109 private Clock mPreviousRecurrenceRuleClock;
110 private boolean mRecurrenceRuleClockMocked;
111
112 private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
113 when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
114 when(mContext.getSystemService(serviceName)).thenReturn(service);
115 }
116
117 @Before
118 public void setUp() {
119 MockitoAnnotations.initMocks(this);
120
121 mPreviousRecurrenceRuleClock = RecurrenceRule.sClock;
122 RecurrenceRule.sClock = mClock;
123 mRecurrenceRuleClockMocked = true;
124
125 mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
126
127 when(mContext.getResources()).thenReturn(mResources);
128 when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
129 when(mContext.registerReceiverAsUser(mConfigChangeReceiverCaptor.capture(),
130 any(), argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any()))
131 .thenReturn(null);
132
133 when(mDeps.getClock()).thenReturn(mClock);
134
135 when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
136
137 mContentResolver = Mockito.spy(new MockContentResolver(mContext));
138 mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
139 Settings.Global.clearProviderForTest();
140 when(mContext.getContentResolver()).thenReturn(mContentResolver);
141
142 mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM);
143 mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM);
144 mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager);
145 mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
146
147 LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
148 LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI);
149
150 LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
151 LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal);
152
153 mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps);
154 }
155
156 @After
157 public void tearDown() {
158 // Avoid setting static clock to null (which should normally not be the case)
159 // if MockitoAnnotations.initMocks threw an exception
160 if (mRecurrenceRuleClockMocked) {
161 RecurrenceRule.sClock = mPreviousRecurrenceRuleClock;
162 }
163 mRecurrenceRuleClockMocked = false;
164 }
165
166 private void setDefaultQuotaGlobalSetting(long setting) {
167 Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
168 (int) setting);
169 }
170
171 private void testGetMultipathPreference(
172 long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
173 long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
174
175 // TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly.
176 final ZonedDateTime now = ZonedDateTime.ofInstant(
177 Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault());
178 final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS);
179 when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli());
180 when(mClock.instant()).thenReturn(now.toInstant());
181 when(mClock.getZone()).thenReturn(ZoneId.systemDefault());
182
183 // Setup plan quota
184 when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
185 .thenReturn(subscriptionQuota);
186
187 // Setup user policy warning / limit
188 if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) {
189 final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z");
190 final RecurrenceRule recurrenceRule = new RecurrenceRule(
191 ZonedDateTime.ofInstant(
192 recurrenceStart,
193 ZoneId.systemDefault()),
194 null /* end */,
195 Period.ofMonths(1));
196 final boolean snoozeWarning = policyWarning == POLICY_SNOOZED;
197 final boolean snoozeLimit = policyLimit == POLICY_SNOOZED;
198 when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] {
199 new NetworkPolicy(
200 NetworkTemplate.buildTemplateMobileWildcard(),
201 recurrenceRule,
202 snoozeWarning ? 0 : policyWarning,
203 snoozeLimit ? 0 : policyLimit,
204 snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
205 snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
206 SNOOZE_NEVER,
207 true /* metered */,
208 false /* inferred */)
209 });
210 } else {
211 when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
212 }
213
214 // Setup default quota in settings and resources
215 if (defaultGlobalSetting > 0) {
216 setDefaultQuotaGlobalSetting(defaultGlobalSetting);
217 }
218 when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
219 .thenReturn((int) defaultResSetting);
220
221 when(mNetworkStatsManagerInternal.getNetworkTotalBytes(
222 any(),
223 eq(startOfDay.toInstant().toEpochMilli()),
224 eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday);
225
226 ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
227 ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
228 mTracker.start();
229 verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
230
231 // Simulate callback after capability changes
232 final NetworkCapabilities capabilities = new NetworkCapabilities()
233 .addCapability(NET_CAPABILITY_INTERNET)
234 .addTransportType(TRANSPORT_CELLULAR)
235 .setNetworkSpecifier(new StringNetworkSpecifier("234"));
236 if (!roaming) {
237 capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
238 }
239 networkCallback.getValue().onCapabilitiesChanged(
240 TEST_NETWORK,
241 capabilities);
242 }
243
244 @Test
245 public void testGetMultipathPreference_SubscriptionQuota() {
246 testGetMultipathPreference(
247 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
248 DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
249 DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
250 LIMIT_DISABLED,
251 DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
252 2_500_000 /* defaultResSetting */,
253 false /* roaming */);
254
255 verify(mStatsManager, times(1)).registerUsageCallback(
256 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
257 }
258
259 @Test
260 public void testGetMultipathPreference_UserWarningQuota() {
261 testGetMultipathPreference(
262 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
263 OPPORTUNISTIC_QUOTA_UNKNOWN,
264 // 29 days from Apr. 2nd to May 1st
265 DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */,
266 LIMIT_DISABLED,
267 DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
268 2_500_000 /* defaultResSetting */,
269 false /* roaming */);
270
271 // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
272 verify(mStatsManager, times(1)).registerUsageCallback(
273 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
274 }
275
276 @Test
277 public void testGetMultipathPreference_SnoozedWarningQuota() {
278 testGetMultipathPreference(
279 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
280 OPPORTUNISTIC_QUOTA_UNKNOWN,
281 // 29 days from Apr. 2nd to May 1st
282 POLICY_SNOOZED /* policyWarning */,
283 DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */,
284 DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
285 2_500_000 /* defaultResSetting */,
286 false /* roaming */);
287
288 // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
289 verify(mStatsManager, times(1)).registerUsageCallback(
290 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
291 }
292
293 @Test
294 public void testGetMultipathPreference_SnoozedBothQuota() {
295 testGetMultipathPreference(
296 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
297 OPPORTUNISTIC_QUOTA_UNKNOWN,
298 // 29 days from Apr. 2nd to May 1st
299 POLICY_SNOOZED /* policyWarning */,
300 POLICY_SNOOZED /* policyLimit */,
301 DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
302 2_500_000 /* defaultResSetting */,
303 false /* roaming */);
304
305 // Default global setting should be used: 12 - 7 = 5
306 verify(mStatsManager, times(1)).registerUsageCallback(
307 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
308 }
309
310 @Test
311 public void testGetMultipathPreference_SettingChanged() {
312 testGetMultipathPreference(
313 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
314 OPPORTUNISTIC_QUOTA_UNKNOWN,
315 WARNING_DISABLED,
316 LIMIT_DISABLED,
317 -1 /* defaultGlobalSetting */,
318 DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */,
319 false /* roaming */);
320
321 verify(mStatsManager, times(1)).registerUsageCallback(
322 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
323
324 // Update setting
325 setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
326 mTracker.mSettingsObserver.onChange(
327 false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES));
328
329 // Callback must have been re-registered with new setting
330 verify(mStatsManager, times(1)).unregisterUsageCallback(any());
331 verify(mStatsManager, times(1)).registerUsageCallback(
332 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
333 }
334
335 @Test
336 public void testGetMultipathPreference_ResourceChanged() {
337 testGetMultipathPreference(
338 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
339 OPPORTUNISTIC_QUOTA_UNKNOWN,
340 WARNING_DISABLED,
341 LIMIT_DISABLED,
342 -1 /* defaultGlobalSetting */,
343 DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */,
344 false /* roaming */);
345
346 verify(mStatsManager, times(1)).registerUsageCallback(
347 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
348
349 when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
350 .thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
351
352 final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue();
353 assertNotNull(configChangeReceiver);
354 configChangeReceiver.onReceive(mContext, new Intent());
355
356 // Uses the new setting (16 - 2 = 14MB)
357 verify(mStatsManager, times(1)).registerUsageCallback(
358 any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
359 }
360}