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 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 } 361