• 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.server.connectivity;
18 
19 import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
22 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
23 import static android.net.NetworkPolicy.LIMIT_DISABLED;
24 import static android.net.NetworkPolicy.SNOOZE_NEVER;
25 import static android.net.NetworkPolicy.WARNING_DISABLED;
26 import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
27 
28 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
29 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
30 
31 import static junit.framework.TestCase.assertNotNull;
32 
33 import static org.mockito.ArgumentMatchers.any;
34 import static org.mockito.ArgumentMatchers.anyInt;
35 import static org.mockito.ArgumentMatchers.argThat;
36 import static org.mockito.ArgumentMatchers.eq;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.when;
40 
41 import android.app.usage.NetworkStatsManager;
42 import android.content.BroadcastReceiver;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.pm.ApplicationInfo;
46 import android.content.res.Resources;
47 import android.net.ConnectivityManager;
48 import android.net.Network;
49 import android.net.NetworkCapabilities;
50 import android.net.NetworkPolicy;
51 import android.net.NetworkPolicyManager;
52 import android.net.NetworkTemplate;
53 import android.net.StringNetworkSpecifier;
54 import android.os.Handler;
55 import android.provider.Settings;
56 import android.telephony.TelephonyManager;
57 import android.test.mock.MockContentResolver;
58 import android.util.DataUnit;
59 import android.util.RecurrenceRule;
60 
61 import androidx.test.filters.SmallTest;
62 import androidx.test.runner.AndroidJUnit4;
63 
64 import com.android.internal.R;
65 import com.android.internal.util.test.FakeSettingsProvider;
66 import com.android.server.LocalServices;
67 import com.android.server.net.NetworkPolicyManagerInternal;
68 import com.android.server.net.NetworkStatsManagerInternal;
69 
70 import org.junit.After;
71 import org.junit.Before;
72 import org.junit.Test;
73 import org.junit.runner.RunWith;
74 import org.mockito.ArgumentCaptor;
75 import org.mockito.Mock;
76 import org.mockito.Mockito;
77 import org.mockito.MockitoAnnotations;
78 
79 import java.time.Clock;
80 import java.time.Instant;
81 import java.time.Period;
82 import java.time.ZoneId;
83 import java.time.ZonedDateTime;
84 import java.time.temporal.ChronoUnit;
85 
86 @RunWith(AndroidJUnit4.class)
87 @SmallTest
88 public 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 
mockService(String serviceName, Class<T> serviceClass, T service)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
setUp()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
tearDown()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 
setDefaultQuotaGlobalSetting(long setting)166     private void setDefaultQuotaGlobalSetting(long setting) {
167         Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
168                 (int) setting);
169     }
170 
testGetMultipathPreference( long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit, long defaultGlobalSetting, long defaultResSetting, boolean roaming)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
testGetMultipathPreference_SubscriptionQuota()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
testGetMultipathPreference_UserWarningQuota()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
testGetMultipathPreference_SnoozedWarningQuota()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
testGetMultipathPreference_SnoozedBothQuota()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
testGetMultipathPreference_SettingChanged()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
testGetMultipathPreference_ResourceChanged()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 }
361