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