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