Remi NGUYEN VAN | 6a7a5a1 | 2018-04-02 21:18:52 +0900 | [diff] [blame] | 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.assertEquals; |
| 32 | import static junit.framework.TestCase.assertNotNull; |
| 33 | |
| 34 | import static org.mockito.ArgumentMatchers.any; |
| 35 | import static org.mockito.ArgumentMatchers.anyInt; |
| 36 | import static org.mockito.ArgumentMatchers.argThat; |
| 37 | import static org.mockito.ArgumentMatchers.eq; |
| 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.Network; |
| 50 | import android.net.NetworkCapabilities; |
| 51 | import android.net.NetworkPolicy; |
| 52 | import android.net.NetworkPolicyManager; |
| 53 | import android.net.NetworkTemplate; |
| 54 | import android.net.StringNetworkSpecifier; |
| 55 | import android.os.Handler; |
| 56 | import android.provider.Settings; |
| 57 | import android.support.test.filters.SmallTest; |
| 58 | import android.support.test.runner.AndroidJUnit4; |
| 59 | import android.telephony.TelephonyManager; |
| 60 | import android.test.mock.MockContentResolver; |
| 61 | import android.util.DataUnit; |
| 62 | import android.util.RecurrenceRule; |
| 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 | } |