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