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